superjs-core 0.3.3 → 0.3.5
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/dist/async/index.d.ts +84 -1
- package/dist/async/index.js +151 -0
- package/dist/async/index.js.map +1 -1
- package/dist/collection/index.d.ts +7 -1
- package/dist/collection/index.js +55 -0
- package/dist/collection/index.js.map +1 -1
- package/dist/date/index.d.ts +71 -1
- package/dist/date/index.js +121 -1
- package/dist/date/index.js.map +1 -1
- package/dist/error/index.d.ts +148 -0
- package/dist/error/index.js +115 -0
- package/dist/error/index.js.map +1 -0
- package/dist/index-BgG21uJC.d.ts +166 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +506 -0
- package/dist/index.js.map +1 -1
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/index.js +214 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/transports.d.ts +1 -0
- package/dist/logger/transports.js +122 -0
- package/dist/logger/transports.js.map +1 -0
- package/dist/math/index.d.ts +59 -1
- package/dist/math/index.js +69 -0
- package/dist/math/index.js.map +1 -1
- package/dist/string/index.d.ts +41 -1
- package/dist/string/index.js +117 -0
- package/dist/string/index.js.map +1 -1
- package/dist/validation/index.d.ts +106 -0
- package/dist/validation/index.js +183 -0
- package/dist/validation/index.js.map +1 -0
- package/package.json +17 -1
package/dist/async/index.d.ts
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
import { RetryOptions } from '../core/index.js';
|
|
2
2
|
|
|
3
|
+
interface QueueOptions {
|
|
4
|
+
concurrency?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Priority-based async task queue with concurrency control.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const queue = new Queue({ concurrency: 2 })
|
|
11
|
+
* const result = await queue.add(() => fetch('/api/data'))
|
|
12
|
+
*/
|
|
13
|
+
declare class Queue<T = unknown> {
|
|
14
|
+
private _tasks;
|
|
15
|
+
private _running;
|
|
16
|
+
private _paused;
|
|
17
|
+
private _idleResolve;
|
|
18
|
+
private _maxConcurrency;
|
|
19
|
+
constructor(options?: QueueOptions);
|
|
20
|
+
get pending(): number;
|
|
21
|
+
get running(): number;
|
|
22
|
+
add<R>(task: () => Promise<R>, options?: {
|
|
23
|
+
priority?: number;
|
|
24
|
+
}): Promise<R>;
|
|
25
|
+
pause(): void;
|
|
26
|
+
resume(): void;
|
|
27
|
+
clear(): void;
|
|
28
|
+
onIdle(): Promise<void>;
|
|
29
|
+
private _process;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Semaphore for controlling concurrent access to a resource.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const sem = new Semaphore(2)
|
|
37
|
+
* await sem.use(async () => {
|
|
38
|
+
* // Only 2 callers at a time
|
|
39
|
+
* await doSomething()
|
|
40
|
+
* })
|
|
41
|
+
*/
|
|
42
|
+
declare class Semaphore {
|
|
43
|
+
private _available;
|
|
44
|
+
private _waiting;
|
|
45
|
+
constructor(concurrency: number);
|
|
46
|
+
get available(): number;
|
|
47
|
+
acquire(): Promise<() => void>;
|
|
48
|
+
use<T>(fn: () => Promise<T>): Promise<T>;
|
|
49
|
+
private _release;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Options for memoizeAsync.
|
|
54
|
+
*/
|
|
55
|
+
interface MemoizeAsyncOptions {
|
|
56
|
+
/** Time-to-live in milliseconds (default: 60000) */
|
|
57
|
+
ttl?: number;
|
|
58
|
+
/** Return stale value while fetching fresh data (default: false) */
|
|
59
|
+
staleWhileRevalidate?: boolean;
|
|
60
|
+
/** Maximum number of cached entries (default: 100) */
|
|
61
|
+
maxSize?: number;
|
|
62
|
+
/** Custom cache key resolver. Defaults to JSON.stringify of args. */
|
|
63
|
+
resolver?: (...args: unknown[]) => string;
|
|
64
|
+
}
|
|
65
|
+
interface CacheEntry {
|
|
66
|
+
value: unknown;
|
|
67
|
+
timestamp: number;
|
|
68
|
+
promise?: Promise<unknown>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Memoizes an async function with TTL, stale-while-revalidate, and bounded cache.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* const fetchData = memoizeAsync(async (id: string) => {
|
|
75
|
+
* return await api.fetch(id)
|
|
76
|
+
* }, { ttl: 5000, staleWhileRevalidate: true })
|
|
77
|
+
*
|
|
78
|
+
* const data = await fetchData('123') // cached for 5s
|
|
79
|
+
* const data2 = await fetchData('123') // returns cached, refreshes in bg
|
|
80
|
+
*/
|
|
81
|
+
declare function memoizeAsync<T extends (...args: unknown[]) => Promise<unknown>>(fn: T, options?: MemoizeAsyncOptions): T & {
|
|
82
|
+
cache: Map<string, CacheEntry>;
|
|
83
|
+
clear: () => void;
|
|
84
|
+
};
|
|
85
|
+
|
|
3
86
|
/**
|
|
4
87
|
* Delays execution for the given number of milliseconds.
|
|
5
88
|
*/
|
|
@@ -41,4 +124,4 @@ interface Deferred<T> {
|
|
|
41
124
|
reject: (reason: unknown) => void;
|
|
42
125
|
}
|
|
43
126
|
|
|
44
|
-
export { type Deferred, allSettledMap, deferred, parallelMap, pipeline, raceWithTimeout, retryAsync, sleep, timeout };
|
|
127
|
+
export { type Deferred, type MemoizeAsyncOptions, Queue, type QueueOptions, Semaphore, allSettledMap, deferred, memoizeAsync, parallelMap, pipeline, raceWithTimeout, retryAsync, sleep, timeout };
|
package/dist/async/index.js
CHANGED
|
@@ -1,3 +1,151 @@
|
|
|
1
|
+
// src/async/queue.ts
|
|
2
|
+
var Queue = class {
|
|
3
|
+
_tasks = [];
|
|
4
|
+
_running = 0;
|
|
5
|
+
_paused = false;
|
|
6
|
+
_idleResolve = null;
|
|
7
|
+
_maxConcurrency;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this._maxConcurrency = options?.concurrency ?? 1;
|
|
10
|
+
if (this._maxConcurrency < 1) this._maxConcurrency = 1;
|
|
11
|
+
}
|
|
12
|
+
get pending() {
|
|
13
|
+
return this._tasks.length;
|
|
14
|
+
}
|
|
15
|
+
get running() {
|
|
16
|
+
return this._running;
|
|
17
|
+
}
|
|
18
|
+
add(task, options) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
this._tasks.push({
|
|
21
|
+
priority: options?.priority ?? 0,
|
|
22
|
+
task,
|
|
23
|
+
resolve,
|
|
24
|
+
reject
|
|
25
|
+
});
|
|
26
|
+
this._tasks.sort((a, b) => b.priority - a.priority);
|
|
27
|
+
this._process();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
pause() {
|
|
31
|
+
this._paused = true;
|
|
32
|
+
}
|
|
33
|
+
resume() {
|
|
34
|
+
this._paused = false;
|
|
35
|
+
this._process();
|
|
36
|
+
}
|
|
37
|
+
clear() {
|
|
38
|
+
const err = new Error("Queue cleared");
|
|
39
|
+
for (const t of this._tasks) t.reject(err);
|
|
40
|
+
this._tasks = [];
|
|
41
|
+
}
|
|
42
|
+
onIdle() {
|
|
43
|
+
if (this._running === 0 && this._tasks.length === 0) return Promise.resolve();
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
this._idleResolve = resolve;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
_process() {
|
|
49
|
+
if (this._paused) return;
|
|
50
|
+
while (this._running < this._maxConcurrency && this._tasks.length > 0) {
|
|
51
|
+
const item = this._tasks.shift();
|
|
52
|
+
this._running++;
|
|
53
|
+
item.task().then((result) => item.resolve(result)).catch((err) => item.reject(err)).finally(() => {
|
|
54
|
+
this._running--;
|
|
55
|
+
this._process();
|
|
56
|
+
if (this._running === 0 && this._tasks.length === 0 && this._idleResolve) {
|
|
57
|
+
this._idleResolve();
|
|
58
|
+
this._idleResolve = null;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// src/async/semaphore.ts
|
|
66
|
+
var Semaphore = class {
|
|
67
|
+
_available;
|
|
68
|
+
_waiting = [];
|
|
69
|
+
constructor(concurrency) {
|
|
70
|
+
if (concurrency < 1) throw new RangeError("Semaphore concurrency must be >= 1");
|
|
71
|
+
this._available = concurrency;
|
|
72
|
+
}
|
|
73
|
+
get available() {
|
|
74
|
+
return this._available;
|
|
75
|
+
}
|
|
76
|
+
acquire() {
|
|
77
|
+
if (this._available > 0) {
|
|
78
|
+
this._available--;
|
|
79
|
+
return Promise.resolve(this._release.bind(this));
|
|
80
|
+
}
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
this._waiting.push(() => {
|
|
83
|
+
this._available--;
|
|
84
|
+
resolve(this._release.bind(this));
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async use(fn) {
|
|
89
|
+
const release = await this.acquire();
|
|
90
|
+
try {
|
|
91
|
+
return await fn();
|
|
92
|
+
} finally {
|
|
93
|
+
release();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
_release() {
|
|
97
|
+
this._available++;
|
|
98
|
+
if (this._waiting.length > 0) {
|
|
99
|
+
const next = this._waiting.shift();
|
|
100
|
+
if (next) next();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// src/async/memoize.ts
|
|
106
|
+
function memoizeAsync(fn, options) {
|
|
107
|
+
const {
|
|
108
|
+
ttl = 6e4,
|
|
109
|
+
staleWhileRevalidate = false,
|
|
110
|
+
maxSize = 100,
|
|
111
|
+
resolver = (...args) => JSON.stringify(args)
|
|
112
|
+
} = options ?? {};
|
|
113
|
+
const cache = /* @__PURE__ */ new Map();
|
|
114
|
+
const memoized = (async (...args) => {
|
|
115
|
+
const key = resolver(...args);
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
const entry = cache.get(key);
|
|
118
|
+
if (entry && now - entry.timestamp < ttl) {
|
|
119
|
+
return entry.value;
|
|
120
|
+
}
|
|
121
|
+
if (entry && staleWhileRevalidate && now - entry.timestamp >= ttl) {
|
|
122
|
+
if (entry.promise) {
|
|
123
|
+
return entry.value;
|
|
124
|
+
}
|
|
125
|
+
entry.promise = fn(...args).then((result2) => {
|
|
126
|
+
entry.value = result2;
|
|
127
|
+
entry.timestamp = now;
|
|
128
|
+
entry.promise = void 0;
|
|
129
|
+
return result2;
|
|
130
|
+
}).catch((err) => {
|
|
131
|
+
entry.promise = void 0;
|
|
132
|
+
throw err;
|
|
133
|
+
});
|
|
134
|
+
return entry.value;
|
|
135
|
+
}
|
|
136
|
+
const result = await fn(...args);
|
|
137
|
+
cache.set(key, { value: result, timestamp: now });
|
|
138
|
+
if (cache.size > maxSize) {
|
|
139
|
+
const firstKey = cache.keys().next().value;
|
|
140
|
+
if (firstKey !== void 0) cache.delete(firstKey);
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
});
|
|
144
|
+
memoized.cache = cache;
|
|
145
|
+
memoized.clear = () => cache.clear();
|
|
146
|
+
return memoized;
|
|
147
|
+
}
|
|
148
|
+
|
|
1
149
|
// src/async/index.ts
|
|
2
150
|
function sleep(ms) {
|
|
3
151
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -76,8 +224,11 @@ function deferred() {
|
|
|
76
224
|
return { promise, resolve, reject };
|
|
77
225
|
}
|
|
78
226
|
export {
|
|
227
|
+
Queue,
|
|
228
|
+
Semaphore,
|
|
79
229
|
allSettledMap,
|
|
80
230
|
deferred,
|
|
231
|
+
memoizeAsync,
|
|
81
232
|
parallelMap,
|
|
82
233
|
pipeline,
|
|
83
234
|
raceWithTimeout,
|
package/dist/async/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/async/index.ts"],"sourcesContent":["import type { RetryOptions } from '../core/index.js'\r\n\r\n/**\r\n * Delays execution for the given number of milliseconds.\r\n */\r\nexport function sleep(ms: number): Promise<void> {\r\n return new Promise(resolve => setTimeout(resolve, ms))\r\n}\r\n\r\n/**\r\n * Rejects a promise if it does not resolve within the specified timeout.\r\n */\r\nexport async function timeout<T>(promise: Promise<T>, ms: number, errorMessage?: string): Promise<T> {\r\n let timer: ReturnType<typeof setTimeout> | undefined\r\n const timeoutPromise = new Promise<never>((_, reject) => {\r\n timer = setTimeout(() => reject(new Error(errorMessage ?? `Promise timed out after ${ms}ms`)), ms)\r\n })\r\n try {\r\n return await Promise.race([promise, timeoutPromise])\r\n } finally {\r\n if (timer !== undefined) clearTimeout(timer)\r\n }\r\n}\r\n\r\n/**\r\n * Races a promise against a timeout, returning 'timeout' if the timer wins.\r\n */\r\nexport async function raceWithTimeout<T>(promise: Promise<T>, ms: number): Promise<T | 'timeout'> {\r\n let timer: ReturnType<typeof setTimeout> | undefined\r\n const timeoutPromise = new Promise<'timeout'>(resolve => {\r\n timer = setTimeout(() => resolve('timeout'), ms)\r\n })\r\n try {\r\n return await Promise.race([promise, timeoutPromise])\r\n } finally {\r\n if (timer !== undefined) clearTimeout(timer)\r\n }\r\n}\r\n\r\n/**\r\n * Maps over an array with an async function, using Promise.allSettled.\r\n * Returns an array of PromiseSettledResult.\r\n */\r\nexport async function allSettledMap<T, R>(\r\n items: T[],\r\n fn: (item: T) => Promise<R>,\r\n): Promise<PromiseSettledResult<R>[]> {\r\n return await Promise.allSettled(items.map(fn))\r\n}\r\n\r\n/**\r\n * Maps over an array with an async function, limiting concurrency.\r\n *\r\n * @param concurrency - Maximum number of concurrent operations (default Infinity)\r\n */\r\nexport async function parallelMap<T, R>(\r\n items: T[],\r\n fn: (item: T) => Promise<R>,\r\n concurrency = Number.POSITIVE_INFINITY,\r\n): Promise<R[]> {\r\n if (concurrency === Number.POSITIVE_INFINITY) {\r\n return await Promise.all(items.map(fn))\r\n }\r\n\r\n const results: R[] = []\r\n const queue = [...items]\r\n\r\n const worker = async (): Promise<void> => {\r\n while (queue.length > 0) {\r\n const item = queue.shift()!\r\n results.push(await fn(item))\r\n }\r\n }\r\n\r\n const workerCount = Math.min(concurrency, items.length)\r\n const workers = Array.from({ length: workerCount }, () => worker())\r\n await Promise.all(workers)\r\n return results\r\n}\r\n\r\n/**\r\n * Retries an async function with exponential backoff.\r\n */\r\nexport async function retryAsync<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T> {\r\n const { attempts = 3, baseDelay = 1000, maxDelay = 30000, shouldRetry = () => true } = options ?? {}\r\n let lastError: unknown\r\n for (let attempt = 0; attempt < attempts; attempt++) {\r\n try {\r\n return await fn()\r\n } catch (error) {\r\n lastError = error\r\n if (!shouldRetry(error) || attempt >= attempts - 1) break\r\n const delay = Math.min(baseDelay * 2 ** attempt + Math.random() * baseDelay, maxDelay)\r\n await sleep(delay)\r\n }\r\n }\r\n throw lastError\r\n}\r\n\r\n/**\r\n * Composes async functions, passing the result of each to the next.\r\n */\r\nexport async function pipeline<T>(initial: T, ...fns: Array<(arg: T) => Promise<T>>): Promise<T> {\r\n let result = initial\r\n for (const fn of fns) {\r\n result = await fn(result)\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Creates a deferred promise with external resolve and reject methods.\r\n */\r\nexport function deferred<T>(): Deferred<T> {\r\n let resolve!: (value: T) => void\r\n let reject!: (reason: unknown) => void\r\n const promise = new Promise<T>((res, rej) => {\r\n resolve = res\r\n reject = rej\r\n })\r\n return { promise, resolve, reject }\r\n}\r\n\r\nexport interface Deferred<T> {\r\n promise: Promise<T>\r\n resolve: (value: T) => void\r\n reject: (reason: unknown) => void\r\n}\r\n"],"mappings":";AAKO,SAAS,MAAM,IAA2B;AAC/C,SAAO,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACvD;AAKA,eAAsB,QAAW,SAAqB,IAAY,cAAmC;AACnG,MAAI;AACJ,QAAM,iBAAiB,IAAI,QAAe,CAAC,GAAG,WAAW;AACvD,YAAQ,WAAW,MAAM,OAAO,IAAI,MAAM,gBAAgB,2BAA2B,EAAE,IAAI,CAAC,GAAG,EAAE;AAAA,EACnG,CAAC;AACD,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK,CAAC,SAAS,cAAc,CAAC;AAAA,EACrD,UAAE;AACA,QAAI,UAAU,OAAW,cAAa,KAAK;AAAA,EAC7C;AACF;AAKA,eAAsB,gBAAmB,SAAqB,IAAoC;AAChG,MAAI;AACJ,QAAM,iBAAiB,IAAI,QAAmB,aAAW;AACvD,YAAQ,WAAW,MAAM,QAAQ,SAAS,GAAG,EAAE;AAAA,EACjD,CAAC;AACD,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK,CAAC,SAAS,cAAc,CAAC;AAAA,EACrD,UAAE;AACA,QAAI,UAAU,OAAW,cAAa,KAAK;AAAA,EAC7C;AACF;AAMA,eAAsB,cACpB,OACA,IACoC;AACpC,SAAO,MAAM,QAAQ,WAAW,MAAM,IAAI,EAAE,CAAC;AAC/C;AAOA,eAAsB,YACpB,OACA,IACA,cAAc,OAAO,mBACP;AACd,MAAI,gBAAgB,OAAO,mBAAmB;AAC5C,WAAO,MAAM,QAAQ,IAAI,MAAM,IAAI,EAAE,CAAC;AAAA,EACxC;AAEA,QAAM,UAAe,CAAC;AACtB,QAAM,QAAQ,CAAC,GAAG,KAAK;AAEvB,QAAM,SAAS,YAA2B;AACxC,WAAO,MAAM,SAAS,GAAG;AACvB,YAAM,OAAO,MAAM,MAAM;AACzB,cAAQ,KAAK,MAAM,GAAG,IAAI,CAAC;AAAA,IAC7B;AAAA,EACF;AAEA,QAAM,cAAc,KAAK,IAAI,aAAa,MAAM,MAAM;AACtD,QAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,YAAY,GAAG,MAAM,OAAO,CAAC;AAClE,QAAM,QAAQ,IAAI,OAAO;AACzB,SAAO;AACT;AAKA,eAAsB,WAAc,IAAsB,SAAoC;AAC5F,QAAM,EAAE,WAAW,GAAG,YAAY,KAAM,WAAW,KAAO,cAAc,MAAM,KAAK,IAAI,WAAW,CAAC;AACnG,MAAI;AACJ,WAAS,UAAU,GAAG,UAAU,UAAU,WAAW;AACnD,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,SAAS,OAAO;AACd,kBAAY;AACZ,UAAI,CAAC,YAAY,KAAK,KAAK,WAAW,WAAW,EAAG;AACpD,YAAM,QAAQ,KAAK,IAAI,YAAY,KAAK,UAAU,KAAK,OAAO,IAAI,WAAW,QAAQ;AACrF,YAAM,MAAM,KAAK;AAAA,IACnB;AAAA,EACF;AACA,QAAM;AACR;AAKA,eAAsB,SAAY,YAAe,KAAgD;AAC/F,MAAI,SAAS;AACb,aAAW,MAAM,KAAK;AACpB,aAAS,MAAM,GAAG,MAAM;AAAA,EAC1B;AACA,SAAO;AACT;AAKO,SAAS,WAA2B;AACzC,MAAI;AACJ,MAAI;AACJ,QAAM,UAAU,IAAI,QAAW,CAAC,KAAK,QAAQ;AAC3C,cAAU;AACV,aAAS;AAAA,EACX,CAAC;AACD,SAAO,EAAE,SAAS,SAAS,OAAO;AACpC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/async/queue.ts","../../src/async/semaphore.ts","../../src/async/memoize.ts","../../src/async/index.ts"],"sourcesContent":["export interface QueueOptions {\n concurrency?: number\n}\n\n/**\n * Priority-based async task queue with concurrency control.\n *\n * @example\n * const queue = new Queue({ concurrency: 2 })\n * const result = await queue.add(() => fetch('/api/data'))\n */\nexport class Queue<T = unknown> {\n private _tasks: Array<{\n priority: number\n task: () => Promise<T>\n resolve: (value: T) => void\n reject: (reason: unknown) => void\n }> = []\n private _running = 0\n private _paused = false\n private _idleResolve: (() => void) | null = null\n private _maxConcurrency: number\n\n constructor(options?: QueueOptions) {\n this._maxConcurrency = options?.concurrency ?? 1\n if (this._maxConcurrency < 1) this._maxConcurrency = 1\n }\n\n get pending(): number {\n return this._tasks.length\n }\n\n get running(): number {\n return this._running\n }\n\n add<R>(task: () => Promise<R>, options?: { priority?: number }): Promise<R> {\n return new Promise<R>((resolve, reject) => {\n this._tasks.push({\n priority: options?.priority ?? 0,\n task: task as unknown as () => Promise<T>,\n resolve: resolve as (value: T) => void,\n reject,\n })\n this._tasks.sort((a, b) => b.priority - a.priority)\n this._process()\n })\n }\n\n pause(): void {\n this._paused = true\n }\n\n resume(): void {\n this._paused = false\n this._process()\n }\n\n clear(): void {\n const err = new Error('Queue cleared')\n for (const t of this._tasks) t.reject(err)\n this._tasks = []\n }\n\n onIdle(): Promise<void> {\n if (this._running === 0 && this._tasks.length === 0) return Promise.resolve()\n return new Promise<void>((resolve) => {\n this._idleResolve = resolve\n })\n }\n\n private _process(): void {\n if (this._paused) return\n while (this._running < this._maxConcurrency && this._tasks.length > 0) {\n const item = this._tasks.shift()!\n this._running++\n item\n .task()\n .then((result) => item.resolve(result))\n .catch((err) => item.reject(err))\n .finally(() => {\n this._running--\n this._process()\n if (this._running === 0 && this._tasks.length === 0 && this._idleResolve) {\n this._idleResolve()\n this._idleResolve = null\n }\n })\n }\n }\n}\n\nexport default Queue\n","/**\n * Semaphore for controlling concurrent access to a resource.\n *\n * @example\n * const sem = new Semaphore(2)\n * await sem.use(async () => {\n * // Only 2 callers at a time\n * await doSomething()\n * })\n */\nexport class Semaphore {\n private _available: number\n private _waiting: Array<() => void> = []\n\n constructor(concurrency: number) {\n if (concurrency < 1) throw new RangeError('Semaphore concurrency must be >= 1')\n this._available = concurrency\n }\n\n get available(): number {\n return this._available\n }\n\n acquire(): Promise<() => void> {\n if (this._available > 0) {\n this._available--\n return Promise.resolve(this._release.bind(this))\n }\n return new Promise<() => void>((resolve) => {\n this._waiting.push(() => {\n this._available--\n resolve(this._release.bind(this))\n })\n })\n }\n\n async use<T>(fn: () => Promise<T>): Promise<T> {\n const release = await this.acquire()\n try {\n return await fn()\n } finally {\n release()\n }\n }\n\n private _release(): void {\n this._available++\n if (this._waiting.length > 0) {\n const next = this._waiting.shift()\n if (next) next()\n }\n }\n}\n\nexport default Semaphore\n","/**\n * Options for memoizeAsync.\n */\nexport interface MemoizeAsyncOptions {\n /** Time-to-live in milliseconds (default: 60000) */\n ttl?: number\n /** Return stale value while fetching fresh data (default: false) */\n staleWhileRevalidate?: boolean\n /** Maximum number of cached entries (default: 100) */\n maxSize?: number\n /** Custom cache key resolver. Defaults to JSON.stringify of args. */\n resolver?: (...args: unknown[]) => string\n}\n\ninterface CacheEntry {\n value: unknown\n timestamp: number\n promise?: Promise<unknown>\n}\n\n/**\n * Memoizes an async function with TTL, stale-while-revalidate, and bounded cache.\n *\n * @example\n * const fetchData = memoizeAsync(async (id: string) => {\n * return await api.fetch(id)\n * }, { ttl: 5000, staleWhileRevalidate: true })\n *\n * const data = await fetchData('123') // cached for 5s\n * const data2 = await fetchData('123') // returns cached, refreshes in bg\n */\nexport function memoizeAsync<T extends (...args: unknown[]) => Promise<unknown>>(\n fn: T,\n options?: MemoizeAsyncOptions,\n): T & {\n cache: Map<string, CacheEntry>\n clear: () => void\n} {\n const {\n ttl = 60000,\n staleWhileRevalidate = false,\n maxSize = 100,\n resolver = (...args: unknown[]) => JSON.stringify(args),\n } = options ?? {}\n\n const cache = new Map<string, CacheEntry>()\n\n const memoized = (async (...args: unknown[]): Promise<unknown> => {\n const key = resolver(...args)\n const now = Date.now()\n const entry = cache.get(key)\n\n if (entry && now - entry.timestamp < ttl) {\n return entry.value\n }\n\n if (entry && staleWhileRevalidate && now - entry.timestamp >= ttl) {\n if (entry.promise) {\n return entry.value\n }\n entry.promise = fn(...args)\n .then((result) => {\n entry.value = result\n entry.timestamp = now\n entry.promise = undefined\n return result\n })\n .catch((err) => {\n entry.promise = undefined\n throw err\n })\n return entry.value\n }\n\n const result = await fn(...args)\n cache.set(key, { value: result, timestamp: now })\n\n if (cache.size > maxSize) {\n const firstKey = cache.keys().next().value\n if (firstKey !== undefined) cache.delete(firstKey)\n }\n\n return result\n }) as T & {\n cache: Map<string, CacheEntry>\n clear: () => void\n }\n\n memoized.cache = cache\n memoized.clear = () => cache.clear()\n\n return memoized\n}\n\nexport default memoizeAsync\n","import type { RetryOptions } from '../core/index.js'\r\n\r\n/**\r\n * Delays execution for the given number of milliseconds.\r\n */\r\nexport function sleep(ms: number): Promise<void> {\r\n return new Promise(resolve => setTimeout(resolve, ms))\r\n}\r\n\r\n/**\r\n * Rejects a promise if it does not resolve within the specified timeout.\r\n */\r\nexport async function timeout<T>(promise: Promise<T>, ms: number, errorMessage?: string): Promise<T> {\r\n let timer: ReturnType<typeof setTimeout> | undefined\r\n const timeoutPromise = new Promise<never>((_, reject) => {\r\n timer = setTimeout(() => reject(new Error(errorMessage ?? `Promise timed out after ${ms}ms`)), ms)\r\n })\r\n try {\r\n return await Promise.race([promise, timeoutPromise])\r\n } finally {\r\n if (timer !== undefined) clearTimeout(timer)\r\n }\r\n}\r\n\r\n/**\r\n * Races a promise against a timeout, returning 'timeout' if the timer wins.\r\n */\r\nexport async function raceWithTimeout<T>(promise: Promise<T>, ms: number): Promise<T | 'timeout'> {\r\n let timer: ReturnType<typeof setTimeout> | undefined\r\n const timeoutPromise = new Promise<'timeout'>(resolve => {\r\n timer = setTimeout(() => resolve('timeout'), ms)\r\n })\r\n try {\r\n return await Promise.race([promise, timeoutPromise])\r\n } finally {\r\n if (timer !== undefined) clearTimeout(timer)\r\n }\r\n}\r\n\r\n/**\r\n * Maps over an array with an async function, using Promise.allSettled.\r\n * Returns an array of PromiseSettledResult.\r\n */\r\nexport async function allSettledMap<T, R>(\r\n items: T[],\r\n fn: (item: T) => Promise<R>,\r\n): Promise<PromiseSettledResult<R>[]> {\r\n return await Promise.allSettled(items.map(fn))\r\n}\r\n\r\n/**\r\n * Maps over an array with an async function, limiting concurrency.\r\n *\r\n * @param concurrency - Maximum number of concurrent operations (default Infinity)\r\n */\r\nexport async function parallelMap<T, R>(\r\n items: T[],\r\n fn: (item: T) => Promise<R>,\r\n concurrency = Number.POSITIVE_INFINITY,\r\n): Promise<R[]> {\r\n if (concurrency === Number.POSITIVE_INFINITY) {\r\n return await Promise.all(items.map(fn))\r\n }\r\n\r\n const results: R[] = []\r\n const queue = [...items]\r\n\r\n const worker = async (): Promise<void> => {\r\n while (queue.length > 0) {\r\n const item = queue.shift()!\r\n results.push(await fn(item))\r\n }\r\n }\r\n\r\n const workerCount = Math.min(concurrency, items.length)\r\n const workers = Array.from({ length: workerCount }, () => worker())\r\n await Promise.all(workers)\r\n return results\r\n}\r\n\r\n/**\r\n * Retries an async function with exponential backoff.\r\n */\r\nexport async function retryAsync<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T> {\r\n const { attempts = 3, baseDelay = 1000, maxDelay = 30000, shouldRetry = () => true } = options ?? {}\r\n let lastError: unknown\r\n for (let attempt = 0; attempt < attempts; attempt++) {\r\n try {\r\n return await fn()\r\n } catch (error) {\r\n lastError = error\r\n if (!shouldRetry(error) || attempt >= attempts - 1) break\r\n const delay = Math.min(baseDelay * 2 ** attempt + Math.random() * baseDelay, maxDelay)\r\n await sleep(delay)\r\n }\r\n }\r\n throw lastError\r\n}\r\n\r\n/**\r\n * Composes async functions, passing the result of each to the next.\r\n */\r\nexport async function pipeline<T>(initial: T, ...fns: Array<(arg: T) => Promise<T>>): Promise<T> {\r\n let result = initial\r\n for (const fn of fns) {\r\n result = await fn(result)\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Creates a deferred promise with external resolve and reject methods.\r\n */\r\nexport function deferred<T>(): Deferred<T> {\r\n let resolve!: (value: T) => void\r\n let reject!: (reason: unknown) => void\r\n const promise = new Promise<T>((res, rej) => {\r\n resolve = res\r\n reject = rej\r\n })\r\n return { promise, resolve, reject }\r\n}\r\n\r\nexport interface Deferred<T> {\r\n promise: Promise<T>\r\n resolve: (value: T) => void\r\n reject: (reason: unknown) => void\r\n}\r\n\r\n// ─── Queue, Semaphore, memoizeAsync ─────────────────────\r\nexport { Queue } from './queue.js'\r\nexport type { QueueOptions } from './queue.js'\r\nexport { Semaphore } from './semaphore.js'\r\nexport { memoizeAsync } from './memoize.js'\r\nexport type { MemoizeAsyncOptions } from './memoize.js'\r\n"],"mappings":"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","names":["result"]}
|
|
@@ -71,5 +71,11 @@ declare function last<T>(items: T[]): T | undefined;
|
|
|
71
71
|
*/
|
|
72
72
|
declare function isEmpty(value: unknown): boolean;
|
|
73
73
|
type SortDirection = 'asc' | 'desc';
|
|
74
|
+
declare function topoSort<T extends {
|
|
75
|
+
id: string;
|
|
76
|
+
dependencies?: string[];
|
|
77
|
+
}>(items: T[]): T[];
|
|
78
|
+
declare function slidingWindows<T>(items: T[], size: number, step?: number): T[][];
|
|
79
|
+
declare function tumblingWindows<T>(items: T[], size: number): T[][];
|
|
74
80
|
|
|
75
|
-
export { type SortDirection, chunk, first, flatten, groupBy, isEmpty, keyBy, last, omit, orderBy, pick, pluck, sample, sampleSize, shuffle, sortBy, uniq, uniqueBy };
|
|
81
|
+
export { type SortDirection, chunk, first, flatten, groupBy, isEmpty, keyBy, last, omit, orderBy, pick, pluck, sample, sampleSize, shuffle, slidingWindows, sortBy, topoSort, tumblingWindows, uniq, uniqueBy };
|
package/dist/collection/index.js
CHANGED
|
@@ -118,6 +118,58 @@ function isEmpty(value) {
|
|
|
118
118
|
if (value !== null && typeof value === "object") return Object.keys(value).length === 0;
|
|
119
119
|
return false;
|
|
120
120
|
}
|
|
121
|
+
function topoSort(items) {
|
|
122
|
+
const adj = /* @__PURE__ */ new Map();
|
|
123
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
124
|
+
const itemMap = /* @__PURE__ */ new Map();
|
|
125
|
+
for (const item of items) {
|
|
126
|
+
itemMap.set(item.id, item);
|
|
127
|
+
if (!adj.has(item.id)) adj.set(item.id, []);
|
|
128
|
+
if (!inDegree.has(item.id)) inDegree.set(item.id, 0);
|
|
129
|
+
}
|
|
130
|
+
for (const item of items) {
|
|
131
|
+
if (item.dependencies) {
|
|
132
|
+
for (const depId of item.dependencies) {
|
|
133
|
+
adj.get(depId)?.push(item.id);
|
|
134
|
+
inDegree.set(item.id, (inDegree.get(item.id) ?? 0) + 1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const queue = [];
|
|
139
|
+
for (const [id, degree] of inDegree) {
|
|
140
|
+
if (degree === 0) queue.push(id);
|
|
141
|
+
}
|
|
142
|
+
const sorted = [];
|
|
143
|
+
while (queue.length > 0) {
|
|
144
|
+
const id = queue.shift();
|
|
145
|
+
sorted.push(id);
|
|
146
|
+
for (const neighbor of adj.get(id) ?? []) {
|
|
147
|
+
const newDegree = (inDegree.get(neighbor) ?? 1) - 1;
|
|
148
|
+
inDegree.set(neighbor, newDegree);
|
|
149
|
+
if (newDegree === 0) queue.push(neighbor);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (sorted.length !== items.length) {
|
|
153
|
+
throw new Error("Circular dependency detected");
|
|
154
|
+
}
|
|
155
|
+
return sorted.map((id) => itemMap.get(id));
|
|
156
|
+
}
|
|
157
|
+
function slidingWindows(items, size, step = 1) {
|
|
158
|
+
if (size <= 0 || items.length === 0 || step <= 0) return [];
|
|
159
|
+
const result = [];
|
|
160
|
+
for (let i = 0; i + size <= items.length; i += step) {
|
|
161
|
+
result.push(items.slice(i, i + size));
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
function tumblingWindows(items, size) {
|
|
166
|
+
if (size <= 0 || items.length === 0) return [];
|
|
167
|
+
const result = [];
|
|
168
|
+
for (let i = 0; i < items.length; i += size) {
|
|
169
|
+
result.push(items.slice(i, i + size));
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
121
173
|
export {
|
|
122
174
|
chunk,
|
|
123
175
|
first,
|
|
@@ -133,7 +185,10 @@ export {
|
|
|
133
185
|
sample,
|
|
134
186
|
sampleSize,
|
|
135
187
|
shuffle,
|
|
188
|
+
slidingWindows,
|
|
136
189
|
sortBy,
|
|
190
|
+
topoSort,
|
|
191
|
+
tumblingWindows,
|
|
137
192
|
uniq,
|
|
138
193
|
uniqueBy
|
|
139
194
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/collection/index.ts"],"sourcesContent":["/**\r\n * Groups an array of items by a key extracted from each item.\r\n *\r\n * @example groupBy([{ type: 'a' }, { type: 'b' }, { type: 'a' }], x => x.type)\r\n * // => { a: [{ type: 'a' }, { type: 'a' }], b: [{ type: 'b' }] }\r\n */\r\nexport function groupBy<T, K extends string | number | symbol>(items: T[], keyFn: (item: T) => K): Record<K, T[]> {\r\n const result = {} as Record<K, T[]>\r\n for (const item of items) {\r\n const key = keyFn(item)\r\n if (!result[key]) result[key] = []\r\n result[key]!.push(item)\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Creates an object keyed by the result of keyFn for each item.\r\n *\r\n * @example keyBy([{ id: 1, name: 'a' }, { id: 2, name: 'b' }], x => x.id)\r\n * // => { 1: { id: 1, name: 'a' }, 2: { id: 2, name: 'b' } }\r\n */\r\nexport function keyBy<T, K extends string | number | symbol>(items: T[], keyFn: (item: T) => K): Record<K, T> {\r\n const result = {} as Record<K, T>\r\n for (const item of items) {\r\n const key = keyFn(item)\r\n result[key] = item\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Returns a copy of the object with the specified keys omitted.\r\n */\r\nexport function omit<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {\r\n const result = { ...obj } as T\r\n for (const key of keys) {\r\n delete result[key]\r\n }\r\n return result as Omit<T, K>\r\n}\r\n\r\n/**\r\n * Returns a copy of the object with only the specified keys.\r\n */\r\nexport function pick<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {\r\n const result = {} as Pick<T, K>\r\n for (const key of keys) {\r\n if (key in obj) {\r\n result[key] = obj[key]\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Extracts a property value from each item in an array.\r\n */\r\nexport function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {\r\n return items.map(item => item[key])\r\n}\r\n\r\n/**\r\n * Randomizes array order in-place using the Fisher-Yates algorithm.\r\n */\r\nexport function shuffle<T>(items: T[]): T[] {\r\n const result = [...items]\r\n for (let i = result.length - 1; i > 0; i--) {\r\n const j = Math.floor(Math.random() * (i + 1))\r\n const temp = result[i]!\r\n result[i] = result[j]!\r\n result[j] = temp\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Returns a random element from the array, or undefined if empty.\r\n */\r\nexport function sample<T>(items: T[]): T | undefined {\r\n return items.length > 0 ? items[Math.floor(Math.random() * items.length)] : undefined\r\n}\r\n\r\n/**\r\n * Returns an array of `size` random elements (without duplicates).\r\n */\r\nexport function sampleSize<T>(items: T[], size: number): T[] {\r\n if (size <= 0 || items.length === 0) return []\r\n const pool = shuffle(items)\r\n return pool.slice(0, Math.min(size, items.length))\r\n}\r\n\r\n/**\r\n * Splits an array into chunks of the specified size.\r\n */\r\nexport function chunk<T>(items: T[], size: number): T[][] {\r\n if (size <= 0 || items.length === 0) return []\r\n const result: T[][] = []\r\n for (let i = 0; i < items.length; i += size) {\r\n result.push(items.slice(i, i + size))\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Returns a sorted copy of the array using the provided criteria functions.\r\n * Earlier criteria take precedence.\r\n */\r\nfunction compareValues(a: unknown, b: unknown): number {\r\n if (a === b) return 0\r\n if (a == null) return -1\r\n if (b == null) return 1\r\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : 1\r\n if (typeof a === 'number' && typeof b === 'number') return a < b ? -1 : 1\r\n return String(a) < String(b) ? -1 : 1\r\n}\r\n\r\nexport function sortBy<T>(items: T[], ...criteria: Array<(item: T) => unknown>): T[] {\r\n return [...items].sort((a, b) => {\r\n for (const criterion of criteria) {\r\n const cmp = compareValues(criterion(a), criterion(b))\r\n if (cmp !== 0) return cmp\r\n }\r\n return 0\r\n })\r\n}\r\n\r\n/**\r\n * Returns a sorted copy of the array by a single key and direction.\r\n */\r\nexport function orderBy<T>(items: T[], key: (item: T) => unknown, direction: SortDirection = 'asc'): T[] {\r\n return [...items].sort((a, b) => {\r\n const cmp = compareValues(key(a), key(b))\r\n return direction === 'asc' ? cmp : -cmp\r\n })\r\n}\r\n\r\n/**\r\n * Returns unique elements based on the result of keyFn.\r\n */\r\nexport function uniqueBy<T>(items: T[], keyFn: (item: T) => unknown): T[] {\r\n const seen = new Set<unknown>()\r\n return items.filter(item => {\r\n const key = keyFn(item)\r\n if (seen.has(key)) return false\r\n seen.add(key)\r\n return true\r\n })\r\n}\r\n\r\n/**\r\n * Flattens an array of arrays one level.\r\n */\r\nexport function flatten<T>(items: T[][]): T[] {\r\n const result: T[] = []\r\n for (const sub of items) {\r\n for (const item of sub) {\r\n result.push(item)\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Returns an array with unique primitive values.\r\n */\r\nexport function uniq<T>(items: T[]): T[] {\r\n return [...new Set(items)]\r\n}\r\n\r\n/**\r\n * Returns the first element, or undefined if empty.\r\n */\r\nexport function first<T>(items: T[]): T | undefined {\r\n return items[0]\r\n}\r\n\r\n/**\r\n * Returns the last element, or undefined if empty.\r\n */\r\nexport function last<T>(items: T[]): T | undefined {\r\n return items[items.length - 1]\r\n}\r\n\r\n/**\r\n * Checks if a value is empty.\r\n * Works for arrays, objects, strings, Map, and Set.\r\n */\r\nexport function isEmpty(value: unknown): boolean {\r\n if (Array.isArray(value)) return value.length === 0\r\n if (typeof value === 'string') return value.length === 0\r\n if (value instanceof Map || value instanceof Set) return value.size === 0\r\n if (value !== null && typeof value === 'object') return Object.keys(value as Record<string, unknown>).length === 0\r\n return false\r\n}\r\n\r\nexport type SortDirection = 'asc' | 'desc'\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/collection/index.ts"],"sourcesContent":["/**\r\n * Groups an array of items by a key extracted from each item.\r\n *\r\n * @example groupBy([{ type: 'a' }, { type: 'b' }, { type: 'a' }], x => x.type)\r\n * // => { a: [{ type: 'a' }, { type: 'a' }], b: [{ type: 'b' }] }\r\n */\r\nexport function groupBy<T, K extends string | number | symbol>(items: T[], keyFn: (item: T) => K): Record<K, T[]> {\r\n const result = {} as Record<K, T[]>\r\n for (const item of items) {\r\n const key = keyFn(item)\r\n if (!result[key]) result[key] = []\r\n result[key]!.push(item)\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Creates an object keyed by the result of keyFn for each item.\r\n *\r\n * @example keyBy([{ id: 1, name: 'a' }, { id: 2, name: 'b' }], x => x.id)\r\n * // => { 1: { id: 1, name: 'a' }, 2: { id: 2, name: 'b' } }\r\n */\r\nexport function keyBy<T, K extends string | number | symbol>(items: T[], keyFn: (item: T) => K): Record<K, T> {\r\n const result = {} as Record<K, T>\r\n for (const item of items) {\r\n const key = keyFn(item)\r\n result[key] = item\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Returns a copy of the object with the specified keys omitted.\r\n */\r\nexport function omit<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {\r\n const result = { ...obj } as T\r\n for (const key of keys) {\r\n delete result[key]\r\n }\r\n return result as Omit<T, K>\r\n}\r\n\r\n/**\r\n * Returns a copy of the object with only the specified keys.\r\n */\r\nexport function pick<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {\r\n const result = {} as Pick<T, K>\r\n for (const key of keys) {\r\n if (key in obj) {\r\n result[key] = obj[key]\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Extracts a property value from each item in an array.\r\n */\r\nexport function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {\r\n return items.map(item => item[key])\r\n}\r\n\r\n/**\r\n * Randomizes array order in-place using the Fisher-Yates algorithm.\r\n */\r\nexport function shuffle<T>(items: T[]): T[] {\r\n const result = [...items]\r\n for (let i = result.length - 1; i > 0; i--) {\r\n const j = Math.floor(Math.random() * (i + 1))\r\n const temp = result[i]!\r\n result[i] = result[j]!\r\n result[j] = temp\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Returns a random element from the array, or undefined if empty.\r\n */\r\nexport function sample<T>(items: T[]): T | undefined {\r\n return items.length > 0 ? items[Math.floor(Math.random() * items.length)] : undefined\r\n}\r\n\r\n/**\r\n * Returns an array of `size` random elements (without duplicates).\r\n */\r\nexport function sampleSize<T>(items: T[], size: number): T[] {\r\n if (size <= 0 || items.length === 0) return []\r\n const pool = shuffle(items)\r\n return pool.slice(0, Math.min(size, items.length))\r\n}\r\n\r\n/**\r\n * Splits an array into chunks of the specified size.\r\n */\r\nexport function chunk<T>(items: T[], size: number): T[][] {\r\n if (size <= 0 || items.length === 0) return []\r\n const result: T[][] = []\r\n for (let i = 0; i < items.length; i += size) {\r\n result.push(items.slice(i, i + size))\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Returns a sorted copy of the array using the provided criteria functions.\r\n * Earlier criteria take precedence.\r\n */\r\nfunction compareValues(a: unknown, b: unknown): number {\r\n if (a === b) return 0\r\n if (a == null) return -1\r\n if (b == null) return 1\r\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : 1\r\n if (typeof a === 'number' && typeof b === 'number') return a < b ? -1 : 1\r\n return String(a) < String(b) ? -1 : 1\r\n}\r\n\r\nexport function sortBy<T>(items: T[], ...criteria: Array<(item: T) => unknown>): T[] {\r\n return [...items].sort((a, b) => {\r\n for (const criterion of criteria) {\r\n const cmp = compareValues(criterion(a), criterion(b))\r\n if (cmp !== 0) return cmp\r\n }\r\n return 0\r\n })\r\n}\r\n\r\n/**\r\n * Returns a sorted copy of the array by a single key and direction.\r\n */\r\nexport function orderBy<T>(items: T[], key: (item: T) => unknown, direction: SortDirection = 'asc'): T[] {\r\n return [...items].sort((a, b) => {\r\n const cmp = compareValues(key(a), key(b))\r\n return direction === 'asc' ? cmp : -cmp\r\n })\r\n}\r\n\r\n/**\r\n * Returns unique elements based on the result of keyFn.\r\n */\r\nexport function uniqueBy<T>(items: T[], keyFn: (item: T) => unknown): T[] {\r\n const seen = new Set<unknown>()\r\n return items.filter(item => {\r\n const key = keyFn(item)\r\n if (seen.has(key)) return false\r\n seen.add(key)\r\n return true\r\n })\r\n}\r\n\r\n/**\r\n * Flattens an array of arrays one level.\r\n */\r\nexport function flatten<T>(items: T[][]): T[] {\r\n const result: T[] = []\r\n for (const sub of items) {\r\n for (const item of sub) {\r\n result.push(item)\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * Returns an array with unique primitive values.\r\n */\r\nexport function uniq<T>(items: T[]): T[] {\r\n return [...new Set(items)]\r\n}\r\n\r\n/**\r\n * Returns the first element, or undefined if empty.\r\n */\r\nexport function first<T>(items: T[]): T | undefined {\r\n return items[0]\r\n}\r\n\r\n/**\r\n * Returns the last element, or undefined if empty.\r\n */\r\nexport function last<T>(items: T[]): T | undefined {\r\n return items[items.length - 1]\r\n}\r\n\r\n/**\r\n * Checks if a value is empty.\r\n * Works for arrays, objects, strings, Map, and Set.\r\n */\r\nexport function isEmpty(value: unknown): boolean {\r\n if (Array.isArray(value)) return value.length === 0\r\n if (typeof value === 'string') return value.length === 0\r\n if (value instanceof Map || value instanceof Set) return value.size === 0\r\n if (value !== null && typeof value === 'object') return Object.keys(value as Record<string, unknown>).length === 0\r\n return false\r\n}\r\n\r\nexport type SortDirection = 'asc' | 'desc'\r\n\r\nexport function topoSort<T extends { id: string; dependencies?: string[] }>(items: T[]): T[] {\r\n const adj = new Map<string, string[]>()\r\n const inDegree = new Map<string, number>()\r\n const itemMap = new Map<string, T>()\r\n\r\n for (const item of items) {\r\n itemMap.set(item.id, item)\r\n if (!adj.has(item.id)) adj.set(item.id, [])\r\n if (!inDegree.has(item.id)) inDegree.set(item.id, 0)\r\n }\r\n\r\n for (const item of items) {\r\n if (item.dependencies) {\r\n for (const depId of item.dependencies) {\r\n adj.get(depId)?.push(item.id)\r\n inDegree.set(item.id, (inDegree.get(item.id) ?? 0) + 1)\r\n }\r\n }\r\n }\r\n\r\n const queue: string[] = []\r\n for (const [id, degree] of inDegree) {\r\n if (degree === 0) queue.push(id)\r\n }\r\n\r\n const sorted: string[] = []\r\n while (queue.length > 0) {\r\n const id = queue.shift()!\r\n sorted.push(id)\r\n for (const neighbor of adj.get(id) ?? []) {\r\n const newDegree = (inDegree.get(neighbor) ?? 1) - 1\r\n inDegree.set(neighbor, newDegree)\r\n if (newDegree === 0) queue.push(neighbor)\r\n }\r\n }\r\n\r\n if (sorted.length !== items.length) {\r\n throw new Error('Circular dependency detected')\r\n }\r\n\r\n return sorted.map(id => itemMap.get(id)!)\r\n}\r\n\r\nexport function slidingWindows<T>(items: T[], size: number, step: number = 1): T[][] {\r\n if (size <= 0 || items.length === 0 || step <= 0) return []\r\n const result: T[][] = []\r\n for (let i = 0; i + size <= items.length; i += step) {\r\n result.push(items.slice(i, i + size))\r\n }\r\n return result\r\n}\r\n\r\nexport function tumblingWindows<T>(items: T[], size: number): T[][] {\r\n if (size <= 0 || items.length === 0) return []\r\n const result: T[][] = []\r\n for (let i = 0; i < items.length; i += size) {\r\n result.push(items.slice(i, i + size))\r\n }\r\n return result\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/date/index.d.ts
CHANGED
|
@@ -199,5 +199,75 @@ declare function addBusinessDays(date: Date, days: number): Date;
|
|
|
199
199
|
* @throws {InvalidDateError} If the birth date is invalid.
|
|
200
200
|
*/
|
|
201
201
|
declare function calculateAge(birthDate: Date): number;
|
|
202
|
+
/**
|
|
203
|
+
* Returns a human-readable relative time string (e.g. "5 menit yang lalu").
|
|
204
|
+
*
|
|
205
|
+
* @param date - The past date.
|
|
206
|
+
* @param options - Options with locale ('id' by default, 'en' supported).
|
|
207
|
+
* @returns A relative time string.
|
|
208
|
+
*/
|
|
209
|
+
declare function timeAgo(date: Date, options?: {
|
|
210
|
+
locale?: string;
|
|
211
|
+
}): string;
|
|
212
|
+
/**
|
|
213
|
+
* Shows the time remaining until a future date.
|
|
214
|
+
*
|
|
215
|
+
* @param target - The target future date.
|
|
216
|
+
* @param options - Options with locale ('id' by default, 'en' supported).
|
|
217
|
+
* @returns A relative time string.
|
|
218
|
+
*/
|
|
219
|
+
declare function timeRemaining(target: Date, options?: {
|
|
220
|
+
locale?: string;
|
|
221
|
+
}): string;
|
|
222
|
+
/**
|
|
223
|
+
* Represents a duration split into calendar and time components.
|
|
224
|
+
*/
|
|
225
|
+
interface Duration {
|
|
226
|
+
years: number;
|
|
227
|
+
months: number;
|
|
228
|
+
days: number;
|
|
229
|
+
hours: number;
|
|
230
|
+
minutes: number;
|
|
231
|
+
seconds: number;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Formats a Duration object into a human-readable string.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* formatDuration({ hours: 2, minutes: 30, seconds: 15 }) // "2 jam 30 menit 15 detik"
|
|
238
|
+
* formatDuration({ hours: 2, minutes: 30 }, { locale: 'en' }) // "2 hours 30 minutes"
|
|
239
|
+
*
|
|
240
|
+
* @param duration - The duration to format.
|
|
241
|
+
* @param options - Options with locale ('id' by default, 'en' supported).
|
|
242
|
+
* @returns A formatted duration string.
|
|
243
|
+
*/
|
|
244
|
+
declare function formatDuration(duration: Duration, options?: {
|
|
245
|
+
locale?: string;
|
|
246
|
+
}): string;
|
|
247
|
+
/** UTC+7 — Western Indonesia Time (WIB). */
|
|
248
|
+
declare const TIMEZONE_WIB = 7;
|
|
249
|
+
/** UTC+8 — Central Indonesia Time (WITA). */
|
|
250
|
+
declare const TIMEZONE_WITA = 8;
|
|
251
|
+
/** UTC+9 — Eastern Indonesia Time (WIT). */
|
|
252
|
+
declare const TIMEZONE_WIT = 9;
|
|
253
|
+
/**
|
|
254
|
+
* Converts a date to a specific timezone offset by returning a new Date whose
|
|
255
|
+
* local-time getters (getHours, getMinutes etc.) reflect the target timezone.
|
|
256
|
+
*
|
|
257
|
+
* @param date - The source date.
|
|
258
|
+
* @param offsetHours - The timezone offset in hours (e.g. 7 for WIB).
|
|
259
|
+
* @returns A new Date adjusted to the target timezone.
|
|
260
|
+
* @throws {InvalidDateError} If the input date is invalid.
|
|
261
|
+
*/
|
|
262
|
+
declare function toTimezone(date: Date, offsetHours: number): Date;
|
|
263
|
+
/**
|
|
264
|
+
* Formats a date in a specific timezone using `formatDate` tokens.
|
|
265
|
+
*
|
|
266
|
+
* @param date - The source date.
|
|
267
|
+
* @param format - The format string (see `formatDate` for supported tokens).
|
|
268
|
+
* @param offsetHours - The timezone offset in hours.
|
|
269
|
+
* @returns The formatted date string.
|
|
270
|
+
*/
|
|
271
|
+
declare function formatInTimezone(date: Date, format: string, offsetHours: number): string;
|
|
202
272
|
|
|
203
|
-
export { type DateDiff, InvalidDateError, addBusinessDays, addDays, addMonths, addYears, calculateAge, dateDiff, endOfDay, endOfMonth, endOfYear, formatDate, isAfter, isBefore, isBetween, isBusinessDay, isLeapYear, isWeekend, parseDate, startOfDay, startOfMonth, startOfYear };
|
|
273
|
+
export { type DateDiff, type Duration, InvalidDateError, TIMEZONE_WIB, TIMEZONE_WIT, TIMEZONE_WITA, addBusinessDays, addDays, addMonths, addYears, calculateAge, dateDiff, endOfDay, endOfMonth, endOfYear, formatDate, formatDuration, formatInTimezone, isAfter, isBefore, isBetween, isBusinessDay, isLeapYear, isWeekend, parseDate, startOfDay, startOfMonth, startOfYear, timeAgo, timeRemaining, toTimezone };
|
package/dist/date/index.js
CHANGED
|
@@ -253,8 +253,123 @@ function calculateAge(birthDate) {
|
|
|
253
253
|
}
|
|
254
254
|
return age;
|
|
255
255
|
}
|
|
256
|
+
var LOCALE_LABELS = {
|
|
257
|
+
id: {
|
|
258
|
+
years: { single: "tahun", plural: "tahun" },
|
|
259
|
+
months: { single: "bulan", plural: "bulan" },
|
|
260
|
+
weeks: { single: "minggu", plural: "minggu" },
|
|
261
|
+
days: { single: "hari", plural: "hari" },
|
|
262
|
+
hours: { single: "jam", plural: "jam" },
|
|
263
|
+
minutes: { single: "menit", plural: "menit" },
|
|
264
|
+
seconds: { single: "detik", plural: "detik" }
|
|
265
|
+
},
|
|
266
|
+
en: {
|
|
267
|
+
years: { single: "year", plural: "years" },
|
|
268
|
+
months: { single: "month", plural: "months" },
|
|
269
|
+
weeks: { single: "week", plural: "weeks" },
|
|
270
|
+
days: { single: "day", plural: "days" },
|
|
271
|
+
hours: { single: "hour", plural: "hours" },
|
|
272
|
+
minutes: { single: "minute", plural: "minutes" },
|
|
273
|
+
seconds: { single: "second", plural: "seconds" }
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
function getSuffix(diffMs, kind, locale) {
|
|
277
|
+
if (diffMs < 0) {
|
|
278
|
+
return locale === "en" ? "ago" : "yang lalu";
|
|
279
|
+
}
|
|
280
|
+
if (kind === "remaining") {
|
|
281
|
+
return locale === "en" ? "remaining" : "lagi";
|
|
282
|
+
}
|
|
283
|
+
return locale === "en" ? "ago" : "yang lalu";
|
|
284
|
+
}
|
|
285
|
+
function formatRelativeTime(absDiffMs, suffix, locale) {
|
|
286
|
+
const labels = LOCALE_LABELS[locale] ?? LOCALE_LABELS.id;
|
|
287
|
+
const seconds = Math.floor(absDiffMs / 1e3);
|
|
288
|
+
const minutes = Math.floor(seconds / 60);
|
|
289
|
+
const hours = Math.floor(minutes / 60);
|
|
290
|
+
const days = Math.floor(hours / 24);
|
|
291
|
+
const weeks = Math.floor(days / 7);
|
|
292
|
+
const months = Math.floor(days / 30.4375);
|
|
293
|
+
const years = Math.floor(days / 365.25);
|
|
294
|
+
let count;
|
|
295
|
+
let unit;
|
|
296
|
+
if (years >= 1) {
|
|
297
|
+
count = years;
|
|
298
|
+
unit = "years";
|
|
299
|
+
} else if (months >= 1) {
|
|
300
|
+
count = months;
|
|
301
|
+
unit = "months";
|
|
302
|
+
} else if (weeks >= 1) {
|
|
303
|
+
count = weeks;
|
|
304
|
+
unit = "weeks";
|
|
305
|
+
} else if (days >= 1) {
|
|
306
|
+
count = days;
|
|
307
|
+
unit = "days";
|
|
308
|
+
} else if (hours >= 1) {
|
|
309
|
+
count = hours;
|
|
310
|
+
unit = "hours";
|
|
311
|
+
} else if (minutes >= 1) {
|
|
312
|
+
count = minutes;
|
|
313
|
+
unit = "minutes";
|
|
314
|
+
} else {
|
|
315
|
+
count = Math.max(1, seconds);
|
|
316
|
+
unit = "seconds";
|
|
317
|
+
}
|
|
318
|
+
const label = count === 1 ? labels[unit].single : labels[unit].plural;
|
|
319
|
+
return `${count} ${label} ${suffix}`;
|
|
320
|
+
}
|
|
321
|
+
function timeAgo(date, options) {
|
|
322
|
+
const diff = Date.now() - date.getTime();
|
|
323
|
+
const locale = options?.locale ?? "id";
|
|
324
|
+
const suffix = getSuffix(diff, "ago", locale);
|
|
325
|
+
return formatRelativeTime(Math.abs(diff), suffix, locale);
|
|
326
|
+
}
|
|
327
|
+
function timeRemaining(target, options) {
|
|
328
|
+
const diff = target.getTime() - Date.now();
|
|
329
|
+
const locale = options?.locale ?? "id";
|
|
330
|
+
const suffix = getSuffix(diff, "remaining", locale);
|
|
331
|
+
return formatRelativeTime(Math.abs(diff), suffix, locale);
|
|
332
|
+
}
|
|
333
|
+
function formatDuration(duration, options) {
|
|
334
|
+
const locale = options?.locale ?? "id";
|
|
335
|
+
const labels = LOCALE_LABELS[locale] ?? LOCALE_LABELS.id;
|
|
336
|
+
const parts = [];
|
|
337
|
+
const entries = [
|
|
338
|
+
["years", duration.years],
|
|
339
|
+
["months", duration.months],
|
|
340
|
+
["days", duration.days],
|
|
341
|
+
["hours", duration.hours],
|
|
342
|
+
["minutes", duration.minutes],
|
|
343
|
+
["seconds", duration.seconds]
|
|
344
|
+
];
|
|
345
|
+
for (const [key, value] of entries) {
|
|
346
|
+
if (value > 0) {
|
|
347
|
+
const label = value === 1 ? labels[key].single : labels[key].plural;
|
|
348
|
+
parts.push(`${value} ${label}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (parts.length === 0) {
|
|
352
|
+
const label = labels.seconds.plural;
|
|
353
|
+
return `0 ${label}`;
|
|
354
|
+
}
|
|
355
|
+
return parts.join(" ");
|
|
356
|
+
}
|
|
357
|
+
var TIMEZONE_WIB = 7;
|
|
358
|
+
var TIMEZONE_WITA = 8;
|
|
359
|
+
var TIMEZONE_WIT = 9;
|
|
360
|
+
function toTimezone(date, offsetHours) {
|
|
361
|
+
if (!isValidDate(date)) throw new InvalidDateError(date);
|
|
362
|
+
const utcMs = date.getTime() + date.getTimezoneOffset() * 6e4;
|
|
363
|
+
return new Date(utcMs + offsetHours * 36e5);
|
|
364
|
+
}
|
|
365
|
+
function formatInTimezone(date, format, offsetHours) {
|
|
366
|
+
return formatDate(toTimezone(date, offsetHours), format);
|
|
367
|
+
}
|
|
256
368
|
export {
|
|
257
369
|
InvalidDateError,
|
|
370
|
+
TIMEZONE_WIB,
|
|
371
|
+
TIMEZONE_WIT,
|
|
372
|
+
TIMEZONE_WITA,
|
|
258
373
|
addBusinessDays,
|
|
259
374
|
addDays,
|
|
260
375
|
addMonths,
|
|
@@ -265,6 +380,8 @@ export {
|
|
|
265
380
|
endOfMonth,
|
|
266
381
|
endOfYear,
|
|
267
382
|
formatDate,
|
|
383
|
+
formatDuration,
|
|
384
|
+
formatInTimezone,
|
|
268
385
|
isAfter,
|
|
269
386
|
isBefore,
|
|
270
387
|
isBetween,
|
|
@@ -274,6 +391,9 @@ export {
|
|
|
274
391
|
parseDate,
|
|
275
392
|
startOfDay,
|
|
276
393
|
startOfMonth,
|
|
277
|
-
startOfYear
|
|
394
|
+
startOfYear,
|
|
395
|
+
timeAgo,
|
|
396
|
+
timeRemaining,
|
|
397
|
+
toTimezone
|
|
278
398
|
};
|
|
279
399
|
//# sourceMappingURL=index.js.map
|