@forthtilliath/ts-kit 0.14.0 → 0.15.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/README.md +18 -0
- package/dist/array/index.d.ts +1 -0
- package/dist/array/index.d.ts.map +1 -1
- package/dist/array/index.js +1 -0
- package/dist/array/pickRandom.d.ts +14 -0
- package/dist/array/pickRandom.d.ts.map +1 -0
- package/dist/array/pickRandom.js +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/security/createAttemptLimiter.d.ts +65 -0
- package/dist/security/createAttemptLimiter.d.ts.map +1 -0
- package/dist/security/createAttemptLimiter.js +96 -0
- package/dist/security/index.d.ts +2 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +1 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -68,6 +68,8 @@ Importing them is harmless anywhere: the DOM is only touched when called.
|
|
|
68
68
|
- `rankByNameMatch(items, query, getName)` — ranks `items` by relevance to
|
|
69
69
|
`query`: earlier match position first, then shorter name — for a
|
|
70
70
|
search-as-you-type list.
|
|
71
|
+
- `pickRandom(array, random?)` — one element picked at random, or
|
|
72
|
+
`undefined` for an empty array; same `random` as `shuffle`.
|
|
71
73
|
- `shuffle(array, random?)` — shuffled copy (Fisher–Yates); `random`
|
|
72
74
|
(default `Math.random`) can be swapped for a seeded generator in tests.
|
|
73
75
|
- `sortUpcomingFirst(items, getDates, { now? })` — upcoming items first
|
|
@@ -266,6 +268,22 @@ Building blocks for writing an iCalendar (`.ics`, RFC 5545) file.
|
|
|
266
268
|
- `deepEqual(a, b)` — recursive structural equality check.
|
|
267
269
|
- `mapValues(obj, fn)` — maps every value of an object through `fn`.
|
|
268
270
|
|
|
271
|
+
### `security`
|
|
272
|
+
|
|
273
|
+
- `createAttemptLimiter({ storage, key, freeAttempts?, delaysMs?, now? })` —
|
|
274
|
+
growing delays between attempts at a secret (a PIN…): 4 free failures,
|
|
275
|
+
then 30 s, 1 min, 5 min, 15 min for each one after. `attempt(verify)`
|
|
276
|
+
returns `{ ok: true }` or `{ ok: false, lockedUntil }` (0 when no delay
|
|
277
|
+
is running) and doesn't call `verify` while locked; `getLockedUntil()`,
|
|
278
|
+
`reset()`. The counter lives in the injected `storage` (e.g.
|
|
279
|
+
expo-secure-store's `getItemAsync`/`setItemAsync`/`deleteItemAsync`); an
|
|
280
|
+
unreadable or tampered value counts as used up, not as zero, a clock set
|
|
281
|
+
back restarts the delay from now, and parallel attempts are serialized.
|
|
282
|
+
Limit: the delay is measured with the device clock, so setting it forward
|
|
283
|
+
ends the wait early — the failure count still grows, and each new failure
|
|
284
|
+
imposes the longest delay again. Keep the secret itself out of reach
|
|
285
|
+
(stored hashed, in the Android Keystore through expo-secure-store).
|
|
286
|
+
|
|
269
287
|
### `string`
|
|
270
288
|
|
|
271
289
|
- `capitalize(str)` — uppercases the first letter, lowercases the rest.
|
package/dist/array/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./getMostRecentIds.js";
|
|
|
5
5
|
export * from "./indexById.js";
|
|
6
6
|
export * from "./moveItem.js";
|
|
7
7
|
export * from "./nextInCycle.js";
|
|
8
|
+
export * from "./pickRandom.js";
|
|
8
9
|
export * from "./rankByNameMatch.js";
|
|
9
10
|
export * from "./shuffle.js";
|
|
10
11
|
export * from "./sortUpcomingFirst.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/array/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/array/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,gBAAgB,CAAC"}
|
package/dist/array/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./getMostRecentIds.js";
|
|
|
5
5
|
export * from "./indexById.js";
|
|
6
6
|
export * from "./moveItem.js";
|
|
7
7
|
export * from "./nextInCycle.js";
|
|
8
|
+
export * from "./pickRandom.js";
|
|
8
9
|
export * from "./rankByNameMatch.js";
|
|
9
10
|
export * from "./shuffle.js";
|
|
10
11
|
export * from "./sortUpcomingFirst.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns one element of an array, picked at random (each equally likely).
|
|
3
|
+
*
|
|
4
|
+
* @param array - The array to pick from.
|
|
5
|
+
* @param random - Returns a number in `[0, 1)`. Defaults to `Math.random`;
|
|
6
|
+
* pass a seeded generator for deterministic tests.
|
|
7
|
+
* @returns The picked element, or `undefined` if `array` is empty.
|
|
8
|
+
* @example
|
|
9
|
+
* pickRandom(["rock", "paper", "scissors"]); // => e.g. "paper"
|
|
10
|
+
* pickRandom([1, 2, 3], () => 0.99); // => 3
|
|
11
|
+
* pickRandom([]); // => undefined
|
|
12
|
+
*/
|
|
13
|
+
export declare function pickRandom<T>(array: readonly T[], random?: () => number): T | undefined;
|
|
14
|
+
//# sourceMappingURL=pickRandom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pickRandom.d.ts","sourceRoot":"","sources":["../../src/array/pickRandom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,MAAM,GAAE,MAAM,MAAoB,GACjC,CAAC,GAAG,SAAS,CAEf"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns one element of an array, picked at random (each equally likely).
|
|
3
|
+
*
|
|
4
|
+
* @param array - The array to pick from.
|
|
5
|
+
* @param random - Returns a number in `[0, 1)`. Defaults to `Math.random`;
|
|
6
|
+
* pass a seeded generator for deterministic tests.
|
|
7
|
+
* @returns The picked element, or `undefined` if `array` is empty.
|
|
8
|
+
* @example
|
|
9
|
+
* pickRandom(["rock", "paper", "scissors"]); // => e.g. "paper"
|
|
10
|
+
* pickRandom([1, 2, 3], () => 0.99); // => 3
|
|
11
|
+
* pickRandom([]); // => undefined
|
|
12
|
+
*/
|
|
13
|
+
export function pickRandom(array, random = Math.random) {
|
|
14
|
+
return array[Math.floor(random() * array.length)];
|
|
15
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from "./image/index.js";
|
|
|
12
12
|
export * from "./markdown/index.js";
|
|
13
13
|
export * from "./number/index.js";
|
|
14
14
|
export * from "./object/index.js";
|
|
15
|
+
export * from "./security/index.js";
|
|
15
16
|
export * from "./string/index.js";
|
|
16
17
|
export * from "./version/index.js";
|
|
17
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -17,5 +17,6 @@ export * from "./image/index.js";
|
|
|
17
17
|
export * from "./markdown/index.js";
|
|
18
18
|
export * from "./number/index.js";
|
|
19
19
|
export * from "./object/index.js";
|
|
20
|
+
export * from "./security/index.js";
|
|
20
21
|
export * from "./string/index.js";
|
|
21
22
|
export * from "./version/index.js";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Where the failure counter is kept — e.g. expo-secure-store on Android. */
|
|
2
|
+
export interface AttemptLimiterStorage {
|
|
3
|
+
getItem: (key: string) => Promise<string | null>;
|
|
4
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
5
|
+
deleteItem: (key: string) => Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export interface AttemptLimiterOptions {
|
|
8
|
+
storage: AttemptLimiterStorage;
|
|
9
|
+
/** Storage key of the failure counter. */
|
|
10
|
+
key: string;
|
|
11
|
+
/** Failures allowed before any delay. @defaultValue 4 */
|
|
12
|
+
freeAttempts?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Delay imposed after the first failure past `freeAttempts`, the second…
|
|
15
|
+
* the last one repeating for every failure after.
|
|
16
|
+
* @defaultValue 30 s, 1 min, 5 min, 15 min
|
|
17
|
+
*/
|
|
18
|
+
delaysMs?: readonly number[];
|
|
19
|
+
/** @defaultValue `Date.now` */
|
|
20
|
+
now?: () => number;
|
|
21
|
+
}
|
|
22
|
+
export type AttemptResult = {
|
|
23
|
+
ok: true;
|
|
24
|
+
} | {
|
|
25
|
+
ok: false;
|
|
26
|
+
lockedUntil: number;
|
|
27
|
+
};
|
|
28
|
+
export interface AttemptLimiter {
|
|
29
|
+
/**
|
|
30
|
+
* Runs `verify` (e.g. compares a PIN) unless a delay is running, and
|
|
31
|
+
* updates the counter: reset on success, one more failure otherwise.
|
|
32
|
+
* `lockedUntil` is the end of the delay now imposed (0 for none). Attempts
|
|
33
|
+
* run one at a time, so parallel ones can't lose a failure.
|
|
34
|
+
*/
|
|
35
|
+
attempt: (verify: () => boolean | Promise<boolean>) => Promise<AttemptResult>;
|
|
36
|
+
/** End of the running delay (a timestamp), or 0 if an attempt is allowed. */
|
|
37
|
+
getLockedUntil: () => Promise<number>;
|
|
38
|
+
/** Forgets every failure (e.g. once the PIN is reset another way). */
|
|
39
|
+
reset: () => Promise<void>;
|
|
40
|
+
/** The delay imposed after `count` consecutive failures (0 for none). */
|
|
41
|
+
delayAfter: (count: number) => number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Limits attempts at a secret (a PIN, a passphrase) with growing delays:
|
|
45
|
+
* after `freeAttempts` failures, each new one imposes a wait. The counter is
|
|
46
|
+
* persisted through `storage`, so closing the app doesn't reset it, and an
|
|
47
|
+
* unreadable value is never taken as "no failure" (tampering with it would
|
|
48
|
+
* otherwise lift the limit): the next failure is then delayed at once. A
|
|
49
|
+
* last failure dated in the future (the clock was set back) restarts the
|
|
50
|
+
* delay from now instead of locking until that date.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* const pinLock = createAttemptLimiter({
|
|
54
|
+
* storage: {
|
|
55
|
+
* getItem: SecureStore.getItemAsync,
|
|
56
|
+
* setItem: SecureStore.setItemAsync,
|
|
57
|
+
* deleteItem: SecureStore.deleteItemAsync,
|
|
58
|
+
* },
|
|
59
|
+
* key: "pin-failures",
|
|
60
|
+
* });
|
|
61
|
+
* const result = await pinLock.attempt(() => isPinCorrect(typedPin));
|
|
62
|
+
* if (!result.ok && result.lockedUntil) showWaitUntil(result.lockedUntil);
|
|
63
|
+
*/
|
|
64
|
+
export declare function createAttemptLimiter({ storage, key, freeAttempts, delaysMs, now, }: AttemptLimiterOptions): AttemptLimiter;
|
|
65
|
+
//# sourceMappingURL=createAttemptLimiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createAttemptLimiter.d.ts","sourceRoot":"","sources":["../../src/security/createAttemptLimiter.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjD,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,qBAAqB,CAAC;IAC/B,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,+BAA+B;IAC/B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAC9E,6EAA6E;IAC7E,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,sEAAsE;IACtE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,yEAAyE;IACzE,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC;AAUD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,GAAG,EACH,YAAgB,EAChB,QAA4B,EAC5B,GAAc,GACf,EAAE,qBAAqB,GAAG,cAAc,CA6ExC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const DEFAULT_DELAYS_MS = [30_000, 60_000, 5 * 60_000, 15 * 60_000];
|
|
2
|
+
const MAX_COUNT = 1_000_000;
|
|
3
|
+
/**
|
|
4
|
+
* Limits attempts at a secret (a PIN, a passphrase) with growing delays:
|
|
5
|
+
* after `freeAttempts` failures, each new one imposes a wait. The counter is
|
|
6
|
+
* persisted through `storage`, so closing the app doesn't reset it, and an
|
|
7
|
+
* unreadable value is never taken as "no failure" (tampering with it would
|
|
8
|
+
* otherwise lift the limit): the next failure is then delayed at once. A
|
|
9
|
+
* last failure dated in the future (the clock was set back) restarts the
|
|
10
|
+
* delay from now instead of locking until that date.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const pinLock = createAttemptLimiter({
|
|
14
|
+
* storage: {
|
|
15
|
+
* getItem: SecureStore.getItemAsync,
|
|
16
|
+
* setItem: SecureStore.setItemAsync,
|
|
17
|
+
* deleteItem: SecureStore.deleteItemAsync,
|
|
18
|
+
* },
|
|
19
|
+
* key: "pin-failures",
|
|
20
|
+
* });
|
|
21
|
+
* const result = await pinLock.attempt(() => isPinCorrect(typedPin));
|
|
22
|
+
* if (!result.ok && result.lockedUntil) showWaitUntil(result.lockedUntil);
|
|
23
|
+
*/
|
|
24
|
+
export function createAttemptLimiter({ storage, key, freeAttempts = 4, delaysMs = DEFAULT_DELAYS_MS, now = Date.now, }) {
|
|
25
|
+
let queue = Promise.resolve();
|
|
26
|
+
function delayAfter(count) {
|
|
27
|
+
if (count <= freeAttempts || delaysMs.length === 0)
|
|
28
|
+
return 0;
|
|
29
|
+
const index = Math.min(count - freeAttempts - 1, delaysMs.length - 1);
|
|
30
|
+
return delaysMs[index] ?? 0;
|
|
31
|
+
}
|
|
32
|
+
async function readFailures() {
|
|
33
|
+
const raw = await storage.getItem(key);
|
|
34
|
+
if (raw === null || raw === "")
|
|
35
|
+
return { count: 0, lastFailureAt: 0 };
|
|
36
|
+
try {
|
|
37
|
+
const { count, lastFailureAt } = JSON.parse(raw);
|
|
38
|
+
if (typeof count === "number" &&
|
|
39
|
+
Number.isInteger(count) &&
|
|
40
|
+
count >= 0 &&
|
|
41
|
+
count <= MAX_COUNT &&
|
|
42
|
+
typeof lastFailureAt === "number" &&
|
|
43
|
+
Number.isFinite(lastFailureAt) &&
|
|
44
|
+
lastFailureAt >= 0) {
|
|
45
|
+
return { count, lastFailureAt };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Unreadable: handled below.
|
|
50
|
+
}
|
|
51
|
+
return { count: freeAttempts, lastFailureAt: 0 };
|
|
52
|
+
}
|
|
53
|
+
async function writeFailures(failures) {
|
|
54
|
+
await storage.setItem(key, JSON.stringify(failures));
|
|
55
|
+
}
|
|
56
|
+
async function lockedUntil() {
|
|
57
|
+
const failures = await readFailures();
|
|
58
|
+
const current = now();
|
|
59
|
+
if (failures.lastFailureAt > current) {
|
|
60
|
+
failures.lastFailureAt = current;
|
|
61
|
+
await writeFailures(failures);
|
|
62
|
+
}
|
|
63
|
+
const until = failures.lastFailureAt + delayAfter(failures.count);
|
|
64
|
+
return until > current ? until : 0;
|
|
65
|
+
}
|
|
66
|
+
function serialized(task) {
|
|
67
|
+
const run = queue.then(task, task);
|
|
68
|
+
queue = run.catch(() => undefined);
|
|
69
|
+
return run;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
delayAfter,
|
|
73
|
+
getLockedUntil: () => serialized(lockedUntil),
|
|
74
|
+
reset: () => serialized(() => storage.deleteItem(key)),
|
|
75
|
+
attempt: (verify) => serialized(async () => {
|
|
76
|
+
const locked = await lockedUntil();
|
|
77
|
+
if (locked)
|
|
78
|
+
return { ok: false, lockedUntil: locked };
|
|
79
|
+
if (await verify()) {
|
|
80
|
+
await storage.deleteItem(key);
|
|
81
|
+
return { ok: true };
|
|
82
|
+
}
|
|
83
|
+
const { count } = await readFailures();
|
|
84
|
+
const failures = {
|
|
85
|
+
count: Math.min(count + 1, MAX_COUNT),
|
|
86
|
+
lastFailureAt: now(),
|
|
87
|
+
};
|
|
88
|
+
await writeFailures(failures);
|
|
89
|
+
const delay = delayAfter(failures.count);
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
lockedUntil: delay ? failures.lastFailureAt + delay : 0,
|
|
93
|
+
};
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./createAttemptLimiter.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forthtilliath/ts-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"./*": "./dist/*.js"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@vitest/coverage-v8": "5.0.
|
|
25
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
26
26
|
"eslint": "10.7.0",
|
|
27
27
|
"jsdom": "^30.1.1",
|
|
28
28
|
"typescript": "^5.9.3",
|
|
29
|
-
"vitest": "^5.0.
|
|
30
|
-
"@forthtilliath/
|
|
31
|
-
"@forthtilliath/
|
|
29
|
+
"vitest": "^5.0.5",
|
|
30
|
+
"@forthtilliath/eslint-config": "0.10.0",
|
|
31
|
+
"@forthtilliath/typescript-config": "0.1.1"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"dev": "tsc --watch -p tsconfig.build.json",
|