@entwico/dash 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 entwico GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @entwico/dash
2
+
3
+ Typical reusable utilities shared across projects — the canonical home for the small helpers.
4
+
5
+ - **ESM-only**, `sideEffects: false`, fully tree-shakable
6
+ - **Zero-dependency core** — heavier worlds (React, Tailwind) are isolated behind subpath exports with optional peer dependencies
7
+
8
+ ## Installation
9
+
10
+ ```sh
11
+ pnpm add @entwico/dash
12
+ ```
13
+
14
+ ## Entry points
15
+
16
+ | Entry | Contents | Peer dependencies |
17
+ | --------------------- | ------------------------------------ | ------------------------------ |
18
+ | `@entwico/dash` | zero-dependency utilities and types | — |
19
+ | `@entwico/dash/async` | AsyncIterable primitives and backoff | — |
20
+ | `@entwico/dash/match` | performant string pattern matching | — |
21
+ | `@entwico/dash/cn` | `cn()` class name merging | `clsx` ≥2, `tailwind-merge` ≥2 |
22
+ | `@entwico/dash/react` | React hooks | `react` ≥18 |
23
+
24
+ All peer dependencies are optional — install only what the entry points you use need.
25
+
26
+ ## Core (`@entwico/dash`)
27
+
28
+ | Export | Purpose |
29
+ | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
30
+ | `assert(expr, message?)` | assertion with `asserts expr` narrowing; `message` may be a string or an `Error` |
31
+ | `capitalize(str)` | uppercase the first letter |
32
+ | `createBatcher(onFlush, delayMs)` | batch calls into one flush after a delay |
33
+ | `debounce(fn, wait)` | classic trailing debounce |
34
+ | `deepFreeze(value)` | recursive `Object.freeze`, returns `ReadonlyDeep<T>` |
35
+ | `defined(val)` / `truthy(val)` | type-guard filters for `null` / `undefined` / falsy |
36
+ | `indexBy(array, keyFn, valueFn?)` | index an array into a `Map`; key/value selectors are property names or functions |
37
+ | `mapConcurrent(items, fn, concurrency)` | order-preserving concurrent map with a bounded worker pool |
38
+ | `maybeThen(value, fn)` / `maybeCatch(value, fn)` | chain transformations on `MaybePromise` values without unwrapping |
39
+ | `maybeAll(values)` / `maybeAllSettled(values)` | `Promise.all` / `Promise.allSettled` over `MaybePromise` values, synchronous when all values are |
40
+ | `noop` / `markAsUsed` | no-op functions |
41
+ | `omitUndefined(obj, recursive?)` | strip `undefined` properties (deep by default) |
42
+ | `optionalize(fn, options?)` | lift a function to accept `null` / `undefined`; `defaultValue` / `strategy` (`'nullish'` \| `'falsy'`) options |
43
+ | `retry(fn, options?)` | retry with a fixed delay; `retries` / `delayMs` / `signal` / `onError` options |
44
+ | `sleep(ms, signal?)` | promise-based delay, abortable via `AbortSignal` |
45
+
46
+ Types: `MaybePromise<T>`, `ReadonlyDeep<T>`.
47
+
48
+ ## Async (`@entwico/dash/async`)
49
+
50
+ Zero-dependency streaming and retry primitives — the rxjs patterns without rxjs:
51
+
52
+ | Export | Purpose |
53
+ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
54
+ | `createAsyncIterableSubject()` / `AsyncIterableSubject<T>` | multicast source with `next` / `error` / `complete`, consumable via `for await` or `subscribe(observerOrNext)` |
55
+ | `firstAsync(iter)` | first yielded value, then disposes the iterator; throws if the iterable completes empty (≈ `firstValueFrom`) |
56
+ | `mapAsync(iter, fn)` / `filterAsync(iter, predicate)` | lazy operators over an `AsyncIterable` |
57
+ | `concatAsync(iter)` | concatenate all yielded arrays into one (plain collection: `Array.fromAsync`) |
58
+ | `exponentialBackoff(attempt, options?)` | jittered exponential delay, abortable; bounds configurable via `maxDelayMs` / `jitterMinMs` / `jitterMaxMs` |
59
+ | `retryWithBackoff(fn, options?)` | retry `fn` with exponential backoff indefinitely until it resolves, abortable via `signal` |
60
+ | `keepalive(factory, options?)` | keep a long-running `AsyncIterable` alive: re-subscribe with backoff on error or completion, stop on `signal` abort |
61
+
62
+ ## Match (`@entwico/dash/match`)
63
+
64
+ A pattern union that keeps cheap checks cheap — `startsWith` beats a regex when a prefix is all you need:
65
+
66
+ ```ts
67
+ import { type StringPattern, createMatcher } from "@entwico/dash/match";
68
+
69
+ const isExcluded = createMatcher([{ exact: "/favicon.ico" }, { prefix: "/_astro/" }, { suffix: ".map" }, { includes: "/internal/" }, { pattern: /^\/api\/v\d+\// }, (path) => path.length > 2000]);
70
+
71
+ isExcluded("/_astro/chunk.js"); // true
72
+ ```
73
+
74
+ `createMatcher` buckets patterns by check cost at creation time: all `exact` patterns collapse into a single `Set` lookup, then prefixes / suffixes / substrings, then regexps and functions. For one-shot checks there are `matches(value, pattern)` and `matchesAny(value, patterns)`.
75
+
76
+ ## cn (`@entwico/dash/cn`)
77
+
78
+ ```ts
79
+ import { type Classable, cn } from "@entwico/dash/cn";
80
+
81
+ cn("p-2", condition && "p-4"); // tailwind conflicts resolved in favor of the last class
82
+ ```
83
+
84
+ `Classable` is the `{ className?: string }` contract for component props designed to be merged via `cn()`.
85
+
86
+ ## React (`@entwico/dash/react`)
87
+
88
+ - `useDebouncedValue(value, delay)` — the value, updated only after it has stayed unchanged for the given delay
89
+ - `useEffectAfterMount(effect, deps?)` — run the effect on updates, skip the initial mount
90
+ - `useEffectOnce(effect)` — run the effect exactly once on mount
91
+ - `useIsMobile()` — whether the viewport is below the mobile breakpoint (768px); `false` during SSR
92
+
93
+ ## License
94
+
95
+ MIT
@@ -0,0 +1,87 @@
1
+ //#region src/async/async-iterable-subject.d.ts
2
+ /**
3
+ * Multicast event source with `next` / `error` / `complete` (Subject semantics) plus an
4
+ * `AsyncIterable` view. Two consumption modes that fan out from the same source:
5
+ *
6
+ * - `for await (const v of subject)` — iteration. Each `[Symbol.asyncIterator]()` call returns
7
+ * a fresh per-iterator queue. Stop by `break`ing the loop. On `.error(err)` the iterator's
8
+ * pending `next()` rejects, propagating into the for-await as a thrown exception.
9
+ *
10
+ * - `subject.subscribe(observerOrNext)` — observer style. Pass either a full/partial observer
11
+ * `{ next?, error?, complete? }` or just an `(value) => …` function as the `next`-only shortcut.
12
+ * Returns an unsubscribe function. Missing handlers default to no-ops.
13
+ *
14
+ * Producer side: call `.next(value)` to emit, `.error(err)` to fail the subject (all consumers
15
+ * see the error and are detached), `.complete()` to close cleanly (all consumers see done and
16
+ * are detached). After either `error` or `complete`, further `.next()` calls are dropped and
17
+ * new `.subscribe(...)` calls register a no-op unsubscribe.
18
+ *
19
+ * Backpressure: unbounded queue per pending iterator. Intended for low-frequency events.
20
+ */
21
+ type AsyncIterableSubject<T> = AsyncIterable<T> & {
22
+ next: (value: T) => void;
23
+ error: (err: unknown) => void;
24
+ complete: () => void;
25
+ subscribe: (observerOrNext: ((value: T) => void) | {
26
+ next?: (value: T) => void;
27
+ error?: (err: unknown) => void;
28
+ complete?: () => void;
29
+ }) => () => void;
30
+ };
31
+ declare function createAsyncIterableSubject<T>(): AsyncIterableSubject<T>;
32
+ //#endregion
33
+ //#region src/async/backoff.d.ts
34
+ type BackoffOptions = {
35
+ /** upper bound for the exponential delay, before jitter (default 30_000) */
36
+ maxDelayMs?: number | undefined;
37
+ /** lower bound of the random jitter added to every delay (default 300) */
38
+ jitterMinMs?: number | undefined;
39
+ /** upper bound of the random jitter added to every delay (default 3000) */
40
+ jitterMaxMs?: number | undefined;
41
+ /** aborts the backoff wait (and, where applicable, the retry loop) */
42
+ signal?: AbortSignal | undefined;
43
+ };
44
+ /**
45
+ * Exponential backoff with jitter. `attempt` starts at 0 → ~1s+jitter, 1 → ~2s+jitter, etc.,
46
+ * capped at `maxDelayMs`. Returns a Promise that resolves after the delay.
47
+ */
48
+ declare function exponentialBackoff(attempt: number, options?: BackoffOptions | undefined): Promise<void>;
49
+ /**
50
+ * Runs `fn` with exponential-backoff retry on rejection, indefinitely, resolving with the first
51
+ * successful result. `options.signal` aborts both the in-flight `fn` (if it observes the signal)
52
+ * and the backoff wait.
53
+ */
54
+ declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: BackoffOptions | undefined): Promise<T>;
55
+ //#endregion
56
+ //#region src/async/concat-async.d.ts
57
+ /**
58
+ * Concatenates all elements of all arrays yielded by `iter` into a single array. Used to support
59
+ * subscriptions that emit chunks of items before completing.
60
+ */
61
+ declare function concatAsync<T>(iter: AsyncIterable<T[]>): Promise<T[]>;
62
+ //#endregion
63
+ //#region src/async/filter-async.d.ts
64
+ /** Lazy filter over an async iterable. */
65
+ declare function filterAsync<T>(iter: AsyncIterable<T>, predicate: (value: T) => boolean): AsyncIterable<T>;
66
+ //#endregion
67
+ //#region src/async/first-async.d.ts
68
+ /**
69
+ * Returns the first value yielded by `iter`, then disposes the iterator. Throws if the iterable
70
+ * completes without yielding. Equivalent of rxjs's `firstValueFrom` for AsyncIterables.
71
+ */
72
+ declare function firstAsync<T>(iter: AsyncIterable<T>): Promise<T>;
73
+ //#endregion
74
+ //#region src/async/keepalive.d.ts
75
+ /**
76
+ * Long-running stream wrapper: subscribes via `factory()`, yields every value, and on error or
77
+ * unexpected completion re-subscribes with exponential backoff. Stops only when `options.signal`
78
+ * aborts. Designed for subscriptions that should stay open across reconnects.
79
+ */
80
+ declare function keepalive<T>(factory: () => AsyncIterable<T>, options?: BackoffOptions | undefined): AsyncIterable<T>;
81
+ //#endregion
82
+ //#region src/async/map-async.d.ts
83
+ /** Lazy map over an async iterable. Cleanup propagates: breaking the outer iter disposes the inner. */
84
+ declare function mapAsync<T, R>(iter: AsyncIterable<T>, fn: (value: T) => R): AsyncIterable<R>;
85
+ //#endregion
86
+ export { AsyncIterableSubject, BackoffOptions, concatAsync, createAsyncIterableSubject, exponentialBackoff, filterAsync, firstAsync, keepalive, mapAsync, retryWithBackoff };
87
+ //# sourceMappingURL=async.d.ts.map
package/dist/async.js ADDED
@@ -0,0 +1,225 @@
1
+ import { t as sleep } from "./sleep-J71HYu7n.js";
2
+ //#region src/async/async-iterable-subject.ts
3
+ function createAsyncIterableSubject() {
4
+ const consumers = /* @__PURE__ */ new Set();
5
+ let status = { state: "open" };
6
+ const next = (value) => {
7
+ if (status.state !== "open") return;
8
+ for (const consumer of consumers) if (consumer.kind === "listener") consumer.next?.(value);
9
+ else if (consumer.pending) {
10
+ const p = consumer.pending;
11
+ consumer.pending = void 0;
12
+ p.resolve({
13
+ value,
14
+ done: false
15
+ });
16
+ } else consumer.queue.push(value);
17
+ };
18
+ const error = (err) => {
19
+ if (status.state !== "open") return;
20
+ status = {
21
+ state: "errored",
22
+ err
23
+ };
24
+ for (const consumer of consumers) if (consumer.kind === "listener") consumer.error?.(err);
25
+ else if (consumer.pending) {
26
+ const p = consumer.pending;
27
+ consumer.pending = void 0;
28
+ p.reject(err);
29
+ }
30
+ consumers.clear();
31
+ };
32
+ const complete = () => {
33
+ if (status.state !== "open") return;
34
+ status = { state: "completed" };
35
+ for (const consumer of consumers) if (consumer.kind === "listener") consumer.complete?.();
36
+ else if (consumer.pending) {
37
+ const p = consumer.pending;
38
+ consumer.pending = void 0;
39
+ p.resolve({
40
+ value: void 0,
41
+ done: true
42
+ });
43
+ }
44
+ consumers.clear();
45
+ };
46
+ const subscribe = (observerOrNext) => {
47
+ if (status.state !== "open") return () => {};
48
+ const consumer = {
49
+ kind: "listener",
50
+ ...typeof observerOrNext === "function" ? { next: observerOrNext } : observerOrNext
51
+ };
52
+ consumers.add(consumer);
53
+ return () => {
54
+ consumers.delete(consumer);
55
+ };
56
+ };
57
+ return Object.assign({ [Symbol.asyncIterator]() {
58
+ if (status.state === "errored") {
59
+ const err = status.err;
60
+ return {
61
+ next: () => Promise.reject(err),
62
+ return: () => Promise.resolve({
63
+ value: void 0,
64
+ done: true
65
+ })
66
+ };
67
+ }
68
+ if (status.state === "completed") return {
69
+ next: () => Promise.resolve({
70
+ value: void 0,
71
+ done: true
72
+ }),
73
+ return: () => Promise.resolve({
74
+ value: void 0,
75
+ done: true
76
+ })
77
+ };
78
+ const consumer = {
79
+ kind: "iterator",
80
+ queue: []
81
+ };
82
+ consumers.add(consumer);
83
+ return {
84
+ next() {
85
+ if (consumer.queue.length > 0) return Promise.resolve({
86
+ value: consumer.queue.shift(),
87
+ done: false
88
+ });
89
+ if (status.state === "errored") return Promise.reject(status.err);
90
+ if (status.state === "completed") return Promise.resolve({
91
+ value: void 0,
92
+ done: true
93
+ });
94
+ return new Promise((resolve, reject) => {
95
+ consumer.pending = {
96
+ resolve,
97
+ reject
98
+ };
99
+ });
100
+ },
101
+ return() {
102
+ consumers.delete(consumer);
103
+ if (consumer.pending) {
104
+ const p = consumer.pending;
105
+ consumer.pending = void 0;
106
+ p.resolve({
107
+ value: void 0,
108
+ done: true
109
+ });
110
+ }
111
+ return Promise.resolve({
112
+ value: void 0,
113
+ done: true
114
+ });
115
+ },
116
+ throw(err) {
117
+ consumers.delete(consumer);
118
+ return Promise.reject(err);
119
+ }
120
+ };
121
+ } }, {
122
+ next,
123
+ error,
124
+ complete,
125
+ subscribe
126
+ });
127
+ }
128
+ //#endregion
129
+ //#region src/async/backoff.ts
130
+ const DEFAULT_MAX_DELAY_MS = 3e4;
131
+ const DEFAULT_JITTER_MIN_MS = 300;
132
+ const DEFAULT_JITTER_MAX_MS = 3e3;
133
+ /**
134
+ * Exponential backoff with jitter. `attempt` starts at 0 → ~1s+jitter, 1 → ~2s+jitter, etc.,
135
+ * capped at `maxDelayMs`. Returns a Promise that resolves after the delay.
136
+ */
137
+ function exponentialBackoff(attempt, options) {
138
+ const maxDelayMs = options?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
139
+ const jitterMinMs = options?.jitterMinMs ?? DEFAULT_JITTER_MIN_MS;
140
+ const jitterMaxMs = options?.jitterMaxMs ?? DEFAULT_JITTER_MAX_MS;
141
+ return sleep(Math.min(Math.pow(2, attempt) * 1e3, maxDelayMs) + Math.floor(Math.random() * (jitterMaxMs - jitterMinMs) + jitterMinMs), options?.signal);
142
+ }
143
+ /**
144
+ * Runs `fn` with exponential-backoff retry on rejection, indefinitely, resolving with the first
145
+ * successful result. `options.signal` aborts both the in-flight `fn` (if it observes the signal)
146
+ * and the backoff wait.
147
+ */
148
+ async function retryWithBackoff(fn, options) {
149
+ let attempt = 0;
150
+ for (;;) {
151
+ if (options?.signal?.aborted) throw options.signal.reason;
152
+ try {
153
+ return await fn();
154
+ } catch {
155
+ await exponentialBackoff(attempt, options);
156
+ attempt++;
157
+ }
158
+ }
159
+ }
160
+ //#endregion
161
+ //#region src/async/concat-async.ts
162
+ /**
163
+ * Concatenates all elements of all arrays yielded by `iter` into a single array. Used to support
164
+ * subscriptions that emit chunks of items before completing.
165
+ */
166
+ async function concatAsync(iter) {
167
+ const out = [];
168
+ for await (const chunk of iter) out.push(...chunk);
169
+ return out;
170
+ }
171
+ //#endregion
172
+ //#region src/async/filter-async.ts
173
+ /** Lazy filter over an async iterable. */
174
+ async function* filterAsync(iter, predicate) {
175
+ for await (const value of iter) if (predicate(value)) yield value;
176
+ }
177
+ //#endregion
178
+ //#region src/async/first-async.ts
179
+ /**
180
+ * Returns the first value yielded by `iter`, then disposes the iterator. Throws if the iterable
181
+ * completes without yielding. Equivalent of rxjs's `firstValueFrom` for AsyncIterables.
182
+ */
183
+ async function firstAsync(iter) {
184
+ const it = iter[Symbol.asyncIterator]();
185
+ try {
186
+ const result = await it.next();
187
+ if (result.done) throw new Error("firstAsync: async iterable completed without yielding a value");
188
+ return result.value;
189
+ } finally {
190
+ await it.return?.();
191
+ }
192
+ }
193
+ //#endregion
194
+ //#region src/async/keepalive.ts
195
+ /**
196
+ * Long-running stream wrapper: subscribes via `factory()`, yields every value, and on error or
197
+ * unexpected completion re-subscribes with exponential backoff. Stops only when `options.signal`
198
+ * aborts. Designed for subscriptions that should stay open across reconnects.
199
+ */
200
+ async function* keepalive(factory, options) {
201
+ let attempt = 0;
202
+ const signal = options?.signal;
203
+ while (!signal?.aborted) try {
204
+ for await (const value of factory()) {
205
+ if (signal?.aborted) return;
206
+ yield value;
207
+ attempt = 0;
208
+ }
209
+ attempt = 0;
210
+ } catch (error) {
211
+ if (signal?.aborted) return;
212
+ await exponentialBackoff(attempt, options);
213
+ attempt++;
214
+ }
215
+ }
216
+ //#endregion
217
+ //#region src/async/map-async.ts
218
+ /** Lazy map over an async iterable. Cleanup propagates: breaking the outer iter disposes the inner. */
219
+ async function* mapAsync(iter, fn) {
220
+ for await (const value of iter) yield fn(value);
221
+ }
222
+ //#endregion
223
+ export { concatAsync, createAsyncIterableSubject, exponentialBackoff, filterAsync, firstAsync, keepalive, mapAsync, retryWithBackoff };
224
+
225
+ //# sourceMappingURL=async.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async.js","names":[],"sources":["../src/async/async-iterable-subject.ts","../src/async/backoff.ts","../src/async/concat-async.ts","../src/async/filter-async.ts","../src/async/first-async.ts","../src/async/keepalive.ts","../src/async/map-async.ts"],"sourcesContent":["/**\n * Multicast event source with `next` / `error` / `complete` (Subject semantics) plus an\n * `AsyncIterable` view. Two consumption modes that fan out from the same source:\n *\n * - `for await (const v of subject)` — iteration. Each `[Symbol.asyncIterator]()` call returns\n * a fresh per-iterator queue. Stop by `break`ing the loop. On `.error(err)` the iterator's\n * pending `next()` rejects, propagating into the for-await as a thrown exception.\n *\n * - `subject.subscribe(observerOrNext)` — observer style. Pass either a full/partial observer\n * `{ next?, error?, complete? }` or just an `(value) => …` function as the `next`-only shortcut.\n * Returns an unsubscribe function. Missing handlers default to no-ops.\n *\n * Producer side: call `.next(value)` to emit, `.error(err)` to fail the subject (all consumers\n * see the error and are detached), `.complete()` to close cleanly (all consumers see done and\n * are detached). After either `error` or `complete`, further `.next()` calls are dropped and\n * new `.subscribe(...)` calls register a no-op unsubscribe.\n *\n * Backpressure: unbounded queue per pending iterator. Intended for low-frequency events.\n */\nexport type AsyncIterableSubject<T> = AsyncIterable<T> & {\n next: (value: T) => void;\n error: (err: unknown) => void;\n complete: () => void;\n subscribe: (\n observerOrNext:\n | ((value: T) => void)\n | {\n next?: (value: T) => void;\n error?: (err: unknown) => void;\n complete?: () => void;\n },\n ) => () => void;\n};\n\ntype IteratorConsumer<T> = {\n kind: 'iterator';\n queue: T[];\n pending?: { resolve: (r: IteratorResult<T>) => void; reject: (e: unknown) => void } | undefined;\n};\n\ntype ListenerConsumer<T> = {\n kind: 'listener';\n next?: (value: T) => void;\n error?: (err: unknown) => void;\n complete?: () => void;\n};\n\ntype Consumer<T> = IteratorConsumer<T> | ListenerConsumer<T>;\n\ntype Status = { state: 'open' } | { state: 'errored'; err: unknown } | { state: 'completed' };\n\nexport function createAsyncIterableSubject<T>(): AsyncIterableSubject<T> {\n const consumers = new Set<Consumer<T>>();\n let status: Status = { state: 'open' };\n\n const next = (value: T): void => {\n if (status.state !== 'open') {\n return;\n }\n\n for (const consumer of consumers) {\n if (consumer.kind === 'listener') {\n consumer.next?.(value);\n } else if (consumer.pending) {\n const p = consumer.pending;\n\n consumer.pending = undefined;\n p.resolve({ value, done: false });\n } else {\n consumer.queue.push(value);\n }\n }\n };\n\n const error = (err: unknown): void => {\n if (status.state !== 'open') {\n return;\n }\n\n status = { state: 'errored', err };\n\n for (const consumer of consumers) {\n if (consumer.kind === 'listener') {\n consumer.error?.(err);\n } else if (consumer.pending) {\n const p = consumer.pending;\n\n consumer.pending = undefined;\n p.reject(err);\n }\n }\n\n consumers.clear();\n };\n\n const complete = (): void => {\n if (status.state !== 'open') {\n return;\n }\n\n status = { state: 'completed' };\n\n for (const consumer of consumers) {\n if (consumer.kind === 'listener') {\n consumer.complete?.();\n } else if (consumer.pending) {\n const p = consumer.pending;\n\n consumer.pending = undefined;\n p.resolve({ value: undefined as unknown as T, done: true });\n }\n }\n\n consumers.clear();\n };\n\n const subscribe = (\n observerOrNext:\n | ((value: T) => void)\n | {\n next?: (value: T) => void;\n error?: (err: unknown) => void;\n complete?: () => void;\n },\n ): (() => void) => {\n if (status.state !== 'open') {\n return () => {};\n }\n\n const observer = typeof observerOrNext === 'function' ? { next: observerOrNext } : observerOrNext;\n const consumer: ListenerConsumer<T> = { kind: 'listener', ...observer };\n\n consumers.add(consumer);\n\n return () => {\n consumers.delete(consumer);\n };\n };\n\n const iterable: AsyncIterable<T> = {\n [Symbol.asyncIterator](): AsyncIterator<T> {\n if (status.state === 'errored') {\n const err = status.err;\n\n return {\n next: () => Promise.reject(err),\n return: () => Promise.resolve({ value: undefined as unknown as T, done: true }),\n };\n }\n\n if (status.state === 'completed') {\n return {\n next: () => Promise.resolve({ value: undefined as unknown as T, done: true }),\n return: () => Promise.resolve({ value: undefined as unknown as T, done: true }),\n };\n }\n\n const consumer: IteratorConsumer<T> = { kind: 'iterator', queue: [] };\n\n consumers.add(consumer);\n\n return {\n next(): Promise<IteratorResult<T>> {\n if (consumer.queue.length > 0) {\n return Promise.resolve({ value: consumer.queue.shift()!, done: false });\n }\n\n if (status.state === 'errored') {\n return Promise.reject(status.err);\n }\n\n if (status.state === 'completed') {\n return Promise.resolve({ value: undefined as unknown as T, done: true });\n }\n\n return new Promise<IteratorResult<T>>((resolve, reject) => {\n consumer.pending = { resolve, reject };\n });\n },\n return(): Promise<IteratorResult<T>> {\n consumers.delete(consumer);\n\n if (consumer.pending) {\n const p = consumer.pending;\n\n consumer.pending = undefined;\n p.resolve({ value: undefined as unknown as T, done: true });\n }\n\n return Promise.resolve({ value: undefined as unknown as T, done: true });\n },\n throw(err): Promise<IteratorResult<T>> {\n consumers.delete(consumer);\n\n return Promise.reject(err);\n },\n };\n },\n };\n\n return Object.assign(iterable, { next, error, complete, subscribe });\n}\n","import { sleep } from '../core/sleep';\n\nconst DEFAULT_MAX_DELAY_MS = 30_000;\nconst DEFAULT_JITTER_MIN_MS = 300;\nconst DEFAULT_JITTER_MAX_MS = 3000;\n\nexport type BackoffOptions = {\n /** upper bound for the exponential delay, before jitter (default 30_000) */\n maxDelayMs?: number | undefined;\n\n /** lower bound of the random jitter added to every delay (default 300) */\n jitterMinMs?: number | undefined;\n\n /** upper bound of the random jitter added to every delay (default 3000) */\n jitterMaxMs?: number | undefined;\n\n /** aborts the backoff wait (and, where applicable, the retry loop) */\n signal?: AbortSignal | undefined;\n};\n\n/**\n * Exponential backoff with jitter. `attempt` starts at 0 → ~1s+jitter, 1 → ~2s+jitter, etc.,\n * capped at `maxDelayMs`. Returns a Promise that resolves after the delay.\n */\nexport function exponentialBackoff(attempt: number, options?: BackoffOptions | undefined): Promise<void> {\n const maxDelayMs = options?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const jitterMinMs = options?.jitterMinMs ?? DEFAULT_JITTER_MIN_MS;\n const jitterMaxMs = options?.jitterMaxMs ?? DEFAULT_JITTER_MAX_MS;\n\n const base = Math.min(Math.pow(2, attempt) * 1000, maxDelayMs);\n const jitter = Math.floor(Math.random() * (jitterMaxMs - jitterMinMs) + jitterMinMs);\n\n return sleep(base + jitter, options?.signal);\n}\n\n/**\n * Runs `fn` with exponential-backoff retry on rejection, indefinitely, resolving with the first\n * successful result. `options.signal` aborts both the in-flight `fn` (if it observes the signal)\n * and the backoff wait.\n */\nexport async function retryWithBackoff<T>(fn: () => Promise<T>, options?: BackoffOptions | undefined): Promise<T> {\n let attempt = 0;\n\n for (;;) {\n if (options?.signal?.aborted) {\n throw options.signal.reason;\n }\n\n try {\n return await fn();\n } catch {\n await exponentialBackoff(attempt, options);\n attempt++;\n }\n }\n}\n","/**\n * Concatenates all elements of all arrays yielded by `iter` into a single array. Used to support\n * subscriptions that emit chunks of items before completing.\n */\nexport async function concatAsync<T>(iter: AsyncIterable<T[]>): Promise<T[]> {\n const out: T[] = [];\n\n for await (const chunk of iter) {\n out.push(...chunk);\n }\n\n return out;\n}\n","/** Lazy filter over an async iterable. */\nexport async function* filterAsync<T>(iter: AsyncIterable<T>, predicate: (value: T) => boolean): AsyncIterable<T> {\n for await (const value of iter) {\n if (predicate(value)) {\n yield value;\n }\n }\n}\n","/**\n * Returns the first value yielded by `iter`, then disposes the iterator. Throws if the iterable\n * completes without yielding. Equivalent of rxjs's `firstValueFrom` for AsyncIterables.\n */\nexport async function firstAsync<T>(iter: AsyncIterable<T>): Promise<T> {\n const it = iter[Symbol.asyncIterator]();\n\n try {\n const result = await it.next();\n\n if (result.done) {\n throw new Error('firstAsync: async iterable completed without yielding a value');\n }\n\n return result.value;\n } finally {\n await it.return?.();\n }\n}\n","import { type BackoffOptions, exponentialBackoff } from './backoff';\n\n/**\n * Long-running stream wrapper: subscribes via `factory()`, yields every value, and on error or\n * unexpected completion re-subscribes with exponential backoff. Stops only when `options.signal`\n * aborts. Designed for subscriptions that should stay open across reconnects.\n */\nexport async function* keepalive<T>(\n factory: () => AsyncIterable<T>,\n options?: BackoffOptions | undefined,\n): AsyncIterable<T> {\n let attempt = 0;\n const signal = options?.signal;\n\n while (!signal?.aborted) {\n try {\n for await (const value of factory()) {\n if (signal?.aborted) {\n return;\n }\n\n yield value;\n attempt = 0;\n }\n\n // source completed cleanly — re-subscribe immediately\n attempt = 0;\n } catch (error) {\n if (signal?.aborted) {\n return;\n }\n\n // swallow + back off, then retry. callers that want the error to surface should not use keepalive\n void error;\n await exponentialBackoff(attempt, options);\n attempt++;\n }\n }\n}\n","/** Lazy map over an async iterable. Cleanup propagates: breaking the outer iter disposes the inner. */\nexport async function* mapAsync<T, R>(iter: AsyncIterable<T>, fn: (value: T) => R): AsyncIterable<R> {\n for await (const value of iter) {\n yield fn(value);\n }\n}\n"],"mappings":"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"}
package/dist/cn.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { ClassValue } from "clsx";
2
+ //#region src/cn/classable.d.ts
3
+ type Classable = {
4
+ readonly className?: string | undefined;
5
+ };
6
+ //#endregion
7
+ //#region src/cn/cn.d.ts
8
+ declare function cn(...inputs: ClassValue[]): string;
9
+ //#endregion
10
+ export { Classable, cn };
11
+ //# sourceMappingURL=cn.d.ts.map
package/dist/cn.js ADDED
@@ -0,0 +1,10 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ //#region src/cn/cn.ts
4
+ function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+ //#endregion
8
+ export { cn };
9
+
10
+ //# sourceMappingURL=cn.js.map
package/dist/cn.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cn.js","names":[],"sources":["../src/cn/cn.ts"],"sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";;;AAGA,SAAgB,GAAG,GAAG,QAA8B;CAClD,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B"}
@@ -0,0 +1,156 @@
1
+ //#region src/core/assert.d.ts
2
+ declare function assert<T>(expr: T, message?: string | Error): asserts expr;
3
+ //#endregion
4
+ //#region src/core/capitalize.d.ts
5
+ declare function capitalize(str: string): string;
6
+ //#endregion
7
+ //#region src/core/create-batcher.d.ts
8
+ /**
9
+ * Buffers function calls and executes them together after a specified delay.
10
+ * Useful for batching multiple events into a single operation.
11
+ *
12
+ * @param onFlush - Function to execute with all buffered items
13
+ * @param delayMs - Delay in milliseconds before flushing the buffer
14
+ * @returns Function that adds items to the buffer
15
+ */
16
+ declare function createBatcher<T>(onFlush: (items: T[]) => void, delayMs: number): (item: T) => void;
17
+ //#endregion
18
+ //#region src/core/debounce.d.ts
19
+ declare const debounce: <A extends unknown[]>(func: (...args: A) => unknown, wait: number) => (...args: A) => void;
20
+ //#endregion
21
+ //#region src/core/readonly-deep.d.ts
22
+ /**
23
+ * Recursively makes a type and all its nested properties readonly.
24
+ *
25
+ * Adapted from type-fest's `ReadonlyDeep` taken under CC0:
26
+ * https://github.com/sindresorhus/type-fest/blob/main/source/readonly-deep.d.ts
27
+ */
28
+ type Primitive = null | undefined | string | number | boolean | symbol | bigint;
29
+ type BuiltIns = Primitive | void | Date | RegExp;
30
+ type HasMultipleCallSignatures<T extends (...arguments_: any[]) => unknown> = T extends {
31
+ (...arguments_: infer A): unknown;
32
+ (...arguments_: infer B): unknown;
33
+ } ? B extends A ? A extends B ? false : true : true : false;
34
+ type EmptyObject = {};
35
+ type ReadonlyDeep<T> = T extends BuiltIns ? T : T extends (new (...arguments_: any[]) => unknown) ? T : T extends ((...arguments_: any[]) => unknown) ? EmptyObject extends ReadonlyObjectDeep<T> ? T : HasMultipleCallSignatures<T> extends true ? T : ((...arguments_: Parameters<T>) => ReturnType<T>) & ReadonlyObjectDeep<T> : T extends Readonly<ReadonlyMap<infer KeyType, infer ValueType>> ? ReadonlyMapDeep<KeyType, ValueType> : T extends Readonly<ReadonlySet<infer ItemType>> ? ReadonlySetDeep<ItemType> : T extends readonly [] | readonly [...never[]] ? readonly [] : T extends readonly [infer U, ...infer V] ? readonly [ReadonlyDeep<U>, ...ReadonlyDeep<V>] : T extends readonly [...infer U, infer V] ? readonly [...ReadonlyDeep<U>, ReadonlyDeep<V>] : T extends ReadonlyArray<infer ItemType> ? ReadonlyArray<ReadonlyDeep<ItemType>> : T extends object ? ReadonlyObjectDeep<T> : unknown;
36
+ type ReadonlyMapDeep<KeyType, ValueType> = EmptyObject & Readonly<ReadonlyMap<ReadonlyDeep<KeyType>, ReadonlyDeep<ValueType>>>;
37
+ type ReadonlySetDeep<ItemType> = EmptyObject & Readonly<ReadonlySet<ReadonlyDeep<ItemType>>>;
38
+ type ReadonlyObjectDeep<ObjectType extends object> = { readonly [KeyType in keyof ObjectType]: ReadonlyDeep<ObjectType[KeyType]>; };
39
+ //#endregion
40
+ //#region src/core/deep-freeze.d.ts
41
+ /**
42
+ * Runtime companion of {@link ReadonlyDeep}: recursively Object.freeze()s a value and returns it
43
+ * typed as deeply readonly. Safe on cyclic structures; already-frozen values are assumed to be
44
+ * deep-frozen and are not descended into.
45
+ *
46
+ * Runtime limits (the type still forbids mutation in all these cases): Map / Set contents are
47
+ * frozen, but the containers themselves stay mutable; Date internals cannot be frozen; functions
48
+ * are left untouched.
49
+ */
50
+ declare function deepFreeze<T>(value: T): ReadonlyDeep<T>;
51
+ //#endregion
52
+ //#region src/core/defined.d.ts
53
+ declare function defined<T>(val: T | undefined | null | void): val is T;
54
+ declare function truthy<T>(val: T | undefined | null | void): val is T;
55
+ //#endregion
56
+ //#region src/core/index-by.d.ts
57
+ declare function indexBy<T>(array: readonly T[], keyFn: keyof T | ((value: T) => string)): Map<string, T>;
58
+ declare function indexBy<T, K extends keyof T>(array: readonly T[], keyFn: keyof T | ((value: T) => string), valueFn: K): Map<string, T[K]>;
59
+ declare function indexBy<T, V>(array: readonly T[], keyFn: keyof T | ((value: T) => string), valueFn: (value: T) => V): Map<string, V>;
60
+ //#endregion
61
+ //#region src/core/map-concurrent.d.ts
62
+ /**
63
+ * Order-preserving concurrent map: runs `fn` over the items with a sliding pool of at most
64
+ * `concurrency` workers, so a slow item never stalls the others. Errors propagate via Promise.all.
65
+ */
66
+ declare function mapConcurrent<T, R>(items: readonly T[], fn: (item: T, index: number) => Promise<R>, concurrency: number): Promise<R[]>;
67
+ //#endregion
68
+ //#region src/core/maybe-promise.d.ts
69
+ type MaybePromise<T> = T | Promise<T>;
70
+ type MaybeAwaitedAll<T extends readonly MaybePromise<unknown>[]> = { -readonly [K in keyof T]: Awaited<T[K]>; };
71
+ type MaybeSettledAll<T extends readonly MaybePromise<unknown>[]> = { -readonly [K in keyof T]: PromiseSettledResult<Awaited<T[K]>>; };
72
+ /**
73
+ * Chains a transformation on a MaybePromise value.
74
+ * If the value is a Promise, uses .then(). Otherwise, calls fn directly.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * const result = maybeThen(map.get('key'), (value) => value.name);
79
+ * // result is MaybePromise<string>
80
+ * ```
81
+ */
82
+ declare function maybeThen<T, R>(value: MaybePromise<T>, fn: (v: T) => MaybePromise<R>): MaybePromise<R>;
83
+ /**
84
+ * Adds error handling to a MaybePromise value.
85
+ * If the value is a Promise, uses .catch(). Otherwise, returns value as-is.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * const result = maybeCatch(map.get('key'), (err) => defaultValue);
90
+ * // result is MaybePromise<T>
91
+ * ```
92
+ */
93
+ declare function maybeCatch<T>(value: MaybePromise<T>, fn: (e: unknown) => MaybePromise<T>): MaybePromise<T>;
94
+ /**
95
+ * Combines MaybePromise values.
96
+ * If any value is a Promise, behaves like Promise.all(). Otherwise, returns the values synchronously.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const [user, config] = await maybeAll([cache.get('user'), cache.get('config')]);
101
+ * // no microtask hop when both were cached synchronously
102
+ * ```
103
+ */
104
+ declare function maybeAll<T extends readonly MaybePromise<unknown>[]>(values: T): MaybePromise<MaybeAwaitedAll<T>>;
105
+ /**
106
+ * Settles MaybePromise values.
107
+ * If any value is a Promise, behaves like Promise.allSettled(). Otherwise, returns the values
108
+ * synchronously, each wrapped as a fulfilled result.
109
+ */
110
+ declare function maybeAllSettled<T extends readonly MaybePromise<unknown>[]>(values: T): MaybePromise<MaybeSettledAll<T>>;
111
+ //#endregion
112
+ //#region src/core/noop.d.ts
113
+ declare const noop: (..._: any[]) => void;
114
+ declare const markAsUsed: (..._: any[]) => void;
115
+ //#endregion
116
+ //#region src/core/omit-undefined.d.ts
117
+ type DeepNonUndefined<T> = T extends Record<string, unknown> ? { [P in keyof T as undefined extends T[P] ? never : P]: T[P] extends (infer U)[] ? DeepNonUndefined<U>[] : T[P] extends Record<string, unknown> ? DeepNonUndefined<T[P]> : Exclude<T[P], undefined>; } : Exclude<T, undefined>;
118
+ type ShallowNonUndefined<T> = { [P in keyof T as undefined extends T[P] ? never : P]: Exclude<T[P], undefined>; };
119
+ /**
120
+ * Removes undefined properties from an object.
121
+ * Optionally processes nested objects and arrays recursively.
122
+ *
123
+ * @param obj object to clean
124
+ * @param recursive whether to recursively process nested objects and arrays (default: true)
125
+ */
126
+ declare function omitUndefined<T extends Record<string, unknown>>(obj: T, recursive?: true): DeepNonUndefined<T>;
127
+ declare function omitUndefined<T extends Record<string, unknown>>(obj: T, recursive: false): ShallowNonUndefined<T>;
128
+ //#endregion
129
+ //#region src/core/optionalize.d.ts
130
+ declare function optionalize<T, U, D = undefined>(fn: (v: T) => U, options?: {
131
+ defaultValue?: D | undefined;
132
+ strategy?: 'falsy' | 'nullish' | undefined;
133
+ } | undefined): (v: T | null | undefined) => U | D;
134
+ //#endregion
135
+ //#region src/core/retry.d.ts
136
+ type RetryOptions = {
137
+ /** total number of attempts before the last error is rethrown (default 3) */
138
+ retries?: number | undefined;
139
+ /** fixed delay between attempts (default 1000) */
140
+ delayMs?: number | undefined;
141
+ /** aborts the delay between attempts */
142
+ signal?: AbortSignal | undefined;
143
+ /** called after every failed attempt with the error and the 1-based attempt number */
144
+ onError?: ((error: unknown, attempt: number) => void) | undefined;
145
+ };
146
+ /**
147
+ * Runs `fn`, retrying with a fixed delay on failure. Resolves with the first successful result;
148
+ * rethrows the last error once the attempts are exhausted.
149
+ */
150
+ declare function retry<T>(fn: () => T, options?: RetryOptions | undefined): Promise<T>;
151
+ //#endregion
152
+ //#region src/core/sleep.d.ts
153
+ declare function sleep(ms: number, signal?: AbortSignal | undefined): Promise<void>;
154
+ //#endregion
155
+ export { MaybePromise, ReadonlyDeep, RetryOptions, assert, capitalize, createBatcher, debounce, deepFreeze, defined, indexBy, mapConcurrent, markAsUsed, maybeAll, maybeAllSettled, maybeCatch, maybeThen, noop, omitUndefined, optionalize, retry, sleep, truthy };
156
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,225 @@
1
+ import { t as sleep } from "./sleep-J71HYu7n.js";
2
+ //#region src/core/assert.ts
3
+ function assert(expr, message) {
4
+ message ||= "unknown assertion error";
5
+ message = typeof message === "string" ? new Error(message) : message;
6
+ if (!expr) throw message;
7
+ }
8
+ //#endregion
9
+ //#region src/core/capitalize.ts
10
+ function capitalize(str) {
11
+ if (!str) return str;
12
+ return str.charAt(0).toUpperCase() + str.slice(1);
13
+ }
14
+ //#endregion
15
+ //#region src/core/create-batcher.ts
16
+ /**
17
+ * Buffers function calls and executes them together after a specified delay.
18
+ * Useful for batching multiple events into a single operation.
19
+ *
20
+ * @param onFlush - Function to execute with all buffered items
21
+ * @param delayMs - Delay in milliseconds before flushing the buffer
22
+ * @returns Function that adds items to the buffer
23
+ */
24
+ function createBatcher(onFlush, delayMs) {
25
+ let buffer = [];
26
+ let timeoutId = null;
27
+ return (item) => {
28
+ buffer.push(item);
29
+ if (timeoutId) clearTimeout(timeoutId);
30
+ timeoutId = setTimeout(() => {
31
+ onFlush(buffer);
32
+ buffer = [];
33
+ timeoutId = null;
34
+ }, delayMs);
35
+ };
36
+ }
37
+ //#endregion
38
+ //#region src/core/debounce.ts
39
+ const debounce = (func, wait) => {
40
+ let timeout;
41
+ return function executedFunction(...args) {
42
+ const later = () => {
43
+ timeout = void 0;
44
+ func(...args);
45
+ };
46
+ clearTimeout(timeout);
47
+ timeout = setTimeout(later, wait);
48
+ };
49
+ };
50
+ //#endregion
51
+ //#region src/core/deep-freeze.ts
52
+ /**
53
+ * Runtime companion of {@link ReadonlyDeep}: recursively Object.freeze()s a value and returns it
54
+ * typed as deeply readonly. Safe on cyclic structures; already-frozen values are assumed to be
55
+ * deep-frozen and are not descended into.
56
+ *
57
+ * Runtime limits (the type still forbids mutation in all these cases): Map / Set contents are
58
+ * frozen, but the containers themselves stay mutable; Date internals cannot be frozen; functions
59
+ * are left untouched.
60
+ */
61
+ function deepFreeze(value) {
62
+ freeze(value);
63
+ return value;
64
+ }
65
+ function freeze(value) {
66
+ if (value === null || typeof value !== "object" || Object.isFrozen(value)) return;
67
+ if (ArrayBuffer.isView(value)) return;
68
+ Object.freeze(value);
69
+ if (value instanceof Map) {
70
+ for (const [key, item] of value) {
71
+ freeze(key);
72
+ freeze(item);
73
+ }
74
+ return;
75
+ }
76
+ if (value instanceof Set) {
77
+ for (const item of value) freeze(item);
78
+ return;
79
+ }
80
+ for (const key of Reflect.ownKeys(value)) freeze(value[key]);
81
+ }
82
+ //#endregion
83
+ //#region src/core/defined.ts
84
+ function defined(val) {
85
+ return val !== void 0 && val !== null;
86
+ }
87
+ function truthy(val) {
88
+ return !!val;
89
+ }
90
+ //#endregion
91
+ //#region src/core/index-by.ts
92
+ function indexBy(array, keyFn, valueFn) {
93
+ return array.reduce((map, item) => {
94
+ const key = typeof keyFn === "function" ? keyFn(item) : String(item[keyFn]);
95
+ if (valueFn) {
96
+ const value = typeof valueFn === "function" ? valueFn(item) : item[valueFn];
97
+ map.set(key, value);
98
+ } else map.set(key, item);
99
+ return map;
100
+ }, /* @__PURE__ */ new Map());
101
+ }
102
+ //#endregion
103
+ //#region src/core/map-concurrent.ts
104
+ /**
105
+ * Order-preserving concurrent map: runs `fn` over the items with a sliding pool of at most
106
+ * `concurrency` workers, so a slow item never stalls the others. Errors propagate via Promise.all.
107
+ */
108
+ async function mapConcurrent(items, fn, concurrency) {
109
+ const results = Array.from({ length: items.length });
110
+ let nextIndex = 0;
111
+ async function worker() {
112
+ while (true) {
113
+ const i = nextIndex++;
114
+ if (i >= items.length) return;
115
+ results[i] = await fn(items[i], i);
116
+ }
117
+ }
118
+ const workerCount = Math.max(1, Math.min(concurrency, items.length));
119
+ const workers = Array.from({ length: workerCount }, () => worker());
120
+ await Promise.all(workers);
121
+ return results;
122
+ }
123
+ //#endregion
124
+ //#region src/core/maybe-promise.ts
125
+ /**
126
+ * Chains a transformation on a MaybePromise value.
127
+ * If the value is a Promise, uses .then(). Otherwise, calls fn directly.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * const result = maybeThen(map.get('key'), (value) => value.name);
132
+ * // result is MaybePromise<string>
133
+ * ```
134
+ */
135
+ function maybeThen(value, fn) {
136
+ if (value instanceof Promise) return value.then(fn);
137
+ return fn(value);
138
+ }
139
+ /**
140
+ * Adds error handling to a MaybePromise value.
141
+ * If the value is a Promise, uses .catch(). Otherwise, returns value as-is.
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * const result = maybeCatch(map.get('key'), (err) => defaultValue);
146
+ * // result is MaybePromise<T>
147
+ * ```
148
+ */
149
+ function maybeCatch(value, fn) {
150
+ if (value instanceof Promise) return value.catch(fn);
151
+ return value;
152
+ }
153
+ /**
154
+ * Combines MaybePromise values.
155
+ * If any value is a Promise, behaves like Promise.all(). Otherwise, returns the values synchronously.
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * const [user, config] = await maybeAll([cache.get('user'), cache.get('config')]);
160
+ * // no microtask hop when both were cached synchronously
161
+ * ```
162
+ */
163
+ function maybeAll(values) {
164
+ if (values.some((value) => value instanceof Promise)) return Promise.all(values);
165
+ return [...values];
166
+ }
167
+ /**
168
+ * Settles MaybePromise values.
169
+ * If any value is a Promise, behaves like Promise.allSettled(). Otherwise, returns the values
170
+ * synchronously, each wrapped as a fulfilled result.
171
+ */
172
+ function maybeAllSettled(values) {
173
+ if (values.some((value) => value instanceof Promise)) return Promise.allSettled(values);
174
+ return values.map((value) => ({
175
+ status: "fulfilled",
176
+ value
177
+ }));
178
+ }
179
+ //#endregion
180
+ //#region src/core/noop.ts
181
+ const noop = (..._) => {};
182
+ const markAsUsed = (..._) => {};
183
+ //#endregion
184
+ //#region src/core/omit-undefined.ts
185
+ function omitUndefined(obj, recursive = true) {
186
+ const result = {};
187
+ for (const [key, value] of Object.entries(obj)) if (value !== void 0) if (recursive && value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) result[key] = omitUndefined(value, recursive);
188
+ else if (recursive && Array.isArray(value)) result[key] = value.filter((item) => item !== void 0).map((item) => {
189
+ if (item && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date)) return omitUndefined(item, recursive);
190
+ return item;
191
+ });
192
+ else result[key] = value;
193
+ return result;
194
+ }
195
+ //#endregion
196
+ //#region src/core/optionalize.ts
197
+ function optionalize(fn, options) {
198
+ const defaultValue = options?.defaultValue;
199
+ const emptyMode = options?.strategy ?? "nullish";
200
+ return (v) => {
201
+ if (v === null || v === void 0 || emptyMode === "falsy" && !v) return defaultValue;
202
+ return fn(v);
203
+ };
204
+ }
205
+ //#endregion
206
+ //#region src/core/retry.ts
207
+ /**
208
+ * Runs `fn`, retrying with a fixed delay on failure. Resolves with the first successful result;
209
+ * rethrows the last error once the attempts are exhausted.
210
+ */
211
+ async function retry(fn, options) {
212
+ const retries = options?.retries ?? 3;
213
+ const delayMs = options?.delayMs ?? 1e3;
214
+ for (let attempt = 1;; attempt++) try {
215
+ return await fn();
216
+ } catch (error) {
217
+ options?.onError?.(error, attempt);
218
+ if (attempt >= retries) throw error;
219
+ await sleep(delayMs, options?.signal);
220
+ }
221
+ }
222
+ //#endregion
223
+ export { assert, capitalize, createBatcher, debounce, deepFreeze, defined, indexBy, mapConcurrent, markAsUsed, maybeAll, maybeAllSettled, maybeCatch, maybeThen, noop, omitUndefined, optionalize, retry, sleep, truthy };
224
+
225
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/core/assert.ts","../src/core/capitalize.ts","../src/core/create-batcher.ts","../src/core/debounce.ts","../src/core/deep-freeze.ts","../src/core/defined.ts","../src/core/index-by.ts","../src/core/map-concurrent.ts","../src/core/maybe-promise.ts","../src/core/noop.ts","../src/core/omit-undefined.ts","../src/core/optionalize.ts","../src/core/retry.ts"],"sourcesContent":["export function assert<T>(expr: T, message?: string | Error): asserts expr {\n message ||= 'unknown assertion error';\n message = typeof message === 'string' ? new Error(message) : message;\n\n if (!expr) {\n throw message;\n }\n}\n","export function capitalize(str: string): string {\n if (!str) return str;\n\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n","/**\n * Buffers function calls and executes them together after a specified delay.\n * Useful for batching multiple events into a single operation.\n *\n * @param onFlush - Function to execute with all buffered items\n * @param delayMs - Delay in milliseconds before flushing the buffer\n * @returns Function that adds items to the buffer\n */\nexport function createBatcher<T>(onFlush: (items: T[]) => void, delayMs: number) {\n let buffer: T[] = [];\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n return (item: T) => {\n buffer.push(item);\n\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n timeoutId = setTimeout(() => {\n onFlush(buffer);\n buffer = [];\n timeoutId = null;\n }, delayMs);\n };\n}\n","export const debounce = <A extends unknown[]>(func: (...args: A) => unknown, wait: number) => {\n let timeout: ReturnType<typeof setTimeout> | undefined;\n\n return function executedFunction(...args: A) {\n const later = () => {\n timeout = undefined;\n func(...args);\n };\n\n clearTimeout(timeout);\n\n timeout = setTimeout(later, wait);\n };\n};\n","import type { ReadonlyDeep } from './readonly-deep';\n\n/**\n * Runtime companion of {@link ReadonlyDeep}: recursively Object.freeze()s a value and returns it\n * typed as deeply readonly. Safe on cyclic structures; already-frozen values are assumed to be\n * deep-frozen and are not descended into.\n *\n * Runtime limits (the type still forbids mutation in all these cases): Map / Set contents are\n * frozen, but the containers themselves stay mutable; Date internals cannot be frozen; functions\n * are left untouched.\n */\nexport function deepFreeze<T>(value: T): ReadonlyDeep<T> {\n freeze(value);\n\n return value as ReadonlyDeep<T>;\n}\n\nfunction freeze(value: unknown): void {\n if (value === null || typeof value !== 'object' || Object.isFrozen(value)) {\n return;\n }\n\n // freezing typed arrays with elements throws\n if (ArrayBuffer.isView(value)) {\n return;\n }\n\n Object.freeze(value);\n\n if (value instanceof Map) {\n for (const [key, item] of value) {\n freeze(key);\n freeze(item);\n }\n\n return;\n }\n\n if (value instanceof Set) {\n for (const item of value) {\n freeze(item);\n }\n\n return;\n }\n\n for (const key of Reflect.ownKeys(value)) {\n freeze((value as Record<PropertyKey, unknown>)[key]);\n }\n}\n","export function defined<T>(val: T | undefined | null | void): val is T {\n return val !== undefined && val !== null;\n}\n\nexport function truthy<T>(val: T | undefined | null | void): val is T {\n return !!val;\n}\n","export function indexBy<T>(array: readonly T[], keyFn: keyof T | ((value: T) => string)): Map<string, T>;\nexport function indexBy<T, K extends keyof T>(\n array: readonly T[],\n keyFn: keyof T | ((value: T) => string),\n valueFn: K,\n): Map<string, T[K]>;\nexport function indexBy<T, V>(\n array: readonly T[],\n keyFn: keyof T | ((value: T) => string),\n valueFn: (value: T) => V,\n): Map<string, V>;\nexport function indexBy<T, V = T>(\n array: readonly T[],\n keyFn: keyof T | ((value: T) => string),\n valueFn?: keyof T | ((value: T) => V),\n): Map<string, V | T> {\n return array.reduce((map, item) => {\n const key = typeof keyFn === 'function' ? keyFn(item) : String(item[keyFn]); // eslint-disable-line unicorn/no-unsafe-property-key\n\n if (valueFn) {\n const value = typeof valueFn === 'function' ? valueFn(item) : item[valueFn]; // eslint-disable-line unicorn/no-unsafe-property-key\n map.set(key, value as V | T);\n } else {\n map.set(key, item as V | T);\n }\n\n return map;\n }, new Map<string, V | T>());\n}\n","/**\n * Order-preserving concurrent map: runs `fn` over the items with a sliding pool of at most\n * `concurrency` workers, so a slow item never stalls the others. Errors propagate via Promise.all.\n */\nexport async function mapConcurrent<T, R>(\n items: readonly T[],\n fn: (item: T, index: number) => Promise<R>,\n concurrency: number,\n): Promise<R[]> {\n const results: R[] = Array.from({ length: items.length });\n let nextIndex = 0;\n\n async function worker(): Promise<void> {\n while (true) {\n const i = nextIndex++;\n\n if (i >= items.length) {\n return;\n }\n\n results[i] = await fn(items[i]!, i);\n }\n }\n\n const workerCount = Math.max(1, Math.min(concurrency, items.length));\n const workers = Array.from({ length: workerCount }, () => worker());\n\n await Promise.all(workers);\n\n return results;\n}\n","export type MaybePromise<T> = T | Promise<T>;\n\ntype MaybeAwaitedAll<T extends readonly MaybePromise<unknown>[]> = { -readonly [K in keyof T]: Awaited<T[K]> };\n\ntype MaybeSettledAll<T extends readonly MaybePromise<unknown>[]> = {\n -readonly [K in keyof T]: PromiseSettledResult<Awaited<T[K]>>;\n};\n\n/**\n * Chains a transformation on a MaybePromise value.\n * If the value is a Promise, uses .then(). Otherwise, calls fn directly.\n *\n * @example\n * ```ts\n * const result = maybeThen(map.get('key'), (value) => value.name);\n * // result is MaybePromise<string>\n * ```\n */\nexport function maybeThen<T, R>(value: MaybePromise<T>, fn: (v: T) => MaybePromise<R>): MaybePromise<R> {\n if (value instanceof Promise) {\n return value.then(fn); // eslint-disable-line unicorn/prefer-await\n }\n\n return fn(value);\n}\n\n/**\n * Adds error handling to a MaybePromise value.\n * If the value is a Promise, uses .catch(). Otherwise, returns value as-is.\n *\n * @example\n * ```ts\n * const result = maybeCatch(map.get('key'), (err) => defaultValue);\n * // result is MaybePromise<T>\n * ```\n */\nexport function maybeCatch<T>(value: MaybePromise<T>, fn: (e: unknown) => MaybePromise<T>): MaybePromise<T> {\n if (value instanceof Promise) {\n return value.catch(fn); // eslint-disable-line unicorn/prefer-await\n }\n\n return value;\n}\n\n/**\n * Combines MaybePromise values.\n * If any value is a Promise, behaves like Promise.all(). Otherwise, returns the values synchronously.\n *\n * @example\n * ```ts\n * const [user, config] = await maybeAll([cache.get('user'), cache.get('config')]);\n * // no microtask hop when both were cached synchronously\n * ```\n */\nexport function maybeAll<T extends readonly MaybePromise<unknown>[]>(values: T): MaybePromise<MaybeAwaitedAll<T>> {\n if (values.some((value) => value instanceof Promise)) {\n return Promise.all(values) as Promise<MaybeAwaitedAll<T>>;\n }\n\n return [...values] as MaybeAwaitedAll<T>;\n}\n\n/**\n * Settles MaybePromise values.\n * If any value is a Promise, behaves like Promise.allSettled(). Otherwise, returns the values\n * synchronously, each wrapped as a fulfilled result.\n */\nexport function maybeAllSettled<T extends readonly MaybePromise<unknown>[]>(\n values: T,\n): MaybePromise<MaybeSettledAll<T>> {\n if (values.some((value) => value instanceof Promise)) {\n return Promise.allSettled(values) as Promise<MaybeSettledAll<T>>;\n }\n\n return values.map((value) => ({ status: 'fulfilled', value })) as MaybeSettledAll<T>;\n}\n","export const noop = (..._: any[]) => {};\n\n// used e.g. because of https://github.com/withastro/astro/issues/14684 — fixed in\n// @astrojs/compiler 3.0.0, but @astrojs/check still bundles the unfixed 2.x line;\n// obsolete once the language server ships with compiler >= 3.0.0\nexport const markAsUsed = (..._: any[]) => {};\n","type DeepNonUndefined<T> =\n T extends Record<string, unknown>\n ? {\n [P in keyof T as undefined extends T[P] ? never : P]: T[P] extends (infer U)[]\n ? DeepNonUndefined<U>[]\n : T[P] extends Record<string, unknown>\n ? DeepNonUndefined<T[P]>\n : Exclude<T[P], undefined>;\n }\n : Exclude<T, undefined>;\n\ntype ShallowNonUndefined<T> = {\n [P in keyof T as undefined extends T[P] ? never : P]: Exclude<T[P], undefined>;\n};\n\n/**\n * Removes undefined properties from an object.\n * Optionally processes nested objects and arrays recursively.\n *\n * @param obj object to clean\n * @param recursive whether to recursively process nested objects and arrays (default: true)\n */\nexport function omitUndefined<T extends Record<string, unknown>>(obj: T, recursive?: true): DeepNonUndefined<T>;\nexport function omitUndefined<T extends Record<string, unknown>>(obj: T, recursive: false): ShallowNonUndefined<T>;\nexport function omitUndefined<T extends Record<string, unknown>>(\n obj: T,\n recursive = true,\n): DeepNonUndefined<T> | ShallowNonUndefined<T> {\n const result = {} as DeepNonUndefined<T> | ShallowNonUndefined<T>;\n\n for (const [key, value] of Object.entries(obj)) {\n if (value !== undefined) {\n // recursively process nested objects\n if (recursive && value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {\n (result as Record<string, unknown>)[key] = omitUndefined(value as Record<string, unknown>, recursive);\n } else if (recursive && Array.isArray(value)) {\n // recursively process arrays\n (result as Record<string, unknown>)[key] = value\n .filter((item) => item !== undefined)\n .map((item) => {\n if (item && typeof item === 'object' && !Array.isArray(item) && !(item instanceof Date)) {\n return omitUndefined(item as Record<string, unknown>, recursive);\n }\n return item;\n });\n } else {\n // handle primitive values\n (result as Record<string, unknown>)[key] = value;\n }\n }\n }\n\n return result;\n}\n","export function optionalize<T, U, D = undefined>(\n fn: (v: T) => U,\n options?: { defaultValue?: D | undefined; strategy?: 'falsy' | 'nullish' | undefined } | undefined,\n): (v: T | null | undefined) => U | D {\n const defaultValue = options?.defaultValue;\n const emptyMode = options?.strategy ?? 'nullish';\n\n return (v: T | null | undefined): U | D => {\n if (v === null || v === undefined || (emptyMode === 'falsy' && !v)) {\n return defaultValue as D;\n }\n\n return fn(v);\n };\n}\n","import { sleep } from './sleep';\n\nexport type RetryOptions = {\n /** total number of attempts before the last error is rethrown (default 3) */\n retries?: number | undefined;\n\n /** fixed delay between attempts (default 1000) */\n delayMs?: number | undefined;\n\n /** aborts the delay between attempts */\n signal?: AbortSignal | undefined;\n\n /** called after every failed attempt with the error and the 1-based attempt number */\n onError?: ((error: unknown, attempt: number) => void) | undefined;\n};\n\n/**\n * Runs `fn`, retrying with a fixed delay on failure. Resolves with the first successful result;\n * rethrows the last error once the attempts are exhausted.\n */\nexport async function retry<T>(fn: () => T, options?: RetryOptions | undefined): Promise<T> {\n const retries = options?.retries ?? 3;\n const delayMs = options?.delayMs ?? 1000;\n\n for (let attempt = 1; ; attempt++) {\n try {\n return await fn();\n } catch (error) {\n options?.onError?.(error, attempt);\n\n if (attempt >= retries) {\n throw error;\n }\n\n await sleep(delayMs, options?.signal);\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ //#region src/match/index.d.ts
2
+ type StringPattern = ((value: string) => boolean) | {
3
+ exact: string;
4
+ } | {
5
+ prefix: string;
6
+ } | {
7
+ suffix: string;
8
+ } | {
9
+ includes: string;
10
+ } | {
11
+ pattern: RegExp;
12
+ };
13
+ /**
14
+ * Checks if a string matches a single pattern.
15
+ */
16
+ declare function matches(value: string, pattern: StringPattern): boolean;
17
+ /**
18
+ * Checks if a string matches any of the patterns.
19
+ * For repeated matching against the same pattern set, prefer {@link createMatcher}.
20
+ */
21
+ declare function matchesAny(value: string, patterns: readonly StringPattern[]): boolean;
22
+ /**
23
+ * Compiles a pattern set into a matcher function.
24
+ *
25
+ * Patterns are bucketed by check cost at creation time: all `exact` patterns collapse
26
+ * into a single Set lookup, then prefixes / suffixes / substrings run as cheap string
27
+ * checks, and regular expressions and matcher functions run last.
28
+ */
29
+ declare function createMatcher(patterns: readonly StringPattern[]): (value: string) => boolean;
30
+ //#endregion
31
+ export { StringPattern, createMatcher, matches, matchesAny };
32
+ //# sourceMappingURL=match.d.ts.map
package/dist/match.js ADDED
@@ -0,0 +1,48 @@
1
+ //#region src/match/index.ts
2
+ /**
3
+ * Checks if a string matches a single pattern.
4
+ */
5
+ function matches(value, pattern) {
6
+ if (typeof pattern === "function") return pattern(value);
7
+ if ("exact" in pattern) return value === pattern.exact;
8
+ if ("prefix" in pattern) return value.startsWith(pattern.prefix);
9
+ if ("suffix" in pattern) return value.endsWith(pattern.suffix);
10
+ if ("includes" in pattern) return value.includes(pattern.includes);
11
+ return pattern.pattern.test(value);
12
+ }
13
+ /**
14
+ * Checks if a string matches any of the patterns.
15
+ * For repeated matching against the same pattern set, prefer {@link createMatcher}.
16
+ */
17
+ function matchesAny(value, patterns) {
18
+ return patterns.some((pattern) => matches(value, pattern));
19
+ }
20
+ /**
21
+ * Compiles a pattern set into a matcher function.
22
+ *
23
+ * Patterns are bucketed by check cost at creation time: all `exact` patterns collapse
24
+ * into a single Set lookup, then prefixes / suffixes / substrings run as cheap string
25
+ * checks, and regular expressions and matcher functions run last.
26
+ */
27
+ function createMatcher(patterns) {
28
+ const exacts = /* @__PURE__ */ new Set();
29
+ const prefixes = [];
30
+ const suffixes = [];
31
+ const substrings = [];
32
+ const regexps = [];
33
+ const fns = [];
34
+ for (const pattern of patterns) if (typeof pattern === "function") fns.push(pattern);
35
+ else if ("exact" in pattern) exacts.add(pattern.exact);
36
+ else if ("prefix" in pattern) prefixes.push(pattern.prefix);
37
+ else if ("suffix" in pattern) suffixes.push(pattern.suffix);
38
+ else if ("includes" in pattern) substrings.push(pattern.includes);
39
+ else regexps.push(pattern.pattern);
40
+ return (value) => {
41
+ if (exacts.has(value)) return true;
42
+ return prefixes.some((prefix) => value.startsWith(prefix)) || suffixes.some((suffix) => value.endsWith(suffix)) || substrings.some((substring) => value.includes(substring)) || regexps.some((regexp) => regexp.test(value)) || fns.some((fn) => fn(value));
43
+ };
44
+ }
45
+ //#endregion
46
+ export { createMatcher, matches, matchesAny };
47
+
48
+ //# sourceMappingURL=match.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.js","names":[],"sources":["../src/match/index.ts"],"sourcesContent":["export type StringPattern =\n | ((value: string) => boolean)\n | { exact: string }\n | { prefix: string }\n | { suffix: string }\n | { includes: string }\n | { pattern: RegExp };\n\n/**\n * Checks if a string matches a single pattern.\n */\nexport function matches(value: string, pattern: StringPattern): boolean {\n if (typeof pattern === 'function') {\n return pattern(value);\n }\n\n if ('exact' in pattern) {\n return value === pattern.exact;\n }\n\n if ('prefix' in pattern) {\n return value.startsWith(pattern.prefix);\n }\n\n if ('suffix' in pattern) {\n return value.endsWith(pattern.suffix);\n }\n\n if ('includes' in pattern) {\n return value.includes(pattern.includes);\n }\n\n return pattern.pattern.test(value);\n}\n\n/**\n * Checks if a string matches any of the patterns.\n * For repeated matching against the same pattern set, prefer {@link createMatcher}.\n */\nexport function matchesAny(value: string, patterns: readonly StringPattern[]): boolean {\n return patterns.some((pattern) => matches(value, pattern));\n}\n\n/**\n * Compiles a pattern set into a matcher function.\n *\n * Patterns are bucketed by check cost at creation time: all `exact` patterns collapse\n * into a single Set lookup, then prefixes / suffixes / substrings run as cheap string\n * checks, and regular expressions and matcher functions run last.\n */\nexport function createMatcher(patterns: readonly StringPattern[]): (value: string) => boolean {\n const exacts = new Set<string>();\n const prefixes: string[] = [];\n const suffixes: string[] = [];\n const substrings: string[] = [];\n const regexps: RegExp[] = [];\n const fns: ((value: string) => boolean)[] = [];\n\n for (const pattern of patterns) {\n if (typeof pattern === 'function') {\n fns.push(pattern);\n } else if ('exact' in pattern) {\n exacts.add(pattern.exact);\n } else if ('prefix' in pattern) {\n prefixes.push(pattern.prefix);\n } else if ('suffix' in pattern) {\n suffixes.push(pattern.suffix);\n } else if ('includes' in pattern) {\n substrings.push(pattern.includes);\n } else {\n regexps.push(pattern.pattern);\n }\n }\n\n return (value) => {\n if (exacts.has(value)) {\n return true;\n }\n\n return (\n prefixes.some((prefix) => value.startsWith(prefix)) ||\n suffixes.some((suffix) => value.endsWith(suffix)) ||\n substrings.some((substring) => value.includes(substring)) ||\n regexps.some((regexp) => regexp.test(value)) ||\n fns.some((fn) => fn(value))\n );\n };\n}\n"],"mappings":";;;;AAWA,SAAgB,QAAQ,OAAe,SAAiC;CACtE,IAAI,OAAO,YAAY,YACrB,OAAO,QAAQ,KAAK;CAGtB,IAAI,WAAW,SACb,OAAO,UAAU,QAAQ;CAG3B,IAAI,YAAY,SACd,OAAO,MAAM,WAAW,QAAQ,MAAM;CAGxC,IAAI,YAAY,SACd,OAAO,MAAM,SAAS,QAAQ,MAAM;CAGtC,IAAI,cAAc,SAChB,OAAO,MAAM,SAAS,QAAQ,QAAQ;CAGxC,OAAO,QAAQ,QAAQ,KAAK,KAAK;AACnC;;;;;AAMA,SAAgB,WAAW,OAAe,UAA6C;CACrF,OAAO,SAAS,MAAM,YAAY,QAAQ,OAAO,OAAO,CAAC;AAC3D;;;;;;;;AASA,SAAgB,cAAc,UAAgE;CAC5F,MAAM,yBAAS,IAAI,IAAY;CAC/B,MAAM,WAAqB,CAAC;CAC5B,MAAM,WAAqB,CAAC;CAC5B,MAAM,aAAuB,CAAC;CAC9B,MAAM,UAAoB,CAAC;CAC3B,MAAM,MAAsC,CAAC;CAE7C,KAAK,MAAM,WAAW,UACpB,IAAI,OAAO,YAAY,YACrB,IAAI,KAAK,OAAO;MACX,IAAI,WAAW,SACpB,OAAO,IAAI,QAAQ,KAAK;MACnB,IAAI,YAAY,SACrB,SAAS,KAAK,QAAQ,MAAM;MACvB,IAAI,YAAY,SACrB,SAAS,KAAK,QAAQ,MAAM;MACvB,IAAI,cAAc,SACvB,WAAW,KAAK,QAAQ,QAAQ;MAEhC,QAAQ,KAAK,QAAQ,OAAO;CAIhC,QAAQ,UAAU;EAChB,IAAI,OAAO,IAAI,KAAK,GAClB,OAAO;EAGT,OACE,SAAS,MAAM,WAAW,MAAM,WAAW,MAAM,CAAC,KAClD,SAAS,MAAM,WAAW,MAAM,SAAS,MAAM,CAAC,KAChD,WAAW,MAAM,cAAc,MAAM,SAAS,SAAS,CAAC,KACxD,QAAQ,MAAM,WAAW,OAAO,KAAK,KAAK,CAAC,KAC3C,IAAI,MAAM,OAAO,GAAG,KAAK,CAAC;CAE9B;AACF"}
@@ -0,0 +1,28 @@
1
+ import { DependencyList, EffectCallback } from "react";
2
+ //#region src/react/use-debounced-value.d.ts
3
+ /**
4
+ * Returns the value, updated only after it has stayed unchanged for the given delay.
5
+ */
6
+ declare function useDebouncedValue<T>(value: T, delay: number): T;
7
+ //#endregion
8
+ //#region src/react/use-effect-after-mount.d.ts
9
+ /**
10
+ * Runs the effect on dependency changes, but skips the initial mount.
11
+ */
12
+ declare function useEffectAfterMount(effect: EffectCallback, deps?: DependencyList | undefined): void;
13
+ //#endregion
14
+ //#region src/react/use-effect-once.d.ts
15
+ /**
16
+ * Runs the effect exactly once on mount.
17
+ */
18
+ declare function useEffectOnce(effect: EffectCallback): void;
19
+ //#endregion
20
+ //#region src/react/use-is-mobile.d.ts
21
+ /**
22
+ * Tracks whether the viewport is below the mobile breakpoint (768px).
23
+ * Returns false during SSR.
24
+ */
25
+ declare function useIsMobile(): boolean;
26
+ //#endregion
27
+ export { useDebouncedValue, useEffectAfterMount, useEffectOnce, useIsMobile };
28
+ //# sourceMappingURL=react.d.ts.map
package/dist/react.js ADDED
@@ -0,0 +1,62 @@
1
+ import { useEffect, useRef, useState, useSyncExternalStore } from "react";
2
+ //#region src/react/use-debounced-value.ts
3
+ /**
4
+ * Returns the value, updated only after it has stayed unchanged for the given delay.
5
+ */
6
+ function useDebouncedValue(value, delay) {
7
+ const [debouncedValue, setDebouncedValue] = useState(value);
8
+ useEffect(() => {
9
+ const timeout = setTimeout(() => {
10
+ setDebouncedValue(value);
11
+ }, delay);
12
+ return () => clearTimeout(timeout);
13
+ }, [value, delay]);
14
+ return debouncedValue;
15
+ }
16
+ //#endregion
17
+ //#region src/react/use-effect-after-mount.ts
18
+ /**
19
+ * Runs the effect on dependency changes, but skips the initial mount.
20
+ */
21
+ function useEffectAfterMount(effect, deps) {
22
+ const mountedRef = useRef(false);
23
+ useEffect(() => {
24
+ if (mountedRef.current) return effect();
25
+ mountedRef.current = true;
26
+ }, deps);
27
+ }
28
+ //#endregion
29
+ //#region src/react/use-effect-once.ts
30
+ /**
31
+ * Runs the effect exactly once on mount.
32
+ */
33
+ function useEffectOnce(effect) {
34
+ const hasRunRef = useRef(false);
35
+ useEffect(() => {
36
+ if (hasRunRef.current) return;
37
+ hasRunRef.current = true;
38
+ return effect();
39
+ }, []);
40
+ }
41
+ //#endregion
42
+ //#region src/react/use-is-mobile.ts
43
+ const MOBILE_BREAKPOINT = 768;
44
+ function subscribe(callback) {
45
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
46
+ mql.addEventListener("change", callback);
47
+ return () => mql.removeEventListener("change", callback);
48
+ }
49
+ function getSnapshot() {
50
+ return window.innerWidth < MOBILE_BREAKPOINT;
51
+ }
52
+ /**
53
+ * Tracks whether the viewport is below the mobile breakpoint (768px).
54
+ * Returns false during SSR.
55
+ */
56
+ function useIsMobile() {
57
+ return useSyncExternalStore(subscribe, getSnapshot, () => false);
58
+ }
59
+ //#endregion
60
+ export { useDebouncedValue, useEffectAfterMount, useEffectOnce, useIsMobile };
61
+
62
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","names":[],"sources":["../src/react/use-debounced-value.ts","../src/react/use-effect-after-mount.ts","../src/react/use-effect-once.ts","../src/react/use-is-mobile.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\n\n/**\n * Returns the value, updated only after it has stayed unchanged for the given delay.\n */\nexport function useDebouncedValue<T>(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState(value);\n\n useEffect(() => {\n const timeout = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => clearTimeout(timeout);\n }, [value, delay]);\n\n return debouncedValue;\n}\n","import { type DependencyList, type EffectCallback, useEffect, useRef } from 'react';\n\n/**\n * Runs the effect on dependency changes, but skips the initial mount.\n */\nexport function useEffectAfterMount(effect: EffectCallback, deps?: DependencyList | undefined) {\n const mountedRef = useRef(false);\n\n useEffect(() => {\n if (mountedRef.current) {\n return effect();\n }\n mountedRef.current = true;\n }, deps); // eslint-disable-line @eslint-react/exhaustive-deps\n}\n","import { type EffectCallback, useEffect, useRef } from 'react';\n\n/**\n * Runs the effect exactly once on mount.\n */\nexport function useEffectOnce(effect: EffectCallback) {\n const hasRunRef = useRef(false);\n\n useEffect(() => {\n if (hasRunRef.current) {\n return;\n }\n\n hasRunRef.current = true;\n\n return effect();\n }, []); // eslint-disable-line @eslint-react/exhaustive-deps\n}\n","import { useSyncExternalStore } from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nfunction subscribe(callback: () => void): () => void {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n\n mql.addEventListener('change', callback);\n\n return () => mql.removeEventListener('change', callback);\n}\n\nfunction getSnapshot(): boolean {\n return window.innerWidth < MOBILE_BREAKPOINT;\n}\n\n/**\n * Tracks whether the viewport is below the mobile breakpoint (768px).\n * Returns false during SSR.\n */\nexport function useIsMobile(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, () => false);\n}\n"],"mappings":";;;;;AAKA,SAAgB,kBAAqB,OAAU,OAAkB;CAC/D,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,KAAK;CAE1D,gBAAgB;EACd,MAAM,UAAU,iBAAiB;GAC/B,kBAAkB,KAAK;EACzB,GAAG,KAAK;EAER,aAAa,aAAa,OAAO;CACnC,GAAG,CAAC,OAAO,KAAK,CAAC;CAEjB,OAAO;AACT;;;;;;ACZA,SAAgB,oBAAoB,QAAwB,MAAmC;CAC7F,MAAM,aAAa,OAAO,KAAK;CAE/B,gBAAgB;EACd,IAAI,WAAW,SACb,OAAO,OAAO;EAEhB,WAAW,UAAU;CACvB,GAAG,IAAI;AACT;;;;;;ACTA,SAAgB,cAAc,QAAwB;CACpD,MAAM,YAAY,OAAO,KAAK;CAE9B,gBAAgB;EACd,IAAI,UAAU,SACZ;EAGF,UAAU,UAAU;EAEpB,OAAO,OAAO;CAChB,GAAG,CAAC,CAAC;AACP;;;ACfA,MAAM,oBAAoB;AAE1B,SAAS,UAAU,UAAkC;CACnD,MAAM,MAAM,OAAO,WAAW,eAAe,oBAAoB,EAAE,IAAI;CAEvE,IAAI,iBAAiB,UAAU,QAAQ;CAEvC,aAAa,IAAI,oBAAoB,UAAU,QAAQ;AACzD;AAEA,SAAS,cAAuB;CAC9B,OAAO,OAAO,aAAa;AAC7B;;;;;AAMA,SAAgB,cAAuB;CACrC,OAAO,qBAAqB,WAAW,mBAAmB,KAAK;AACjE"}
@@ -0,0 +1,19 @@
1
+ //#region src/core/sleep.ts
2
+ function sleep(ms, signal) {
3
+ if (signal?.aborted) return Promise.reject(signal.reason);
4
+ return new Promise((resolve, reject) => {
5
+ const timer = setTimeout(() => {
6
+ signal?.removeEventListener("abort", onAbort);
7
+ resolve();
8
+ }, ms);
9
+ const onAbort = () => {
10
+ clearTimeout(timer);
11
+ reject(signal.reason);
12
+ };
13
+ signal?.addEventListener("abort", onAbort, { once: true });
14
+ });
15
+ }
16
+ //#endregion
17
+ export { sleep as t };
18
+
19
+ //# sourceMappingURL=sleep-J71HYu7n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep-J71HYu7n.js","names":[],"sources":["../src/core/sleep.ts"],"sourcesContent":["export function sleep(ms: number, signal?: AbortSignal | undefined): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason);\n }\n\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason);\n };\n\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n"],"mappings":";AAAA,SAAgB,MAAM,IAAY,QAAiD;CACjF,IAAI,QAAQ,SACV,OAAO,QAAQ,OAAO,OAAO,MAAM;CAGrC,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,QAAQ,iBAAiB;GAC7B,QAAQ,oBAAoB,SAAS,OAAO;GAC5C,QAAQ;EACV,GAAG,EAAE;EAEL,MAAM,gBAAgB;GACpB,aAAa,KAAK;GAClB,OAAO,OAAQ,MAAM;EACvB;EAEA,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC3D,CAAC;AACH"}
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@entwico/dash",
3
+ "version": "1.0.0",
4
+ "description": "Typical reusable utilities shared across projects. Tree-shakable, ESM-only, zero-dependency core.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "author": "admin@entwico.com",
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/entwico/dash"
12
+ },
13
+ "scripts": {
14
+ "start": "tsdown --watch",
15
+ "build": "tsdown",
16
+ "test": "vitest",
17
+ "coverage": "vitest run --coverage",
18
+ "clear": "rm -rf dist",
19
+ "lint": "eslint --cache src",
20
+ "lint:fix": "pnpm lint --fix",
21
+ "typecheck": "tsc --noEmit",
22
+ "validate": "pnpm lint && pnpm typecheck && pnpm coverage",
23
+ "prepublishOnly": "pnpm build",
24
+ "ci:version": "changeset version && pnpm install --no-frozen-lockfile",
25
+ "ci:publish": "changeset publish"
26
+ },
27
+ "peerDependencies": {
28
+ "clsx": ">=2",
29
+ "react": ">=18",
30
+ "tailwind-merge": ">=2"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "clsx": {
34
+ "optional": true
35
+ },
36
+ "react": {
37
+ "optional": true
38
+ },
39
+ "tailwind-merge": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "devDependencies": {
44
+ "@changesets/cli": "^2.31.0",
45
+ "@entwico/eslint-config": "^2.0.7",
46
+ "@types/node": "^26.1.1",
47
+ "@types/react": "^19.2.17",
48
+ "@vitest/coverage-istanbul": "^4.1.10",
49
+ "clsx": "^2.1.1",
50
+ "eslint": "^10.6.0",
51
+ "react": "^19.2.7",
52
+ "tailwind-merge": "^3.6.0",
53
+ "tsdown": "^0.22.4",
54
+ "typescript": "^6.0.3",
55
+ "vitest": "^4.1.10"
56
+ },
57
+ "files": [
58
+ "dist"
59
+ ],
60
+ "exports": {
61
+ ".": {
62
+ "types": "./dist/index.d.ts",
63
+ "import": "./dist/index.js"
64
+ },
65
+ "./async": {
66
+ "types": "./dist/async.d.ts",
67
+ "import": "./dist/async.js"
68
+ },
69
+ "./match": {
70
+ "types": "./dist/match.d.ts",
71
+ "import": "./dist/match.js"
72
+ },
73
+ "./cn": {
74
+ "types": "./dist/cn.d.ts",
75
+ "import": "./dist/cn.js"
76
+ },
77
+ "./react": {
78
+ "types": "./dist/react.d.ts",
79
+ "import": "./dist/react.js"
80
+ }
81
+ },
82
+ "publishConfig": {
83
+ "access": "public",
84
+ "provenance": true
85
+ },
86
+ "packageManager": "pnpm@11.1.3"
87
+ }