@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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/array.d.ts +15 -0
  4. package/dist/array.d.ts.map +1 -0
  5. package/dist/array.js +25 -0
  6. package/dist/async.d.ts +62 -0
  7. package/dist/async.d.ts.map +1 -0
  8. package/dist/async.js +147 -0
  9. package/dist/colors.d.ts +41 -0
  10. package/dist/colors.d.ts.map +1 -0
  11. package/dist/colors.js +106 -0
  12. package/dist/counter.d.ts +7 -0
  13. package/dist/counter.d.ts.map +1 -0
  14. package/dist/counter.js +7 -0
  15. package/dist/deep_equal.d.ts +18 -0
  16. package/dist/deep_equal.d.ts.map +1 -0
  17. package/dist/deep_equal.js +152 -0
  18. package/dist/dom.d.ts +35 -0
  19. package/dist/dom.d.ts.map +1 -0
  20. package/dist/dom.js +95 -0
  21. package/dist/error.d.ts +15 -0
  22. package/dist/error.d.ts.map +1 -0
  23. package/dist/error.js +18 -0
  24. package/dist/fetch.d.ts +81 -0
  25. package/dist/fetch.d.ts.map +1 -0
  26. package/dist/fetch.js +162 -0
  27. package/dist/fs.d.ts +34 -0
  28. package/dist/fs.d.ts.map +1 -0
  29. package/dist/fs.js +73 -0
  30. package/dist/function.d.ts +27 -0
  31. package/dist/function.d.ts.map +1 -0
  32. package/dist/function.js +21 -0
  33. package/dist/git.d.ts +132 -0
  34. package/dist/git.d.ts.map +1 -0
  35. package/dist/git.js +288 -0
  36. package/dist/id.d.ts +18 -0
  37. package/dist/id.d.ts.map +1 -0
  38. package/dist/id.js +18 -0
  39. package/dist/iterator.d.ts +5 -0
  40. package/dist/iterator.d.ts.map +1 -0
  41. package/dist/iterator.js +9 -0
  42. package/dist/json.d.ts +30 -0
  43. package/dist/json.d.ts.map +1 -0
  44. package/dist/json.js +44 -0
  45. package/dist/library_json.d.ts +42 -0
  46. package/dist/library_json.d.ts.map +1 -0
  47. package/dist/library_json.js +76 -0
  48. package/dist/log.d.ts +188 -0
  49. package/dist/log.d.ts.map +1 -0
  50. package/dist/log.js +393 -0
  51. package/dist/map.d.ts +12 -0
  52. package/dist/map.d.ts.map +1 -0
  53. package/dist/map.js +14 -0
  54. package/dist/maths.d.ts +85 -0
  55. package/dist/maths.d.ts.map +1 -0
  56. package/dist/maths.js +87 -0
  57. package/dist/object.d.ts +46 -0
  58. package/dist/object.d.ts.map +1 -0
  59. package/dist/object.js +89 -0
  60. package/dist/package_json.d.ts +90 -0
  61. package/dist/package_json.d.ts.map +1 -0
  62. package/dist/package_json.js +112 -0
  63. package/dist/path.d.ts +63 -0
  64. package/dist/path.d.ts.map +1 -0
  65. package/dist/path.js +83 -0
  66. package/dist/print.d.ts +52 -0
  67. package/dist/print.d.ts.map +1 -0
  68. package/dist/print.js +89 -0
  69. package/dist/process.d.ts +77 -0
  70. package/dist/process.d.ts.map +1 -0
  71. package/dist/process.js +148 -0
  72. package/dist/random.d.ts +25 -0
  73. package/dist/random.d.ts.map +1 -0
  74. package/dist/random.js +35 -0
  75. package/dist/random_alea.d.ts +23 -0
  76. package/dist/random_alea.d.ts.map +1 -0
  77. package/dist/random_alea.js +95 -0
  78. package/dist/regexp.d.ts +12 -0
  79. package/dist/regexp.d.ts.map +1 -0
  80. package/dist/regexp.js +16 -0
  81. package/dist/result.d.ts +64 -0
  82. package/dist/result.d.ts.map +1 -0
  83. package/dist/result.js +48 -0
  84. package/dist/source_json.d.ts +375 -0
  85. package/dist/source_json.d.ts.map +1 -0
  86. package/dist/source_json.js +189 -0
  87. package/dist/string.d.ts +51 -0
  88. package/dist/string.d.ts.map +1 -0
  89. package/dist/string.js +92 -0
  90. package/dist/throttle.d.ts +26 -0
  91. package/dist/throttle.d.ts.map +1 -0
  92. package/dist/throttle.js +53 -0
  93. package/dist/timings.d.ts +33 -0
  94. package/dist/timings.d.ts.map +1 -0
  95. package/dist/timings.js +75 -0
  96. package/dist/types.d.ts +77 -0
  97. package/dist/types.d.ts.map +1 -0
  98. package/dist/types.js +10 -0
  99. package/dist/url.d.ts +10 -0
  100. package/dist/url.d.ts.map +1 -0
  101. package/dist/url.js +8 -0
  102. package/package.json +125 -0
  103. package/src/lib/array.ts +30 -0
  104. package/src/lib/async.ts +182 -0
  105. package/src/lib/colors.ts +132 -0
  106. package/src/lib/counter.ts +11 -0
  107. package/src/lib/deep_equal.ts +155 -0
  108. package/src/lib/dom.ts +108 -0
  109. package/src/lib/error.ts +22 -0
  110. package/src/lib/fetch.ts +231 -0
  111. package/src/lib/fs.ts +128 -0
  112. package/src/lib/function.ts +32 -0
  113. package/src/lib/git.ts +390 -0
  114. package/src/lib/id.ts +30 -0
  115. package/src/lib/iterator.ts +8 -0
  116. package/src/lib/json.ts +61 -0
  117. package/src/lib/library_json.ts +122 -0
  118. package/src/lib/log.ts +469 -0
  119. package/src/lib/map.ts +18 -0
  120. package/src/lib/maths.ts +91 -0
  121. package/src/lib/object.ts +110 -0
  122. package/src/lib/package_json.ts +135 -0
  123. package/src/lib/path.ts +137 -0
  124. package/src/lib/print.ts +111 -0
  125. package/src/lib/process.ts +207 -0
  126. package/src/lib/random.ts +48 -0
  127. package/src/lib/random_alea.ts +107 -0
  128. package/src/lib/regexp.ts +17 -0
  129. package/src/lib/result.ts +67 -0
  130. package/src/lib/source_json.ts +209 -0
  131. package/src/lib/string.ts +99 -0
  132. package/src/lib/throttle.ts +70 -0
  133. package/src/lib/timings.ts +93 -0
  134. package/src/lib/types.ts +99 -0
  135. package/src/lib/url.ts +14 -0
