@fuzdev/fuz_util 0.42.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 +21 -0
- package/README.md +83 -0
- package/dist/array.d.ts +15 -0
- package/dist/array.d.ts.map +1 -0
- package/dist/array.js +25 -0
- package/dist/async.d.ts +62 -0
- package/dist/async.d.ts.map +1 -0
- package/dist/async.js +147 -0
- package/dist/colors.d.ts +41 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +106 -0
- package/dist/counter.d.ts +7 -0
- package/dist/counter.d.ts.map +1 -0
- package/dist/counter.js +7 -0
- package/dist/deep_equal.d.ts +18 -0
- package/dist/deep_equal.d.ts.map +1 -0
- package/dist/deep_equal.js +152 -0
- package/dist/dom.d.ts +35 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +95 -0
- package/dist/error.d.ts +15 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +18 -0
- package/dist/fetch.d.ts +81 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +162 -0
- package/dist/fs.d.ts +34 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +73 -0
- package/dist/function.d.ts +27 -0
- package/dist/function.d.ts.map +1 -0
- package/dist/function.js +21 -0
- package/dist/git.d.ts +132 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +288 -0
- package/dist/id.d.ts +18 -0
- package/dist/id.d.ts.map +1 -0
- package/dist/id.js +18 -0
- package/dist/iterator.d.ts +5 -0
- package/dist/iterator.d.ts.map +1 -0
- package/dist/iterator.js +9 -0
- package/dist/json.d.ts +30 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +44 -0
- package/dist/library_json.d.ts +42 -0
- package/dist/library_json.d.ts.map +1 -0
- package/dist/library_json.js +76 -0
- package/dist/log.d.ts +188 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +393 -0
- package/dist/map.d.ts +12 -0
- package/dist/map.d.ts.map +1 -0
- package/dist/map.js +14 -0
- package/dist/maths.d.ts +85 -0
- package/dist/maths.d.ts.map +1 -0
- package/dist/maths.js +87 -0
- package/dist/object.d.ts +46 -0
- package/dist/object.d.ts.map +1 -0
- package/dist/object.js +89 -0
- package/dist/package_json.d.ts +90 -0
- package/dist/package_json.d.ts.map +1 -0
- package/dist/package_json.js +112 -0
- package/dist/path.d.ts +63 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +83 -0
- package/dist/print.d.ts +52 -0
- package/dist/print.d.ts.map +1 -0
- package/dist/print.js +89 -0
- package/dist/process.d.ts +77 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +148 -0
- package/dist/random.d.ts +25 -0
- package/dist/random.d.ts.map +1 -0
- package/dist/random.js +35 -0
- package/dist/random_alea.d.ts +23 -0
- package/dist/random_alea.d.ts.map +1 -0
- package/dist/random_alea.js +95 -0
- package/dist/regexp.d.ts +12 -0
- package/dist/regexp.d.ts.map +1 -0
- package/dist/regexp.js +16 -0
- package/dist/result.d.ts +64 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +48 -0
- package/dist/source_json.d.ts +375 -0
- package/dist/source_json.d.ts.map +1 -0
- package/dist/source_json.js +189 -0
- package/dist/string.d.ts +51 -0
- package/dist/string.d.ts.map +1 -0
- package/dist/string.js +92 -0
- package/dist/throttle.d.ts +26 -0
- package/dist/throttle.d.ts.map +1 -0
- package/dist/throttle.js +53 -0
- package/dist/timings.d.ts +33 -0
- package/dist/timings.d.ts.map +1 -0
- package/dist/timings.js +75 -0
- package/dist/types.d.ts +77 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/url.d.ts +10 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +8 -0
- package/package.json +125 -0
- package/src/lib/array.ts +30 -0
- package/src/lib/async.ts +182 -0
- package/src/lib/colors.ts +132 -0
- package/src/lib/counter.ts +11 -0
- package/src/lib/deep_equal.ts +155 -0
- package/src/lib/dom.ts +108 -0
- package/src/lib/error.ts +22 -0
- package/src/lib/fetch.ts +231 -0
- package/src/lib/fs.ts +128 -0
- package/src/lib/function.ts +32 -0
- package/src/lib/git.ts +390 -0
- package/src/lib/id.ts +30 -0
- package/src/lib/iterator.ts +8 -0
- package/src/lib/json.ts +61 -0
- package/src/lib/library_json.ts +122 -0
- package/src/lib/log.ts +469 -0
- package/src/lib/map.ts +18 -0
- package/src/lib/maths.ts +91 -0
- package/src/lib/object.ts +110 -0
- package/src/lib/package_json.ts +135 -0
- package/src/lib/path.ts +137 -0
- package/src/lib/print.ts +111 -0
- package/src/lib/process.ts +207 -0
- package/src/lib/random.ts +48 -0
- package/src/lib/random_alea.ts +107 -0
- package/src/lib/regexp.ts +17 -0
- package/src/lib/result.ts +67 -0
- package/src/lib/source_json.ts +209 -0
- package/src/lib/string.ts +99 -0
- package/src/lib/throttle.ts +70 -0
- package/src/lib/timings.ts +93 -0
- package/src/lib/types.ts +99 -0
- package/src/lib/url.ts +14 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
DO NOT USE when security matters, use webcrypto APIs instead.
|
|
4
|
+
This is the Alea pseudo-random number generator by Johannes Baagøe.
|
|
5
|
+
|
|
6
|
+
From http://baagoe.com/en/RandomMusings/javascript/
|
|
7
|
+
via https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
|
|
8
|
+
|
|
9
|
+
Copyright (C) 2010 by Johannes Baagøe <baagoe@baagoe.org>
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in
|
|
19
|
+
all copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
27
|
+
THE SOFTWARE.
|
|
28
|
+
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export interface Alea {
|
|
32
|
+
(): number;
|
|
33
|
+
uint32: () => number;
|
|
34
|
+
fract53: () => number;
|
|
35
|
+
version: string;
|
|
36
|
+
seeds: Array<unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Seeded pseudo-random number generator.
|
|
41
|
+
* DO NOT USE when security matters, use webcrypto APIs instead.
|
|
42
|
+
*
|
|
43
|
+
* @see http://baagoe.com/en/RandomMusings/javascript/
|
|
44
|
+
* @see https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
|
|
45
|
+
*/
|
|
46
|
+
export const create_random_alea = (...seed: Array<unknown>): Alea => {
|
|
47
|
+
let s0 = 0;
|
|
48
|
+
let s1 = 0;
|
|
49
|
+
let s2 = 0;
|
|
50
|
+
let c = 1;
|
|
51
|
+
|
|
52
|
+
const seeds = seed.length ? seed : [Date.now()];
|
|
53
|
+
let mash: Mash | null = masher();
|
|
54
|
+
s0 = mash(' ');
|
|
55
|
+
s1 = mash(' ');
|
|
56
|
+
s2 = mash(' ');
|
|
57
|
+
|
|
58
|
+
for (const s of seeds) {
|
|
59
|
+
s0 -= mash(s);
|
|
60
|
+
if (s0 < 0) s0 += 1;
|
|
61
|
+
s1 -= mash(s);
|
|
62
|
+
if (s1 < 0) s1 += 1;
|
|
63
|
+
s2 -= mash(s);
|
|
64
|
+
if (s2 < 0) s2 += 1;
|
|
65
|
+
}
|
|
66
|
+
mash = null;
|
|
67
|
+
|
|
68
|
+
const random: Alea = (): number => {
|
|
69
|
+
const t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32
|
|
70
|
+
s0 = s1;
|
|
71
|
+
s1 = s2;
|
|
72
|
+
return (s2 = t - (c = t | 0));
|
|
73
|
+
};
|
|
74
|
+
random.uint32 = (): number => {
|
|
75
|
+
return random() * 0x100000000; // 2^32
|
|
76
|
+
};
|
|
77
|
+
random.fract53 = (): number => {
|
|
78
|
+
return random() + ((random() * 0x200000) | 0) * 1.1102230246251565e-16; // 2^-53
|
|
79
|
+
};
|
|
80
|
+
random.version = 'Alea 0.9';
|
|
81
|
+
random.seeds = seeds;
|
|
82
|
+
return random;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
type Mash = (data: any) => number;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @source http://baagoe.com/en/RandomMusings/javascript/
|
|
89
|
+
* @copyright Johannes Baagøe <baagoe@baagoe.com>, 2010
|
|
90
|
+
*/
|
|
91
|
+
export const masher = (): Mash => {
|
|
92
|
+
let n = 0xefc8249d;
|
|
93
|
+
return (data) => {
|
|
94
|
+
const d = data + '';
|
|
95
|
+
for (let i = 0; i < d.length; i++) {
|
|
96
|
+
n += d.charCodeAt(i);
|
|
97
|
+
let h = 0.02519603282416938 * n;
|
|
98
|
+
n = h >>> 0;
|
|
99
|
+
h -= n;
|
|
100
|
+
h *= n;
|
|
101
|
+
n = h >>> 0;
|
|
102
|
+
h -= n;
|
|
103
|
+
n += h * 0x100000000; // 2^32
|
|
104
|
+
}
|
|
105
|
+
return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
|
|
106
|
+
};
|
|
107
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes a string for literal matching in a RegExp.
|
|
3
|
+
* @source https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/RegularExpressions
|
|
4
|
+
*/
|
|
5
|
+
export const escape_regexp = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Reset a RegExp's lastIndex to 0 for global and sticky patterns.
|
|
9
|
+
* Ensures consistent behavior by clearing state that affects subsequent matches.
|
|
10
|
+
* @mutates regexp sets lastIndex to 0 if regexp is global or sticky
|
|
11
|
+
*/
|
|
12
|
+
export const reset_regexp = <T extends RegExp>(regexp: T): T => {
|
|
13
|
+
if (regexp.global || regexp.sticky) {
|
|
14
|
+
regexp.lastIndex = 0;
|
|
15
|
+
}
|
|
16
|
+
return regexp;
|
|
17
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An alternative pattern to throwing and catching errors.
|
|
3
|
+
* Uses the type system to strongly type error return values when desired.
|
|
4
|
+
* Catching errors is then reserved for unexpected situations.
|
|
5
|
+
*/
|
|
6
|
+
export type Result<TValue = object, TError = object> =
|
|
7
|
+
| ({ok: true} & TValue)
|
|
8
|
+
| ({ok: false} & TError);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Frozen object representing a successful result.
|
|
12
|
+
*/
|
|
13
|
+
export const OK = Object.freeze({ok: true} as const);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Frozen object representing a failed result.
|
|
17
|
+
*/
|
|
18
|
+
export const NOT_OK = Object.freeze({ok: false} as const);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A helper that says,
|
|
22
|
+
* "hey I know this is wrapped in a `Result`, but I expect it to be `ok`,
|
|
23
|
+
* so if it's not, I understand it will throw an error that wraps the result".
|
|
24
|
+
* @param result Some `Result` object.
|
|
25
|
+
* @param message Optional custom error message.
|
|
26
|
+
* @returns The wrapped value.
|
|
27
|
+
*/
|
|
28
|
+
export const unwrap = <TValue extends {value?: unknown}, TError extends {message?: string}>(
|
|
29
|
+
result: Result<TValue, TError>,
|
|
30
|
+
message?: string,
|
|
31
|
+
): TValue['value'] => {
|
|
32
|
+
if (!result.ok) throw new ResultError(result, message);
|
|
33
|
+
return result.value;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A custom error class that's thrown by `unwrap`.
|
|
38
|
+
* Wraps a failed `Result` with an optional message,
|
|
39
|
+
* and also accepts an optional message that overrides the result's.
|
|
40
|
+
* Useful for generic handling of unwrapped results
|
|
41
|
+
* to forward custom messages and other failed result data.
|
|
42
|
+
*/
|
|
43
|
+
export class ResultError extends Error {
|
|
44
|
+
static DEFAULT_MESSAGE = 'unknown error';
|
|
45
|
+
|
|
46
|
+
readonly result: {ok: false; message?: string};
|
|
47
|
+
|
|
48
|
+
constructor(result: {ok: false; message?: string}, message?: string, options?: ErrorOptions) {
|
|
49
|
+
super(message ?? result.message ?? ResultError.DEFAULT_MESSAGE, options);
|
|
50
|
+
this.result = result;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A helper that does the opposite of `unwrap`, throwing if the `Result` is ok.
|
|
56
|
+
* Note that while `unwrap` returns the wrapped `value`, `unwrap_error` returns the entire result.
|
|
57
|
+
* @param result Some `Result` object.
|
|
58
|
+
* @param message Optional custom error message.
|
|
59
|
+
* @returns The type-narrowed result.
|
|
60
|
+
*/
|
|
61
|
+
export const unwrap_error = <TError extends object>(
|
|
62
|
+
result: Result<object, TError>,
|
|
63
|
+
message = 'Failed to unwrap result error',
|
|
64
|
+
): {ok: false} & TError => {
|
|
65
|
+
if (result.ok) throw Error(message);
|
|
66
|
+
return result;
|
|
67
|
+
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata types for library source code analysis.
|
|
3
|
+
*
|
|
4
|
+
* These types represent the structure of `src/lib/` exports,
|
|
5
|
+
* extracted at build time via TypeScript compiler analysis.
|
|
6
|
+
* Used for generating API documentation and enabling code search.
|
|
7
|
+
*
|
|
8
|
+
* Hierarchy: SourceJson → ModuleJson → DeclarationJson
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {z} from 'zod';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The kind of exported declaration.
|
|
15
|
+
*/
|
|
16
|
+
export const DeclarationKind = z.enum([
|
|
17
|
+
'type',
|
|
18
|
+
'function',
|
|
19
|
+
'variable',
|
|
20
|
+
'class',
|
|
21
|
+
'constructor',
|
|
22
|
+
'component',
|
|
23
|
+
'json',
|
|
24
|
+
'css',
|
|
25
|
+
]);
|
|
26
|
+
export type DeclarationKind = z.infer<typeof DeclarationKind>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Generic type parameter information.
|
|
30
|
+
*/
|
|
31
|
+
export const GenericParamInfo = z.looseObject({
|
|
32
|
+
/** Parameter name like `T`. */
|
|
33
|
+
name: z.string(),
|
|
34
|
+
/** Constraint like `string` from `T extends string`. */
|
|
35
|
+
constraint: z.string().optional(),
|
|
36
|
+
/** Default type like `unknown` from `T = unknown`. */
|
|
37
|
+
default_type: z.string().optional(),
|
|
38
|
+
});
|
|
39
|
+
export type GenericParamInfo = z.infer<typeof GenericParamInfo>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Parameter information for functions and methods.
|
|
43
|
+
*
|
|
44
|
+
* Kept distinct from ComponentPropInfo despite structural similarity.
|
|
45
|
+
* Function parameters form a tuple with positional semantics:
|
|
46
|
+
* calling order matters (`fn(a, b)` vs `fn(b, a)`),
|
|
47
|
+
* may include rest parameters and destructuring patterns.
|
|
48
|
+
*/
|
|
49
|
+
export const ParameterInfo = z.looseObject({
|
|
50
|
+
name: z.string(),
|
|
51
|
+
type: z.string(),
|
|
52
|
+
optional: z.boolean().optional(),
|
|
53
|
+
description: z.string().optional(),
|
|
54
|
+
default_value: z.string().optional(),
|
|
55
|
+
});
|
|
56
|
+
export type ParameterInfo = z.infer<typeof ParameterInfo>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Component prop information for Svelte components.
|
|
60
|
+
*
|
|
61
|
+
* Kept distinct from ParameterInfo despite structural similarity.
|
|
62
|
+
* Component props are named attributes with different semantics:
|
|
63
|
+
* no positional order when passing (`<Foo {a} {b} />` = `<Foo {b} {a} />`),
|
|
64
|
+
* support two-way binding via `$bindable` rune.
|
|
65
|
+
*/
|
|
66
|
+
export const ComponentPropInfo = z.looseObject({
|
|
67
|
+
name: z.string(),
|
|
68
|
+
type: z.string(),
|
|
69
|
+
optional: z.boolean().optional(),
|
|
70
|
+
description: z.string().optional(),
|
|
71
|
+
default_value: z.string().optional(),
|
|
72
|
+
bindable: z.boolean().optional(),
|
|
73
|
+
});
|
|
74
|
+
export type ComponentPropInfo = z.infer<typeof ComponentPropInfo>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Metadata for an exported declaration (function, type, class, component, etc.).
|
|
78
|
+
*
|
|
79
|
+
* Extracted from TypeScript source and JSDoc/TSDoc comments at build time.
|
|
80
|
+
*/
|
|
81
|
+
export const DeclarationJson = z.looseObject({
|
|
82
|
+
/** The exported name. */
|
|
83
|
+
name: z.string(),
|
|
84
|
+
kind: DeclarationKind,
|
|
85
|
+
/** JSDoc/TSDoc comment in mdz format. */
|
|
86
|
+
doc_comment: z.string().optional(),
|
|
87
|
+
/** Full TypeScript type signature. */
|
|
88
|
+
type_signature: z.string().optional(),
|
|
89
|
+
/** TypeScript modifiers like `readonly`, `private`, or `static`. */
|
|
90
|
+
modifiers: z.array(z.string()).optional(),
|
|
91
|
+
/** 1-indexed line number in source file. */
|
|
92
|
+
source_line: z.number().optional(),
|
|
93
|
+
/** Function/method parameters. */
|
|
94
|
+
parameters: z.array(ParameterInfo).optional(),
|
|
95
|
+
/** Function/method return type. */
|
|
96
|
+
return_type: z.string().optional(),
|
|
97
|
+
/** Return value description from `@returns` tag. */
|
|
98
|
+
return_description: z.string().optional(),
|
|
99
|
+
/** Generic type parameters like `<T, U>`. */
|
|
100
|
+
generic_params: z.array(GenericParamInfo).optional(),
|
|
101
|
+
/** Code examples from `@example` tags. */
|
|
102
|
+
examples: z.array(z.string()).optional(),
|
|
103
|
+
/** Deprecation message from `@deprecated` tag. */
|
|
104
|
+
deprecated_message: z.string().optional(),
|
|
105
|
+
/** Related items from `@see` tags, in mdz format. */
|
|
106
|
+
see_also: z.array(z.string()).optional(),
|
|
107
|
+
/** Exceptions from `@throws` tags. */
|
|
108
|
+
throws: z.array(z.looseObject({type: z.string().optional(), description: z.string()})).optional(),
|
|
109
|
+
/** Version introduced, from `@since` tag. */
|
|
110
|
+
since: z.string().optional(),
|
|
111
|
+
/** Extended classes/interfaces. */
|
|
112
|
+
extends: z.array(z.string()).optional(),
|
|
113
|
+
/** Implemented interfaces. */
|
|
114
|
+
implements: z.array(z.string()).optional(),
|
|
115
|
+
/** Class or interface members (recursive). */
|
|
116
|
+
get members() {
|
|
117
|
+
return z.array(DeclarationJson).optional();
|
|
118
|
+
},
|
|
119
|
+
/** Type/interface properties (recursive). */
|
|
120
|
+
get properties() {
|
|
121
|
+
return z.array(DeclarationJson).optional();
|
|
122
|
+
},
|
|
123
|
+
/** Svelte component props. */
|
|
124
|
+
props: z.array(ComponentPropInfo).optional(),
|
|
125
|
+
/**
|
|
126
|
+
* Module paths (relative to src/lib) that re-export this declaration.
|
|
127
|
+
* The canonical location is this module's `declarations` array.
|
|
128
|
+
*/
|
|
129
|
+
also_exported_from: z.array(z.string()).optional(),
|
|
130
|
+
/**
|
|
131
|
+
* For renamed re-exports (`export {foo as bar}`), the original declaration.
|
|
132
|
+
*/
|
|
133
|
+
alias_of: z
|
|
134
|
+
.object({
|
|
135
|
+
module: z.string(),
|
|
136
|
+
name: z.string(),
|
|
137
|
+
})
|
|
138
|
+
.optional(),
|
|
139
|
+
});
|
|
140
|
+
export type DeclarationJson = z.infer<typeof DeclarationJson>;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* A source file in `src/lib/` with its exported declarations.
|
|
144
|
+
*/
|
|
145
|
+
export const ModuleJson = z.looseObject({
|
|
146
|
+
/** Path relative to src/lib (e.g., `helpers.ts`). */
|
|
147
|
+
path: z.string(),
|
|
148
|
+
declarations: z.array(DeclarationJson).optional(),
|
|
149
|
+
/** File-level JSDoc comment. */
|
|
150
|
+
module_comment: z.string().optional(),
|
|
151
|
+
/** Modules this imports (paths relative to src/lib). */
|
|
152
|
+
dependencies: z.array(z.string()).optional(),
|
|
153
|
+
/** Modules that import this (paths relative to src/lib). */
|
|
154
|
+
dependents: z.array(z.string()).optional(),
|
|
155
|
+
});
|
|
156
|
+
export type ModuleJson = z.infer<typeof ModuleJson>;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Metadata for a library's `src/lib/` exports.
|
|
160
|
+
*/
|
|
161
|
+
export const SourceJson = z.looseObject({
|
|
162
|
+
name: z.string(),
|
|
163
|
+
version: z.string(),
|
|
164
|
+
modules: z.array(ModuleJson).optional(),
|
|
165
|
+
});
|
|
166
|
+
export type SourceJson = z.infer<typeof SourceJson>;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Format declaration name with generic parameters for display.
|
|
170
|
+
* @example declaration_get_display_name({name: 'Map', kind: 'type', generic_params: [{name: 'K'}, {name: 'V'}]})
|
|
171
|
+
* // => 'Map<K, V>'
|
|
172
|
+
*/
|
|
173
|
+
export const declaration_get_display_name = (declaration: DeclarationJson): string => {
|
|
174
|
+
if (!declaration.generic_params?.length) return declaration.name;
|
|
175
|
+
const params = declaration.generic_params.map((p) => {
|
|
176
|
+
let param = p.name;
|
|
177
|
+
if (p.constraint) param += ` extends ${p.constraint}`;
|
|
178
|
+
if (p.default_type) param += ` = ${p.default_type}`;
|
|
179
|
+
return param;
|
|
180
|
+
});
|
|
181
|
+
return `${declaration.name}<${params.join(', ')}>`;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Generate TypeScript import statement for a declaration.
|
|
186
|
+
* @example declaration_generate_import({name: 'Foo', kind: 'type'}, 'foo.ts', '@pkg/lib')
|
|
187
|
+
* // => "import type {Foo} from '@pkg/lib/foo.js';"
|
|
188
|
+
*/
|
|
189
|
+
export const declaration_generate_import = (
|
|
190
|
+
declaration: DeclarationJson,
|
|
191
|
+
module_path: string,
|
|
192
|
+
library_name: string,
|
|
193
|
+
): string => {
|
|
194
|
+
const js_path = module_path.replace(/\.ts$/, '.js');
|
|
195
|
+
const specifier = `${library_name}/${js_path}`;
|
|
196
|
+
|
|
197
|
+
// Handle default exports by converting module name to PascalCase
|
|
198
|
+
if (declaration.name === 'default') {
|
|
199
|
+
const module_name = module_path.replace(/\.(js|ts|svelte)$/, '');
|
|
200
|
+
const pascal_case = module_name
|
|
201
|
+
.split(/[-_]/)
|
|
202
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
203
|
+
.join('');
|
|
204
|
+
return `import ${pascal_case} from '${specifier}';`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const import_keyword = declaration.kind === 'type' ? 'import type' : 'import';
|
|
208
|
+
return `${import_keyword} {${declaration.name}} from '${specifier}';`;
|
|
209
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {count_iterator} from './iterator.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Truncates a string to a maximum length, adding a suffix if needed that defaults to `...`.
|
|
5
|
+
*/
|
|
6
|
+
export const truncate = (str: string, maxLength: number, suffix = '...'): string => {
|
|
7
|
+
if (maxLength < suffix.length) return '';
|
|
8
|
+
if (str.length > maxLength) {
|
|
9
|
+
return str.substring(0, maxLength - suffix.length) + suffix;
|
|
10
|
+
}
|
|
11
|
+
return str;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Removes characters inclusive of `stripped`.
|
|
16
|
+
*/
|
|
17
|
+
export const strip_start = (source: string, stripped: string): string => {
|
|
18
|
+
if (!stripped || !source.startsWith(stripped)) return source;
|
|
19
|
+
return source.substring(stripped.length);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Removes characters inclusive of `stripped`.
|
|
24
|
+
*/
|
|
25
|
+
export const strip_end = (source: string, stripped: string): string => {
|
|
26
|
+
if (!stripped || !source.endsWith(stripped)) return source;
|
|
27
|
+
return source.substring(0, source.length - stripped.length);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Removes characters inclusive of `stripped`.
|
|
32
|
+
*/
|
|
33
|
+
export const strip_after = (source: string, stripped: string): string => {
|
|
34
|
+
if (!stripped) return source;
|
|
35
|
+
const idx = source.indexOf(stripped);
|
|
36
|
+
if (idx === -1) return source;
|
|
37
|
+
return source.substring(0, idx);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Removes characters inclusive of `stripped`.
|
|
42
|
+
*/
|
|
43
|
+
export const strip_before = (source: string, stripped: string): string => {
|
|
44
|
+
if (!stripped) return source;
|
|
45
|
+
const idx = source.indexOf(stripped);
|
|
46
|
+
if (idx === -1) return source;
|
|
47
|
+
return source.substring(idx + stripped.length);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adds the substring `ensured` to the start of the `source` string if it's not already present.
|
|
52
|
+
*/
|
|
53
|
+
export const ensure_start = (source: string, ensured: string): string => {
|
|
54
|
+
if (source.startsWith(ensured)) return source;
|
|
55
|
+
return ensured + source;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Adds the substring `ensured` to the end of the `source` string if it's not already present.
|
|
60
|
+
*/
|
|
61
|
+
export const ensure_end = (source: string, ensured: string): string => {
|
|
62
|
+
if (source.endsWith(ensured)) return source;
|
|
63
|
+
return source + ensured;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Removes leading and trailing spaces from each line of a string.
|
|
68
|
+
*/
|
|
69
|
+
export const deindent = (str: string): string =>
|
|
70
|
+
str
|
|
71
|
+
.split('\n')
|
|
72
|
+
.filter(Boolean)
|
|
73
|
+
.map((s) => s.trim())
|
|
74
|
+
.join('\n');
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Returns a plural suffix based on a count.
|
|
78
|
+
*/
|
|
79
|
+
export const plural = (count: number | undefined | null, suffix = 's'): string =>
|
|
80
|
+
count === 1 ? '' : suffix;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Returns the count of graphemes in a string, the individually rendered characters.
|
|
84
|
+
*/
|
|
85
|
+
export const count_graphemes = (str: string): number =>
|
|
86
|
+
count_iterator(new Intl.Segmenter().segment(str));
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Strips ANSI escape sequences from a string
|
|
90
|
+
*/
|
|
91
|
+
export const strip_ansi = (str: string): string => str.replaceAll(/\x1B\[[0-9;]*[a-zA-Z]/g, ''); // eslint-disable-line no-control-regex
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Stringifies a value like `JSON.stringify` but with some corner cases handled better.
|
|
95
|
+
*
|
|
96
|
+
* @source https://2ality.com/2025/04/stringification-javascript.html
|
|
97
|
+
*/
|
|
98
|
+
export const stringify = (value: unknown): string =>
|
|
99
|
+
typeof value === 'bigint' ? value + 'n' : (JSON.stringify(value) ?? String(value)); // eslint-disable-line @typescript-eslint/no-unnecessary-condition
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {create_deferred, type Deferred} from './async.js';
|
|
2
|
+
import {EMPTY_OBJECT} from './object.js';
|
|
3
|
+
|
|
4
|
+
export interface ThrottleOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Enforced milliseconds between calls. For `when='trailing'` this is the debounce delay.
|
|
7
|
+
*/
|
|
8
|
+
delay?: number;
|
|
9
|
+
/**
|
|
10
|
+
* When to call the throttled function. Defaults to `both`.
|
|
11
|
+
*/
|
|
12
|
+
when?: 'both' | 'leading' | 'trailing';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Throttles calls to a callback that returns a void promise.
|
|
17
|
+
* Immediately invokes the callback on the first call unless `leading=false`.
|
|
18
|
+
* If the throttled function is called while the promise is already pending,
|
|
19
|
+
* the call is queued to run after the pending promise completes plus `delay`,
|
|
20
|
+
* and only the last call is invoked.
|
|
21
|
+
* In other words, all calls and their args are discarded
|
|
22
|
+
* during the pending window except for the most recent.
|
|
23
|
+
* Unlike debouncing, this calls the throttled callback
|
|
24
|
+
* both on the leading and trailing edges of the delay window by default,
|
|
25
|
+
* and this can be customized by setting `leading` or `trailing.
|
|
26
|
+
* It also differs from a queue where every call to the throttled callback eventually runs.
|
|
27
|
+
* @returns same as `cb`
|
|
28
|
+
*/
|
|
29
|
+
export const throttle = <T extends (...args: Array<any>) => Promise<void>>(
|
|
30
|
+
cb: T,
|
|
31
|
+
{delay = 0, when = 'both'}: ThrottleOptions = EMPTY_OBJECT,
|
|
32
|
+
): T => {
|
|
33
|
+
let pending_promise: Promise<void> | null = null;
|
|
34
|
+
let next_args: Array<any> | null = null;
|
|
35
|
+
let next_deferred: Deferred<void> | null = null;
|
|
36
|
+
|
|
37
|
+
const defer = (args: Array<any>): Promise<void> => {
|
|
38
|
+
next_args = args;
|
|
39
|
+
if (!next_deferred) {
|
|
40
|
+
next_deferred = create_deferred<void>();
|
|
41
|
+
setTimeout(flush, delay);
|
|
42
|
+
}
|
|
43
|
+
return next_deferred.promise;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const flush = async (): Promise<void> => {
|
|
47
|
+
if (!next_deferred) return;
|
|
48
|
+
const result = await call(next_args!);
|
|
49
|
+
next_args = null;
|
|
50
|
+
const {resolve} = next_deferred;
|
|
51
|
+
next_deferred = null;
|
|
52
|
+
resolve(result); // resolve last to prevent synchronous call issues
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const call = (args: Array<any>): Promise<any> => {
|
|
56
|
+
pending_promise = cb(...args);
|
|
57
|
+
void pending_promise.finally(() => {
|
|
58
|
+
pending_promise = null;
|
|
59
|
+
});
|
|
60
|
+
return pending_promise;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return ((...args) => {
|
|
64
|
+
if (pending_promise || when === 'trailing') {
|
|
65
|
+
return when === 'leading' ? pending_promise : defer(args); // discarded when pending and not trailing
|
|
66
|
+
} else {
|
|
67
|
+
return call(args);
|
|
68
|
+
}
|
|
69
|
+
}) as T;
|
|
70
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {round} from './maths.js';
|
|
2
|
+
|
|
3
|
+
export type Stopwatch = (reset?: boolean) => number;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tracks elapsed time in milliseconds.
|
|
7
|
+
*/
|
|
8
|
+
export const create_stopwatch = (decimals = 2): Stopwatch => {
|
|
9
|
+
let start = performance.now();
|
|
10
|
+
return (reset = false) => {
|
|
11
|
+
const end = performance.now();
|
|
12
|
+
const elapsed = round(Number(end - start), decimals); // eslint-disable-line @typescript-eslint/no-unnecessary-type-conversion
|
|
13
|
+
if (reset) start = end;
|
|
14
|
+
return elapsed;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type TimingsKey = string | number;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Tracks and manages multiple timing operations.
|
|
22
|
+
* Allows starting, stopping, and retrieving timings with optional precision.
|
|
23
|
+
*/
|
|
24
|
+
export class Timings {
|
|
25
|
+
readonly decimals: number | undefined;
|
|
26
|
+
|
|
27
|
+
private readonly timings: Map<TimingsKey, number | undefined> = new Map();
|
|
28
|
+
private readonly stopwatches: Map<TimingsKey, Stopwatch> = new Map();
|
|
29
|
+
|
|
30
|
+
constructor(decimals?: number) {
|
|
31
|
+
this.decimals = decimals;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Starts a timing operation for the given key.
|
|
36
|
+
*/
|
|
37
|
+
start(key: TimingsKey, decimals = this.decimals): () => number {
|
|
38
|
+
const final_key = this.next_key(key);
|
|
39
|
+
this.stopwatches.set(final_key, create_stopwatch(decimals));
|
|
40
|
+
this.timings.set(final_key, undefined); // initializing to preserve order
|
|
41
|
+
return () => this.stop(final_key);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private next_key(key: TimingsKey): TimingsKey {
|
|
45
|
+
if (!this.stopwatches.has(key)) return key;
|
|
46
|
+
let i = 2;
|
|
47
|
+
while (true) {
|
|
48
|
+
const next = key + '_' + i++;
|
|
49
|
+
if (!this.stopwatches.has(next)) return next;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Stops a timing operation and records the elapsed time.
|
|
55
|
+
*/
|
|
56
|
+
private stop(key: TimingsKey): number {
|
|
57
|
+
const stopwatch = this.stopwatches.get(key);
|
|
58
|
+
if (!stopwatch) return 0; // TODO maybe warn?
|
|
59
|
+
this.stopwatches.delete(key);
|
|
60
|
+
const timing = stopwatch();
|
|
61
|
+
this.timings.set(key, timing);
|
|
62
|
+
return timing;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get(key: TimingsKey): number {
|
|
66
|
+
const timing = this.timings.get(key);
|
|
67
|
+
if (timing === undefined) return 0; // TODO maybe warn?
|
|
68
|
+
return timing;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
entries(): IterableIterator<[TimingsKey, number | undefined]> {
|
|
72
|
+
return this.timings.entries();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Merges other timings into this one,
|
|
77
|
+
* adding together values with identical keys.
|
|
78
|
+
*/
|
|
79
|
+
merge(timings: Timings): void {
|
|
80
|
+
for (const [key, timing] of timings.entries()) {
|
|
81
|
+
this.timings.set(
|
|
82
|
+
key,
|
|
83
|
+
timing === undefined ? undefined : (this.timings.get(key) ?? 0) + timing,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// clear(): void {
|
|
89
|
+
// this.stopwatches.clear();
|
|
90
|
+
// this.timings.clear();
|
|
91
|
+
// }
|
|
92
|
+
// toJSON() {} ?????
|
|
93
|
+
}
|