package/dist/string.js ADDED
@@ -0,0 +1,92 @@
1
+ import { count_iterator } from './iterator.js';
2
+ /**
3
+ * Truncates a string to a maximum length, adding a suffix if needed that defaults to `...`.
4
+ */
5
+ export const truncate = (str, maxLength, suffix = '...') => {
6
+ if (maxLength < suffix.length)
7
+ return '';
8
+ if (str.length > maxLength) {
9
+ return str.substring(0, maxLength - suffix.length) + suffix;
10
+ }
11
+ return str;
12
+ };
13
+ /**
14
+ * Removes characters inclusive of `stripped`.
15
+ */
16
+ export const strip_start = (source, stripped) => {
17
+ if (!stripped || !source.startsWith(stripped))
18
+ return source;
19
+ return source.substring(stripped.length);
20
+ };
21
+ /**
22
+ * Removes characters inclusive of `stripped`.
23
+ */
24
+ export const strip_end = (source, stripped) => {
25
+ if (!stripped || !source.endsWith(stripped))
26
+ return source;
27
+ return source.substring(0, source.length - stripped.length);
28
+ };
29
+ /**
30
+ * Removes characters inclusive of `stripped`.
31
+ */
32
+ export const strip_after = (source, stripped) => {
33
+ if (!stripped)
34
+ return source;
35
+ const idx = source.indexOf(stripped);
36
+ if (idx === -1)
37
+ return source;
38
+ return source.substring(0, idx);
39
+ };
40
+ /**
41
+ * Removes characters inclusive of `stripped`.
42
+ */
43
+ export const strip_before = (source, stripped) => {
44
+ if (!stripped)
45
+ return source;
46
+ const idx = source.indexOf(stripped);
47
+ if (idx === -1)
48
+ return source;
49
+ return source.substring(idx + stripped.length);
50
+ };
51
+ /**
52
+ * Adds the substring `ensured` to the start of the `source` string if it's not already present.
53
+ */
54
+ export const ensure_start = (source, ensured) => {
55
+ if (source.startsWith(ensured))
56
+ return source;
57
+ return ensured + source;
58
+ };
59
+ /**
60
+ * Adds the substring `ensured` to the end of the `source` string if it's not already present.
61
+ */
62
+ export const ensure_end = (source, ensured) => {
63
+ if (source.endsWith(ensured))
64
+ return source;
65
+ return source + ensured;
66
+ };
67
+ /**
68
+ * Removes leading and trailing spaces from each line of a string.
69
+ */
70
+ export const deindent = (str) => str
71
+ .split('\n')
72
+ .filter(Boolean)
73
+ .map((s) => s.trim())
74
+ .join('\n');
75
+ /**
76
+ * Returns a plural suffix based on a count.
77
+ */
78
+ export const plural = (count, suffix = 's') => count === 1 ? '' : suffix;
79
+ /**
80
+ * Returns the count of graphemes in a string, the individually rendered characters.
81
+ */
82
+ export const count_graphemes = (str) => count_iterator(new Intl.Segmenter().segment(str));
83
+ /**
84
+ * Strips ANSI escape sequences from a string
85
+ */
86
+ export const strip_ansi = (str) => str.replaceAll(/\x1B\[[0-9;]*[a-zA-Z]/g, ''); // eslint-disable-line no-control-regex
87
+ /**
88
+ * Stringifies a value like `JSON.stringify` but with some corner cases handled better.
89
+ *
90
+ * @source https://2ality.com/2025/04/stringification-javascript.html
91
+ */
92
+ export const stringify = (value) => typeof value === 'bigint' ? value + 'n' : (JSON.stringify(value) ?? String(value)); // eslint-disable-line @typescript-eslint/no-unnecessary-condition
@@ -0,0 +1,26 @@
1
+ export interface ThrottleOptions {
2
+ /**
3
+ * Enforced milliseconds between calls. For `when='trailing'` this is the debounce delay.
4
+ */
5
+ delay?: number;
6
+ /**
7
+ * When to call the throttled function. Defaults to `both`.
8
+ */
9
+ when?: 'both' | 'leading' | 'trailing';
10
+ }
11
+ /**
12
+ * Throttles calls to a callback that returns a void promise.
13
+ * Immediately invokes the callback on the first call unless `leading=false`.
14
+ * If the throttled function is called while the promise is already pending,
15
+ * the call is queued to run after the pending promise completes plus `delay`,
16
+ * and only the last call is invoked.
17
+ * In other words, all calls and their args are discarded
18
+ * during the pending window except for the most recent.
19
+ * Unlike debouncing, this calls the throttled callback
20
+ * both on the leading and trailing edges of the delay window by default,
21
+ * and this can be customized by setting `leading` or `trailing.
22
+ * It also differs from a queue where every call to the throttled callback eventually runs.
23
+ * @returns same as `cb`
24
+ */
25
+ export declare const throttle: <T extends (...args: Array<any>) => Promise<void>>(cb: T, { delay, when }?: ThrottleOptions) => T;
26
+ //# sourceMappingURL=throttle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/throttle.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC/B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;CACvC;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,EACxE,IAAI,CAAC,EACL,kBAA4B,eAA8B,KACxD,CAsCF,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { create_deferred } from './async.js';
2
+ import { EMPTY_OBJECT } from './object.js';
3
+ /**
4
+ * Throttles calls to a callback that returns a void promise.
5
+ * Immediately invokes the callback on the first call unless `leading=false`.
6
+ * If the throttled function is called while the promise is already pending,
7
+ * the call is queued to run after the pending promise completes plus `delay`,
8
+ * and only the last call is invoked.
9
+ * In other words, all calls and their args are discarded
10
+ * during the pending window except for the most recent.
11
+ * Unlike debouncing, this calls the throttled callback
12
+ * both on the leading and trailing edges of the delay window by default,
13
+ * and this can be customized by setting `leading` or `trailing.
14
+ * It also differs from a queue where every call to the throttled callback eventually runs.
15
+ * @returns same as `cb`
16
+ */
17
+ export const throttle = (cb, { delay = 0, when = 'both' } = EMPTY_OBJECT) => {
18
+ let pending_promise = null;
19
+ let next_args = null;
20
+ let next_deferred = null;
21
+ const defer = (args) => {
22
+ next_args = args;
23
+ if (!next_deferred) {
24
+ next_deferred = create_deferred();
25
+ setTimeout(flush, delay);
26
+ }
27
+ return next_deferred.promise;
28
+ };
29
+ const flush = async () => {
30
+ if (!next_deferred)
31
+ return;
32
+ const result = await call(next_args);
33
+ next_args = null;
34
+ const { resolve } = next_deferred;
35
+ next_deferred = null;
36
+ resolve(result); // resolve last to prevent synchronous call issues
37
+ };
38
+ const call = (args) => {
39
+ pending_promise = cb(...args);
40
+ void pending_promise.finally(() => {
41
+ pending_promise = null;
42
+ });
43
+ return pending_promise;
44
+ };
45
+ return ((...args) => {
46
+ if (pending_promise || when === 'trailing') {
47
+ return when === 'leading' ? pending_promise : defer(args); // discarded when pending and not trailing
48
+ }
49
+ else {
50
+ return call(args);
51
+ }
52
+ });
53
+ };
@@ -0,0 +1,33 @@
1
+ export type Stopwatch = (reset?: boolean) => number;
2
+ /**
3
+ * Tracks elapsed time in milliseconds.
4
+ */
5
+ export declare const create_stopwatch: (decimals?: number) => Stopwatch;
6
+ export type TimingsKey = string | number;
7
+ /**
8
+ * Tracks and manages multiple timing operations.
9
+ * Allows starting, stopping, and retrieving timings with optional precision.
10
+ */
11
+ export declare class Timings {
12
+ readonly decimals: number | undefined;
13
+ private readonly timings;
14
+ private readonly stopwatches;
15
+ constructor(decimals?: number);
16
+ /**
17
+ * Starts a timing operation for the given key.
18
+ */
19
+ start(key: TimingsKey, decimals?: number | undefined): () => number;
20
+ private next_key;
21
+ /**
22
+ * Stops a timing operation and records the elapsed time.
23
+ */
24
+ private stop;
25
+ get(key: TimingsKey): number;
26
+ entries(): IterableIterator<[TimingsKey, number | undefined]>;
27
+ /**
28
+ * Merges other timings into this one,
29
+ * adding together values with identical keys.
30
+ */
31
+ merge(timings: Timings): void;
32
+ }
33
+ //# sourceMappingURL=timings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timings.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/timings.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC;AAEpD;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,iBAAY,KAAG,SAQ/C,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzC;;;GAGG;AACH,qBAAa,OAAO;IACnB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAEtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkD;IAC1E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyC;gBAEzD,QAAQ,CAAC,EAAE,MAAM;IAI7B;;OAEG;IACH,KAAK,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,qBAAgB,GAAG,MAAM,MAAM;IAO9D,OAAO,CAAC,QAAQ;IAShB;;OAEG;IACH,OAAO,CAAC,IAAI;IASZ,GAAG,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM;IAM5B,OAAO,IAAI,gBAAgB,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAI7D;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;CAc7B"}
@@ -0,0 +1,75 @@
1
+ import { round } from './maths.js';
2
+ /**
3
+ * Tracks elapsed time in milliseconds.
4
+ */
5
+ export const create_stopwatch = (decimals = 2) => {
6
+ let start = performance.now();
7
+ return (reset = false) => {
8
+ const end = performance.now();
9
+ const elapsed = round(Number(end - start), decimals); // eslint-disable-line @typescript-eslint/no-unnecessary-type-conversion
10
+ if (reset)
11
+ start = end;
12
+ return elapsed;
13
+ };
14
+ };
15
+ /**
16
+ * Tracks and manages multiple timing operations.
17
+ * Allows starting, stopping, and retrieving timings with optional precision.
18
+ */
19
+ export class Timings {
20
+ decimals;
21
+ timings = new Map();
22
+ stopwatches = new Map();
23
+ constructor(decimals) {
24
+ this.decimals = decimals;
25
+ }
26
+ /**
27
+ * Starts a timing operation for the given key.
28
+ */
29
+ start(key, decimals = this.decimals) {
30
+ const final_key = this.next_key(key);
31
+ this.stopwatches.set(final_key, create_stopwatch(decimals));
32
+ this.timings.set(final_key, undefined); // initializing to preserve order
33
+ return () => this.stop(final_key);
34
+ }
35
+ next_key(key) {
36
+ if (!this.stopwatches.has(key))
37
+ return key;
38
+ let i = 2;
39
+ while (true) {
40
+ const next = key + '_' + i++;
41
+ if (!this.stopwatches.has(next))
42
+ return next;
43
+ }
44
+ }
45
+ /**
46
+ * Stops a timing operation and records the elapsed time.
47
+ */
48
+ stop(key) {
49
+ const stopwatch = this.stopwatches.get(key);
50
+ if (!stopwatch)
51
+ return 0; // TODO maybe warn?
52
+ this.stopwatches.delete(key);
53
+ const timing = stopwatch();
54
+ this.timings.set(key, timing);
55
+ return timing;
56
+ }
57
+ get(key) {
58
+ const timing = this.timings.get(key);
59
+ if (timing === undefined)
60
+ return 0; // TODO maybe warn?
61
+ return timing;
62
+ }
63
+ entries() {
64
+ return this.timings.entries();
65
+ }
66
+ /**
67
+ * Merges other timings into this one,
68
+ * adding together values with identical keys.
69
+ */
70
+ merge(timings) {
71
+ for (const [key, timing] of timings.entries()) {
72
+ this.timings.set(key, timing === undefined ? undefined : (this.timings.get(key) ?? 0) + timing);
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,77 @@
1
+ export type ClassConstructor<TInstance, TArgs extends Array<any> = Array<any>> = new (...args: TArgs) => TInstance;
2
+ export type OmitStrict<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
3
+ /**
4
+ * Like `Pick` but works for type unions.
5
+ *
6
+ * @see https://stackoverflow.com/questions/75271774/creating-a-type-using-pick-with-a-type-that-is-defined-as-a-union-of-types
7
+ * @see https://stackoverflow.com/users/5770132/oblosys
8
+ */
9
+ export type PickUnion<T, K extends KeyofUnion<T>> = T extends unknown ? K & keyof T extends never ? never : Pick<T, K & keyof T> : never;
10
+ /**
11
+ * Like `keyof` but works for type unions.
12
+ *
13
+ * @see https://stackoverflow.com/questions/75271774/creating-a-type-using-pick-with-a-type-that-is-defined-as-a-union-of-types
14
+ * @see https://stackoverflow.com/users/5770132/oblosys
15
+ */
16
+ export type KeyofUnion<T> = T extends unknown ? keyof T : never;
17
+ export type PartialExcept<T, K extends keyof T> = {
18
+ [P in K]: T[P];
19
+ } & {
20
+ [P in Exclude<keyof T, K>]?: T[P];
21
+ };
22
+ export type PartialOnly<T, K extends keyof T> = {
23
+ [P in K]?: T[P];
24
+ } & {
25
+ [P in Exclude<keyof T, K>]: T[P];
26
+ };
27
+ export type PartialValues<T> = {
28
+ [P in keyof T]: Partial<T[P]>;
29
+ };
30
+ export type Assignable<T, K extends keyof T = keyof T> = {
31
+ -readonly [P in K]: T[P];
32
+ };
33
+ export type Defined<T> = T extends undefined ? never : T;
34
+ export type NotNull<T> = T extends null ? never : T;
35
+ export type ArrayElement<T> = T extends ReadonlyArray<infer U> ? U : never;
36
+ /**
37
+ * The `Flavored` and `Branded` type helpers add varying degrees of nominal typing to other types.
38
+ * This is especially useful with primitives like strings and numbers.
39
+ *
40
+ * @see https://spin.atomicobject.com/typescript-flexible-nominal-typing/
41
+ *
42
+ * `Flavored` is a looser form of `Branded` that trades
43
+ * explicitness and a little safety in some cases for ergonomics.
44
+ * With `Flavored` you don't need to cast unflavored types:
45
+ *
46
+ * ```ts
47
+ * type Email = Flavored<string, 'Email'>;
48
+ * const email1: Email = 'foo'; // ok
49
+ * type Address = Flavored<string, 'Address'>;
50
+ * const email2: Email = 'foo' as Address; // error!
51
+ * ```
52
+ *
53
+ * `Branded` requires casting:
54
+ *
55
+ * ```ts
56
+ * type PhoneNumber = Branded<string, 'PhoneNumber'>;
57
+ * const phone1: PhoneNumber = 'foo'; // error!
58
+ * const phone2: PhoneNumber = 'foo' as PhoneNumber; // ok
59
+ * ```
60
+ *
61
+ * See also Zod's `.brand` schema helper:
62
+ *
63
+ * @see https://github.com/colinhacks/zod#brand
64
+ *
65
+ */
66
+ export type Flavored<TValue, TName> = TValue & Flavor<TName>;
67
+ declare const FlavoredSymbol: unique symbol;
68
+ export interface Flavor<T> {
69
+ readonly [FlavoredSymbol]?: T;
70
+ }
71
+ export type Branded<TValue, TName> = TValue & Brand<TName>;
72
+ declare const BrandedSymbol: unique symbol;
73
+ export interface Brand<T> {
74
+ readonly [BrandedSymbol]: T;
75
+ }
76
+ export {};
77
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/types.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,gBAAgB,CAAC,SAAS,EAAE,KAAK,SAAS,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,KAChF,GAAG,IAAI,EAAE,KAAK,KACV,SAAS,CAAC;AAEf,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,OAAO,GAClE,CAAC,GAAG,MAAM,CAAC,SAAS,KAAK,GACxB,KAAK,GACL,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,GACrB,KAAK,CAAC;AAET;;;;;GAKG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,SAAS,OAAO,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC;AAKhE,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI;KAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAC,GAAG;KACnE,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;CACjC,CAAC;AACF,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI;KAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;CAAC,GAAG;KAClE,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;KAC7B,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI;IACxD,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,SAAS,GAAG,KAAK,GAAG,CAAC,CAAC;AACzD,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC;AAEpD,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,SAAS,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;AAC7D,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAC5C,MAAM,WAAW,MAAM,CAAC,CAAC;IACxB,QAAQ,CAAC,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;CAC9B;AACD,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,KAAK,IAAI,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;AAC3D,OAAO,CAAC,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC;AAC3C,MAAM,WAAW,KAAK,CAAC,CAAC;IACvB,QAAQ,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;CAC5B"}
package/dist/types.js ADDED
@@ -0,0 +1,10 @@
1
+ /*
2
+
3
+ These are convenient global types that can be used in both Felt and user code.
4
+ It probably makes more sense to give this file a `.d.ts` extension,
5
+ but that complicates the build because TypeScript does not output them.
6
+
7
+ TODO probably make this `.d.ts` when we make a proper build process
8
+
9
+ */
10
+ export {};
package/dist/url.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+ import type { Flavored } from './types.js';
3
+ /**
4
+ * Formats a URL by removing 'https://', 'www.', and trailing slashes.
5
+ * Notably it does not remove 'http://', so the user can see that it's insecure.
6
+ */
7
+ export declare const format_url: (url: string) => string;
8
+ export declare const Url: z.ZodURL;
9
+ export type Url = Flavored<z.infer<typeof Url>, 'Url'>;
10
+ //# sourceMappingURL=url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAC;AAEzC;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MACyB,CAAC;AAEnE,eAAO,MAAM,GAAG,UAAU,CAAC;AAC3B,MAAM,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC"}
package/dist/url.js ADDED
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ import { strip_end, strip_start } from './string.js';
3
+ /**
4
+ * Formats a URL by removing 'https://', 'www.', and trailing slashes.
5
+ * Notably it does not remove 'http://', so the user can see that it's insecure.
6
+ */
7
+ export const format_url = (url) => strip_end(strip_start(strip_start(url, 'https://'), 'www.'), '/');
8
+ export const Url = z.url();
package/package.json ADDED
@@ -0,0 +1,125 @@
1
+ {
2
+ "name": "@fuzdev/fuz_util",
3
+ "version": "0.42.0",
4
+ "description": "utility belt for JS",
5
+ "glyph": "🦕",
6
+ "logo": "logo.svg",
7
+ "logo_alt": "a green sauropod wearing a brown utility belt",
8
+ "motto": "ancient not extinct",
9
+ "public": true,
10
+ "license": "MIT",
11
+ "homepage": "https://belt.ryanatkn.com/",
12
+ "author": {
13
+ "name": "Ryan Atkinson",
14
+ "email": "mail@ryanatkn.com",
15
+ "url": "https://www.ryanatkn.com/"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/ryanatkn/belt.git"
20
+ },
21
+ "bugs": "https://github.com/ryanatkn/belt/issues",
22
+ "funding": "https://www.ryanatkn.com/funding",
23
+ "scripts": {
24
+ "start": "gro dev",
25
+ "dev": "gro dev",
26
+ "build": "gro build",
27
+ "check": "gro check",
28
+ "test": "gro test",
29
+ "preview": "vite preview",
30
+ "deploy": "gro deploy",
31
+ "benchmark": "gro run src/benchmarks/slugify_benchmark.ts",
32
+ "benchmark-deep-equal": "node --max-old-space-size=8192 --expose-gc --import @ryanatkn/gro/register.js src/lib/deep_equal.bench_comparison.ts",
33
+ "benchmark-deep-equal-deno": "deno run --allow-read --allow-env --v8-flags=--expose-gc src/lib/deep_equal.bench_comparison.ts"
34
+ },
35
+ "type": "module",
36
+ "engines": {
37
+ "node": ">=22.15"
38
+ },
39
+ "keywords": [
40
+ "js",
41
+ "typescript",
42
+ "utilities",
43
+ "web"
44
+ ],
45
+ "peerDependencies": {
46
+ "@types/node": "^24",
47
+ "esm-env": "^1.2.2",
48
+ "zod": "^4.0.14"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@types/node": {
52
+ "optional": true
53
+ },
54
+ "esm-env": {
55
+ "optional": true
56
+ },
57
+ "zod": {
58
+ "optional": true
59
+ }
60
+ },
61
+ "devDependencies": {
62
+ "@changesets/changelog-git": "^0.2.1",
63
+ "@ryanatkn/eslint-config": "^0.9.0",
64
+ "@ryanatkn/fuz": "^0.167.0",
65
+ "@ryanatkn/fuz_code": "^0.36.0",
66
+ "@ryanatkn/gro": "^0.179.0",
67
+ "@ryanatkn/moss": "^0.39.0",
68
+ "@sveltejs/adapter-static": "^3.0.10",
69
+ "@sveltejs/kit": "^2.49.0",
70
+ "@sveltejs/package": "^2.5.6",
71
+ "@sveltejs/vite-plugin-svelte": "^6.2.1",
72
+ "@types/node": "^24.10.1",
73
+ "dequal": "^2.0.3",
74
+ "eslint": "^9.39.1",
75
+ "eslint-plugin-svelte": "^3.13.0",
76
+ "esm-env": "^1.2.2",
77
+ "prettier": "^3.6.2",
78
+ "prettier-plugin-svelte": "^3.4.0",
79
+ "svelte": "^5.44.1",
80
+ "svelte-check": "^4.3.4",
81
+ "tinybench": "^5.1.0",
82
+ "tslib": "^2.8.1",
83
+ "typescript": "^5.9.3",
84
+ "typescript-eslint": "^8.48.0",
85
+ "vitest": "^4.0.14",
86
+ "zod": "^4.1.13"
87
+ },
88
+ "prettier": {
89
+ "plugins": [
90
+ "prettier-plugin-svelte"
91
+ ],
92
+ "useTabs": true,
93
+ "printWidth": 100,
94
+ "singleQuote": true,
95
+ "bracketSpacing": false,
96
+ "overrides": [
97
+ {
98
+ "files": "package.json",
99
+ "options": {
100
+ "useTabs": false
101
+ }
102
+ }
103
+ ]
104
+ },
105
+ "sideEffects": [
106
+ "**/*.css"
107
+ ],
108
+ "files": [
109
+ "dist",
110
+ "src/lib/**/*.ts",
111
+ "!src/lib/**/*.test.*",
112
+ "!dist/**/*.test.*"
113
+ ],
114
+ "exports": {
115
+ "./package.json": "./package.json",
116
+ "./*.js": {
117
+ "types": "./dist/*.d.ts",
118
+ "default": "./dist/*.js"
119
+ },
120
+ "./*.ts": {
121
+ "types": "./dist/*.d.ts",
122
+ "default": "./dist/*.js"
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,30 @@
1
+ import type {ArrayElement} from './types.js';
2
+
3
+ // TODO try to cange to readonly again, see if upstream errors are tolerably fixed
4
+ export const EMPTY_ARRAY: Array<any> = Object.freeze([]) as any;
5
+
6
+ /** Converts `value` to an array if it's not already. */
7
+ export const to_array = <T>(value: T): T extends ReadonlyArray<any> ? T : Array<T> =>
8
+ Array.isArray(value) ? (value as any) : ([value] as any);
9
+
10
+ /**
11
+ * Removes an element from `array` at `index` in an unordered manner.
12
+ * @mutates array swaps element at index with last element, then removes last element
13
+ */
14
+ export const remove_unordered = (array: Array<any>, index: number): void => {
15
+ array[index] = array[array.length - 1];
16
+ array.pop();
17
+ };
18
+
19
+ /**
20
+ * Returns a function that returns the next item in the `array`
21
+ * in a linear sequence, looping back to index 0 when it reaches the end.
22
+ */
23
+ export const to_next = <T extends ReadonlyArray<any>>(array: T): (() => ArrayElement<T>) => {
24
+ let i = -1;
25
+ return () => {
26
+ i++;
27
+ if (i >= array.length) i = 0;
28
+ return array[i];
29
+ };
30
+ };