@no-k/hermes 0.1.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 +15 -0
- package/README.md +88 -0
- package/bin/hermes.mjs +159 -0
- package/catalog.json +1084 -0
- package/lib/catalog.mjs +51 -0
- package/package.json +28 -0
- package/templates/algorithm/geometry/ccw/ccw.ts +25 -0
- package/templates/algorithm/geometry/ccw/index.ts +1 -0
- package/templates/algorithm/geometry/convex-hull/convex-hull.ts +43 -0
- package/templates/algorithm/geometry/convex-hull/index.ts +1 -0
- package/templates/algorithm/geometry/polygon-area/index.ts +1 -0
- package/templates/algorithm/geometry/polygon-area/polygon-area.ts +13 -0
- package/templates/algorithm/graph/bellman-ford/bellman-ford.ts +46 -0
- package/templates/algorithm/graph/bellman-ford/index.ts +1 -0
- package/templates/algorithm/graph/bfs/bfs.ts +21 -0
- package/templates/algorithm/graph/bfs/index.ts +1 -0
- package/templates/algorithm/graph/dfs/dfs.ts +22 -0
- package/templates/algorithm/graph/dfs/index.ts +1 -0
- package/templates/algorithm/graph/dijkstra/dijkstra.ts +37 -0
- package/templates/algorithm/graph/dijkstra/index.ts +1 -0
- package/templates/algorithm/graph/floyd-warshall/floyd-warshall.ts +17 -0
- package/templates/algorithm/graph/floyd-warshall/index.ts +1 -0
- package/templates/algorithm/graph/kruskal/index.ts +1 -0
- package/templates/algorithm/graph/kruskal/kruskal.ts +29 -0
- package/templates/algorithm/graph/prim/index.ts +1 -0
- package/templates/algorithm/graph/prim/prim.ts +44 -0
- package/templates/algorithm/graph/topology-sort/index.ts +1 -0
- package/templates/algorithm/graph/topology-sort/topology-sort.ts +31 -0
- package/templates/algorithm/search/binary-search/binary-search.ts +74 -0
- package/templates/algorithm/search/binary-search/index.ts +1 -0
- package/templates/algorithm/string/kmp/index.ts +1 -0
- package/templates/algorithm/string/kmp/kmp.ts +46 -0
- package/templates/algorithm/string/z-algorithm/index.ts +1 -0
- package/templates/algorithm/string/z-algorithm/z-algorithm.ts +41 -0
- package/templates/algorithm/tree/avl-tree/avl-tree.ts +197 -0
- package/templates/algorithm/tree/avl-tree/index.ts +1 -0
- package/templates/algorithm/tree/binary-search-tree/binary-search-tree.ts +144 -0
- package/templates/algorithm/tree/binary-search-tree/index.ts +1 -0
- package/templates/algorithm/tree/lca/index.ts +1 -0
- package/templates/algorithm/tree/lca/lca.ts +119 -0
- package/templates/array/chunk/chunk.ts +13 -0
- package/templates/array/chunk/index.ts +1 -0
- package/templates/array/compact/compact.ts +8 -0
- package/templates/array/compact/index.ts +1 -0
- package/templates/array/count-by/count-by.ts +12 -0
- package/templates/array/count-by/index.ts +1 -0
- package/templates/array/difference/difference.ts +6 -0
- package/templates/array/difference/index.ts +1 -0
- package/templates/array/drop/drop.ts +7 -0
- package/templates/array/drop/index.ts +1 -0
- package/templates/array/group-by/group-by.ts +18 -0
- package/templates/array/group-by/index.ts +1 -0
- package/templates/array/intersection/index.ts +1 -0
- package/templates/array/intersection/intersection.ts +15 -0
- package/templates/array/key-by/index.ts +1 -0
- package/templates/array/key-by/key-by.ts +11 -0
- package/templates/array/max-by/index.ts +1 -0
- package/templates/array/max-by/max-by.ts +20 -0
- package/templates/array/min-by/index.ts +1 -0
- package/templates/array/min-by/min-by.ts +20 -0
- package/templates/array/partition/index.ts +1 -0
- package/templates/array/partition/partition.ts +16 -0
- package/templates/array/range/index.ts +1 -0
- package/templates/array/range/range.ts +22 -0
- package/templates/array/sample/index.ts +1 -0
- package/templates/array/sample/sample.ts +10 -0
- package/templates/array/shuffle/index.ts +1 -0
- package/templates/array/shuffle/shuffle.ts +15 -0
- package/templates/array/sort-by/index.ts +1 -0
- package/templates/array/sort-by/sort-by.ts +32 -0
- package/templates/array/sum-by/index.ts +1 -0
- package/templates/array/sum-by/sum-by.ts +9 -0
- package/templates/array/take/index.ts +1 -0
- package/templates/array/take/take.ts +7 -0
- package/templates/array/union/index.ts +1 -0
- package/templates/array/union/union.ts +5 -0
- package/templates/array/unique/index.ts +1 -0
- package/templates/array/unique/unique.ts +5 -0
- package/templates/array/unique-by/index.ts +1 -0
- package/templates/array/unique-by/unique-by.ts +19 -0
- package/templates/array/windowed/index.ts +1 -0
- package/templates/array/windowed/windowed.ts +15 -0
- package/templates/array/zip/index.ts +1 -0
- package/templates/array/zip/zip.ts +11 -0
- package/templates/boolean/parse-boolean/index.ts +1 -0
- package/templates/boolean/parse-boolean/parse-boolean.ts +17 -0
- package/templates/boolean/toggle/index.ts +1 -0
- package/templates/boolean/toggle/toggle.ts +2 -0
- package/templates/boolean/xor/index.ts +1 -0
- package/templates/boolean/xor/xor.ts +2 -0
- package/templates/data-structure/deque/deque.ts +79 -0
- package/templates/data-structure/deque/index.ts +1 -0
- package/templates/data-structure/fenwick-tree/fenwick-tree.ts +76 -0
- package/templates/data-structure/fenwick-tree/index.ts +1 -0
- package/templates/data-structure/heap/heap.ts +88 -0
- package/templates/data-structure/heap/index.ts +3 -0
- package/templates/data-structure/heap/max-heap.ts +7 -0
- package/templates/data-structure/heap/min-heap.ts +7 -0
- package/templates/data-structure/priority-queue/index.ts +1 -0
- package/templates/data-structure/priority-queue/priority-queue.ts +44 -0
- package/templates/data-structure/queue/index.ts +1 -0
- package/templates/data-structure/queue/queue.ts +66 -0
- package/templates/data-structure/segment-tree/index.ts +1 -0
- package/templates/data-structure/segment-tree/segment-tree.ts +79 -0
- package/templates/data-structure/trie/index.ts +1 -0
- package/templates/data-structure/trie/trie.ts +104 -0
- package/templates/data-structure/union-find/index.ts +1 -0
- package/templates/data-structure/union-find/union-find.ts +79 -0
- package/templates/dom/attribute/attribute.ts +24 -0
- package/templates/dom/attribute/index.ts +1 -0
- package/templates/dom/class-name/class-name.ts +48 -0
- package/templates/dom/class-name/index.ts +1 -0
- package/templates/dom/is-element/index.ts +1 -0
- package/templates/dom/is-element/is-element.ts +20 -0
- package/templates/dom/query/index.ts +1 -0
- package/templates/dom/query/query.ts +7 -0
- package/templates/http/content-type/content-type.ts +130 -0
- package/templates/http/content-type/index.ts +1 -0
- package/templates/http/header/header.ts +56 -0
- package/templates/http/header/index.ts +1 -0
- package/templates/http/method/index.ts +1 -0
- package/templates/http/method/method.ts +25 -0
- package/templates/http/status/index.ts +1 -0
- package/templates/http/status/status.ts +191 -0
- package/templates/number/clamp/clamp.ts +11 -0
- package/templates/number/clamp/index.ts +1 -0
- package/templates/number/factorial/factorial.ts +15 -0
- package/templates/number/factorial/index.ts +1 -0
- package/templates/number/format-thousands/format-thousands.ts +24 -0
- package/templates/number/format-thousands/index.ts +1 -0
- package/templates/number/gcd/gcd.ts +13 -0
- package/templates/number/gcd/index.ts +1 -0
- package/templates/number/in-range/in-range.ts +3 -0
- package/templates/number/in-range/index.ts +1 -0
- package/templates/number/is-even/index.ts +1 -0
- package/templates/number/is-even/is-even.ts +5 -0
- package/templates/number/is-odd/index.ts +1 -0
- package/templates/number/is-odd/is-odd.ts +7 -0
- package/templates/number/lcm/index.ts +1 -0
- package/templates/number/lcm/lcm.ts +12 -0
- package/templates/number/lerp/index.ts +1 -0
- package/templates/number/lerp/lerp.ts +3 -0
- package/templates/number/percentage/index.ts +1 -0
- package/templates/number/percentage/percentage.ts +7 -0
- package/templates/number/random/index.ts +1 -0
- package/templates/number/random/random.ts +9 -0
- package/templates/number/random-int/index.ts +1 -0
- package/templates/number/random-int/random-int.ts +9 -0
- package/templates/number/round/index.ts +1 -0
- package/templates/number/round/round.ts +22 -0
- package/templates/number/safe-parse-number/index.ts +1 -0
- package/templates/number/safe-parse-number/safe-parse-number.ts +29 -0
- package/templates/number/to-ordinal/index.ts +1 -0
- package/templates/number/to-ordinal/to-ordinal.ts +28 -0
- package/templates/object/deep-clone/deep-clone.ts +47 -0
- package/templates/object/deep-clone/index.ts +1 -0
- package/templates/object/deep-equal/deep-equal.ts +66 -0
- package/templates/object/deep-equal/index.ts +1 -0
- package/templates/object/deep-merge/deep-merge.ts +49 -0
- package/templates/object/deep-merge/index.ts +1 -0
- package/templates/object/defaults/defaults.ts +14 -0
- package/templates/object/defaults/index.ts +1 -0
- package/templates/object/filter-object/filter-object.ts +11 -0
- package/templates/object/filter-object/index.ts +1 -0
- package/templates/object/get/get.ts +75 -0
- package/templates/object/get/index.ts +1 -0
- package/templates/object/has/has.ts +60 -0
- package/templates/object/has/index.ts +1 -0
- package/templates/object/invert/index.ts +1 -0
- package/templates/object/invert/invert.ts +13 -0
- package/templates/object/map-keys/index.ts +1 -0
- package/templates/object/map-keys/map-keys.ts +16 -0
- package/templates/object/map-values/index.ts +1 -0
- package/templates/object/map-values/map-values.ts +14 -0
- package/templates/object/omit/index.ts +1 -0
- package/templates/object/omit/omit.ts +15 -0
- package/templates/object/omit-by/index.ts +1 -0
- package/templates/object/omit-by/omit-by.ts +19 -0
- package/templates/object/pick/index.ts +1 -0
- package/templates/object/pick/pick.ts +15 -0
- package/templates/object/pick-by/index.ts +1 -0
- package/templates/object/pick-by/pick-by.ts +19 -0
- package/templates/object/set/index.ts +1 -0
- package/templates/object/set/set.ts +118 -0
- package/templates/string/capitalize/capitalize.ts +4 -0
- package/templates/string/capitalize/index.ts +1 -0
- package/templates/string/change-case/change-case.ts +98 -0
- package/templates/string/change-case/index.ts +1 -0
- package/templates/string/char/char.ts +14 -0
- package/templates/string/char/index.ts +1 -0
- package/templates/string/count-occurrences/count-occurrences.ts +20 -0
- package/templates/string/count-occurrences/index.ts +1 -0
- package/templates/string/escape-html/escape-html.ts +16 -0
- package/templates/string/escape-html/index.ts +1 -0
- package/templates/string/get-decorated-string/get-decorated-string.ts +7 -0
- package/templates/string/get-decorated-string/index.ts +1 -0
- package/templates/string/is-blank/index.ts +1 -0
- package/templates/string/is-blank/is-blank.ts +6 -0
- package/templates/string/mask/index.ts +1 -0
- package/templates/string/mask/mask.ts +30 -0
- package/templates/string/pad/index.ts +1 -0
- package/templates/string/pad/pad.ts +17 -0
- package/templates/string/random-string/index.ts +1 -0
- package/templates/string/random-string/random-string.ts +20 -0
- package/templates/string/reverse/index.ts +1 -0
- package/templates/string/reverse/reverse.ts +6 -0
- package/templates/string/slugify/index.ts +1 -0
- package/templates/string/slugify/slugify.ts +11 -0
- package/templates/string/strip-tags/index.ts +1 -0
- package/templates/string/strip-tags/strip-tags.ts +8 -0
- package/templates/string/template/index.ts +1 -0
- package/templates/string/template/template.ts +14 -0
- package/templates/string/truncate/index.ts +1 -0
- package/templates/string/truncate/truncate.ts +17 -0
- package/templates/string/truncate-middle/index.ts +1 -0
- package/templates/string/truncate-middle/truncate-middle.ts +25 -0
- package/templates/string/uuid/index.ts +1 -0
- package/templates/string/uuid/uuid.ts +9 -0
- package/templates/tailwindcss/class-variant/class-variant.ts +35 -0
- package/templates/tailwindcss/class-variant/index.ts +1 -0
- package/templates/tailwindcss/cn/cn.ts +29 -0
- package/templates/tailwindcss/cn/index.ts +1 -0
- package/templates/tailwindcss/create-tailwind-css/create-tailwind-css.ts +16 -0
- package/templates/tailwindcss/create-tailwind-css/index.ts +1 -0
- package/templates/tailwindcss/tw-merge-config/index.ts +1 -0
- package/templates/tailwindcss/tw-merge-config/tw-merge-config.ts +14 -0
- package/templates/type/get-type/get-type.ts +27 -0
- package/templates/type/get-type/index.ts +1 -0
- package/templates/type/is-type-of/index.ts +1 -0
- package/templates/type/is-type-of/is-type-of.ts +26 -0
- package/templates/util/mutex/index.ts +1 -0
- package/templates/util/mutex/mutex.ts +109 -0
- package/templates/util/url-parser/index.ts +1 -0
- package/templates/util/url-parser/url-parser.ts +44 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
type SegmentTreeOptions<T> = {
|
|
2
|
+
identity: T;
|
|
3
|
+
combine: (left: T, right: T) => T;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export class SegmentTree<T> {
|
|
7
|
+
private tree: T[];
|
|
8
|
+
private length: number;
|
|
9
|
+
private identity: T;
|
|
10
|
+
private combine: (left: T, right: T) => T;
|
|
11
|
+
|
|
12
|
+
constructor(values: T[], options: SegmentTreeOptions<T>) {
|
|
13
|
+
this.length = values.length;
|
|
14
|
+
this.identity = options.identity;
|
|
15
|
+
this.combine = options.combine;
|
|
16
|
+
this.tree = Array(Math.max(1, this.length * 2)).fill(this.identity);
|
|
17
|
+
|
|
18
|
+
for (let index = 0; index < this.length; index += 1) {
|
|
19
|
+
this.tree[this.length + index] = values[index];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (let index = this.length - 1; index > 0; index -= 1) {
|
|
23
|
+
this.tree[index] = this.combine(this.tree[index * 2], this.tree[index * 2 + 1]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
size() {
|
|
28
|
+
return this.length;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
update(index: number, value: T) {
|
|
32
|
+
this.assertIndex(index);
|
|
33
|
+
|
|
34
|
+
let current = index + this.length;
|
|
35
|
+
this.tree[current] = value;
|
|
36
|
+
|
|
37
|
+
while (current > 1) {
|
|
38
|
+
current = Math.floor(current / 2);
|
|
39
|
+
this.tree[current] = this.combine(this.tree[current * 2], this.tree[current * 2 + 1]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
query(left: number, right: number): T {
|
|
44
|
+
if (left > right) {
|
|
45
|
+
return this.identity;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.assertIndex(left);
|
|
49
|
+
this.assertIndex(right);
|
|
50
|
+
|
|
51
|
+
let start = left + this.length;
|
|
52
|
+
let end = right + this.length;
|
|
53
|
+
let leftResult = this.identity;
|
|
54
|
+
let rightResult = this.identity;
|
|
55
|
+
|
|
56
|
+
while (start <= end) {
|
|
57
|
+
if (start % 2 === 1) {
|
|
58
|
+
leftResult = this.combine(leftResult, this.tree[start]);
|
|
59
|
+
start += 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (end % 2 === 0) {
|
|
63
|
+
rightResult = this.combine(this.tree[end], rightResult);
|
|
64
|
+
end -= 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
start = Math.floor(start / 2);
|
|
68
|
+
end = Math.floor(end / 2);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return this.combine(leftResult, rightResult);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private assertIndex(index: number) {
|
|
75
|
+
if (!Number.isInteger(index) || index < 0 || index >= this.length) {
|
|
76
|
+
throw new RangeError("index is out of range");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./trie";
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
class TrieNode {
|
|
2
|
+
children = new Map<string, TrieNode>();
|
|
3
|
+
isWord = false;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export class Trie {
|
|
7
|
+
private root = new TrieNode();
|
|
8
|
+
private words = 0;
|
|
9
|
+
|
|
10
|
+
size() {
|
|
11
|
+
return this.words;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
insert(word: string) {
|
|
15
|
+
let node = this.root;
|
|
16
|
+
|
|
17
|
+
for (const char of word) {
|
|
18
|
+
const next = node.children.get(char) ?? new TrieNode();
|
|
19
|
+
node.children.set(char, next);
|
|
20
|
+
node = next;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!node.isWord) {
|
|
24
|
+
this.words += 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
node.isWord = true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
has(word: string) {
|
|
31
|
+
return this.findNode(word)?.isWord ?? false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
startsWith(prefix: string) {
|
|
35
|
+
return this.findNode(prefix) !== null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
delete(word: string) {
|
|
39
|
+
if (!this.has(word)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const remove = (node: TrieNode, index: number): boolean => {
|
|
44
|
+
if (index === word.length) {
|
|
45
|
+
node.isWord = false;
|
|
46
|
+
this.words -= 1;
|
|
47
|
+
|
|
48
|
+
return node.children.size === 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const char = word[index];
|
|
52
|
+
const next = node.children.get(char) as TrieNode;
|
|
53
|
+
const shouldDeleteChild = remove(next, index + 1);
|
|
54
|
+
|
|
55
|
+
if (shouldDeleteChild) {
|
|
56
|
+
node.children.delete(char);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return !node.isWord && node.children.size === 0;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
remove(this.root, 0);
|
|
63
|
+
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
collect(prefix = "") {
|
|
68
|
+
const node = this.findNode(prefix);
|
|
69
|
+
if (!node) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const result: string[] = [];
|
|
74
|
+
|
|
75
|
+
const visit = (current: TrieNode, word: string) => {
|
|
76
|
+
if (current.isWord) {
|
|
77
|
+
result.push(word);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const [char, next] of current.children) {
|
|
81
|
+
visit(next, word + char);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
visit(node, prefix);
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private findNode(value: string): TrieNode | null {
|
|
91
|
+
let node = this.root;
|
|
92
|
+
|
|
93
|
+
for (const char of value) {
|
|
94
|
+
const next = node.children.get(char);
|
|
95
|
+
if (!next) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
node = next;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return node;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./union-find";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export class UnionFind<T> {
|
|
2
|
+
private parents = new Map<T, T>();
|
|
3
|
+
private sizes = new Map<T, number>();
|
|
4
|
+
private components = 0;
|
|
5
|
+
|
|
6
|
+
constructor(values: Iterable<T> = []) {
|
|
7
|
+
for (const value of values) {
|
|
8
|
+
this.add(value);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
add(value: T) {
|
|
13
|
+
if (this.parents.has(value)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.parents.set(value, value);
|
|
18
|
+
this.sizes.set(value, 1);
|
|
19
|
+
this.components += 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
find(value: T): T {
|
|
23
|
+
this.add(value);
|
|
24
|
+
|
|
25
|
+
const parent = this.parents.get(value) as T;
|
|
26
|
+
if (parent === value) {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const root = this.find(parent);
|
|
31
|
+
this.parents.set(value, root);
|
|
32
|
+
|
|
33
|
+
return root;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
union(a: T, b: T): boolean {
|
|
37
|
+
let aRoot = this.find(a);
|
|
38
|
+
let bRoot = this.find(b);
|
|
39
|
+
|
|
40
|
+
if (aRoot === bRoot) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if ((this.sizes.get(aRoot) as number) < (this.sizes.get(bRoot) as number)) {
|
|
45
|
+
[aRoot, bRoot] = [bRoot, aRoot];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.parents.set(bRoot, aRoot);
|
|
49
|
+
this.sizes.set(aRoot, (this.sizes.get(aRoot) as number) + (this.sizes.get(bRoot) as number));
|
|
50
|
+
this.components -= 1;
|
|
51
|
+
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
connected(a: T, b: T) {
|
|
56
|
+
return this.find(a) === this.find(b);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
sizeOf(value: T) {
|
|
60
|
+
return this.sizes.get(this.find(value)) as number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
count() {
|
|
64
|
+
return this.components;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
groups() {
|
|
68
|
+
const groups = new Map<T, T[]>();
|
|
69
|
+
|
|
70
|
+
for (const value of this.parents.keys()) {
|
|
71
|
+
const root = this.find(value);
|
|
72
|
+
const values = groups.get(root) ?? [];
|
|
73
|
+
values.push(value);
|
|
74
|
+
groups.set(root, values);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return [...groups.values()];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type AttributeValue = string | number | boolean | null | undefined;
|
|
2
|
+
|
|
3
|
+
export const getAttributeValue = (element: Element, name: string): string | undefined => {
|
|
4
|
+
return element.getAttribute(name) ?? undefined;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const setAttributeValue = <T extends Element>(element: T, name: string, value: AttributeValue): T => {
|
|
8
|
+
if (value === null || value === undefined) {
|
|
9
|
+
element.removeAttribute(name);
|
|
10
|
+
return element;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
element.setAttribute(name, String(value));
|
|
14
|
+
|
|
15
|
+
return element;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const setAttributes = <T extends Element>(element: T, attributes: Record<string, AttributeValue>): T => {
|
|
19
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
20
|
+
setAttributeValue(element, name, value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return element;
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./attribute";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
type ClassNameInput = string | readonly string[];
|
|
2
|
+
|
|
3
|
+
const normalizeClassNames = (classNames: ClassNameInput): string[] => {
|
|
4
|
+
const values =
|
|
5
|
+
typeof classNames === "string" ? classNames.split(/\s+/) : classNames.flatMap((name) => name.split(/\s+/));
|
|
6
|
+
|
|
7
|
+
return values.map((name) => name.trim()).filter((name) => name.length > 0);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const hasClass = (element: Element, className: string): boolean => {
|
|
11
|
+
const [normalizedClassName, extraClassName] = normalizeClassNames(className);
|
|
12
|
+
|
|
13
|
+
if (normalizedClassName === undefined || extraClassName !== undefined) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return element.classList.contains(normalizedClassName);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const addClass = <T extends Element>(element: T, classNames: ClassNameInput): T => {
|
|
21
|
+
const normalizedClassNames = normalizeClassNames(classNames);
|
|
22
|
+
|
|
23
|
+
if (normalizedClassNames.length > 0) {
|
|
24
|
+
element.classList.add(...normalizedClassNames);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return element;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const removeClass = <T extends Element>(element: T, classNames: ClassNameInput): T => {
|
|
31
|
+
const normalizedClassNames = normalizeClassNames(classNames);
|
|
32
|
+
|
|
33
|
+
if (normalizedClassNames.length > 0) {
|
|
34
|
+
element.classList.remove(...normalizedClassNames);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return element;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const toggleClass = (element: Element, className: string, force?: boolean): boolean => {
|
|
41
|
+
const [normalizedClassName, extraClassName] = normalizeClassNames(className);
|
|
42
|
+
|
|
43
|
+
if (normalizedClassName === undefined || extraClassName !== undefined) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return element.classList.toggle(normalizedClassName, force);
|
|
48
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./class-name";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./is-element";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const isElement = (value: unknown, tagName?: string): value is Element => {
|
|
2
|
+
if (typeof value !== "object" || value === null) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const candidate = value as {
|
|
7
|
+
nodeName?: unknown;
|
|
8
|
+
nodeType?: unknown;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
if (candidate.nodeType !== 1 || typeof candidate.nodeName !== "string") {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (tagName === undefined) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return candidate.nodeName.toLowerCase() === tagName.trim().toLowerCase();
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./query";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const queryElement = <T extends Element = Element>(root: ParentNode, selector: string): T | undefined => {
|
|
2
|
+
return root.querySelector<T>(selector) ?? undefined;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export const queryElements = <T extends Element = Element>(root: ParentNode, selector: string): T[] => {
|
|
6
|
+
return Array.from(root.querySelectorAll<T>(selector));
|
|
7
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export const HTTP_CONTENT_TYPE = {
|
|
2
|
+
APPLICATION_FORM_URLENCODED: "application/x-www-form-urlencoded",
|
|
3
|
+
APPLICATION_JAVASCRIPT: "application/javascript",
|
|
4
|
+
APPLICATION_JSON: "application/json",
|
|
5
|
+
APPLICATION_OCTET_STREAM: "application/octet-stream",
|
|
6
|
+
APPLICATION_PDF: "application/pdf",
|
|
7
|
+
APPLICATION_XML: "application/xml",
|
|
8
|
+
IMAGE_SVG_XML: "image/svg+xml",
|
|
9
|
+
MULTIPART_FORM_DATA: "multipart/form-data",
|
|
10
|
+
TEXT_CSS: "text/css",
|
|
11
|
+
TEXT_CSV: "text/csv",
|
|
12
|
+
TEXT_HTML: "text/html",
|
|
13
|
+
TEXT_PLAIN: "text/plain",
|
|
14
|
+
TEXT_XML: "text/xml",
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
export type HttpContentType = (typeof HTTP_CONTENT_TYPE)[keyof typeof HTTP_CONTENT_TYPE];
|
|
18
|
+
|
|
19
|
+
export type ParsedContentType = {
|
|
20
|
+
type: string;
|
|
21
|
+
subtype: string;
|
|
22
|
+
mimeType: string;
|
|
23
|
+
parameters: Record<string, string>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const HTTP_TOKEN_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
27
|
+
|
|
28
|
+
const splitContentTypeSegments = (contentType: string): string[] => {
|
|
29
|
+
const segments: string[] = [];
|
|
30
|
+
let segment = "";
|
|
31
|
+
let isQuoted = false;
|
|
32
|
+
let isEscaped = false;
|
|
33
|
+
|
|
34
|
+
for (const char of contentType) {
|
|
35
|
+
if (isEscaped) {
|
|
36
|
+
segment += char;
|
|
37
|
+
isEscaped = false;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (char === "\\" && isQuoted) {
|
|
42
|
+
segment += char;
|
|
43
|
+
isEscaped = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (char === '"') {
|
|
48
|
+
segment += char;
|
|
49
|
+
isQuoted = !isQuoted;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (char === ";" && !isQuoted) {
|
|
54
|
+
segments.push(segment.trim());
|
|
55
|
+
segment = "";
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
segment += char;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
segments.push(segment.trim());
|
|
63
|
+
|
|
64
|
+
return segments;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const unquoteParameterValue = (value: string): string => {
|
|
68
|
+
const trimmedValue = value.trim();
|
|
69
|
+
|
|
70
|
+
if (trimmedValue.length < 2 || !trimmedValue.startsWith('"') || !trimmedValue.endsWith('"')) {
|
|
71
|
+
return trimmedValue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return trimmedValue.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const parseContentType = (contentType: string): ParsedContentType | undefined => {
|
|
78
|
+
const [rawMimeType, ...rawParameters] = splitContentTypeSegments(contentType);
|
|
79
|
+
|
|
80
|
+
if (rawMimeType === undefined) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const mimeType = rawMimeType.trim().toLowerCase();
|
|
85
|
+
const mimeTypeSegments = mimeType.split("/");
|
|
86
|
+
|
|
87
|
+
if (mimeTypeSegments.length !== 2) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const [type, subtype] = mimeTypeSegments;
|
|
92
|
+
|
|
93
|
+
if (!HTTP_TOKEN_PATTERN.test(type) || !HTTP_TOKEN_PATTERN.test(subtype)) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const parameters: Record<string, string> = {};
|
|
98
|
+
|
|
99
|
+
for (const rawParameter of rawParameters) {
|
|
100
|
+
const separatorIndex = rawParameter.indexOf("=");
|
|
101
|
+
|
|
102
|
+
if (separatorIndex === -1) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const parameterName = rawParameter.slice(0, separatorIndex).trim().toLowerCase();
|
|
107
|
+
|
|
108
|
+
if (!HTTP_TOKEN_PATTERN.test(parameterName)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
parameters[parameterName] = unquoteParameterValue(rawParameter.slice(separatorIndex + 1));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
type,
|
|
117
|
+
subtype,
|
|
118
|
+
mimeType: `${type}/${subtype}`,
|
|
119
|
+
parameters,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const isJsonContentType = (contentType: string): boolean => {
|
|
124
|
+
const parsedContentType = parseContentType(contentType);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
parsedContentType !== undefined &&
|
|
128
|
+
(parsedContentType.mimeType === HTTP_CONTENT_TYPE.APPLICATION_JSON || parsedContentType.subtype.endsWith("+json"))
|
|
129
|
+
);
|
|
130
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./content-type";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const HTTP_HEADER = {
|
|
2
|
+
ACCEPT: "Accept",
|
|
3
|
+
ACCEPT_ENCODING: "Accept-Encoding",
|
|
4
|
+
ACCEPT_LANGUAGE: "Accept-Language",
|
|
5
|
+
ACCESS_CONTROL_ALLOW_ORIGIN: "Access-Control-Allow-Origin",
|
|
6
|
+
AUTHORIZATION: "Authorization",
|
|
7
|
+
CACHE_CONTROL: "Cache-Control",
|
|
8
|
+
CONTENT_ENCODING: "Content-Encoding",
|
|
9
|
+
CONTENT_LENGTH: "Content-Length",
|
|
10
|
+
CONTENT_TYPE: "Content-Type",
|
|
11
|
+
COOKIE: "Cookie",
|
|
12
|
+
ETAG: "ETag",
|
|
13
|
+
HOST: "Host",
|
|
14
|
+
IF_NONE_MATCH: "If-None-Match",
|
|
15
|
+
LOCATION: "Location",
|
|
16
|
+
ORIGIN: "Origin",
|
|
17
|
+
REFERER: "Referer",
|
|
18
|
+
RETRY_AFTER: "Retry-After",
|
|
19
|
+
SET_COOKIE: "Set-Cookie",
|
|
20
|
+
USER_AGENT: "User-Agent",
|
|
21
|
+
WWW_AUTHENTICATE: "WWW-Authenticate",
|
|
22
|
+
X_REQUEST_ID: "X-Request-Id",
|
|
23
|
+
X_REQUESTED_WITH: "X-Requested-With",
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export type HttpHeader = (typeof HTTP_HEADER)[keyof typeof HTTP_HEADER];
|
|
27
|
+
|
|
28
|
+
const HTTP_HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
29
|
+
const CANONICAL_HTTP_HEADER_BY_LOWERCASE = new Map(
|
|
30
|
+
Object.values(HTTP_HEADER).map((name) => [name.toLowerCase(), name]),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export const isHttpHeaderName = (name: string): boolean => {
|
|
34
|
+
return HTTP_HEADER_NAME_PATTERN.test(name);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const normalizeHeaderName = (name: string): string => {
|
|
38
|
+
const trimmedName = name.trim();
|
|
39
|
+
|
|
40
|
+
if (trimmedName.length === 0) {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const normalizedName = trimmedName
|
|
45
|
+
.split("-")
|
|
46
|
+
.map((segment) => {
|
|
47
|
+
if (segment.length === 0) {
|
|
48
|
+
return segment;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return `${segment.charAt(0).toUpperCase()}${segment.slice(1).toLowerCase()}`;
|
|
52
|
+
})
|
|
53
|
+
.join("-");
|
|
54
|
+
|
|
55
|
+
return CANONICAL_HTTP_HEADER_BY_LOWERCASE.get(normalizedName.toLowerCase()) ?? normalizedName;
|
|
56
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./header";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./method";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const HTTP_METHOD = {
|
|
2
|
+
CONNECT: "CONNECT",
|
|
3
|
+
DELETE: "DELETE",
|
|
4
|
+
GET: "GET",
|
|
5
|
+
HEAD: "HEAD",
|
|
6
|
+
OPTIONS: "OPTIONS",
|
|
7
|
+
PATCH: "PATCH",
|
|
8
|
+
POST: "POST",
|
|
9
|
+
PUT: "PUT",
|
|
10
|
+
TRACE: "TRACE",
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export type HttpMethod = (typeof HTTP_METHOD)[keyof typeof HTTP_METHOD];
|
|
14
|
+
|
|
15
|
+
const HTTP_METHOD_SET = new Set<string>(Object.values(HTTP_METHOD));
|
|
16
|
+
|
|
17
|
+
export const isHttpMethod = (method: string): method is HttpMethod => {
|
|
18
|
+
return HTTP_METHOD_SET.has(method);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const normalizeHttpMethod = (method: string): HttpMethod | undefined => {
|
|
22
|
+
const normalizedMethod = method.trim().toUpperCase();
|
|
23
|
+
|
|
24
|
+
return isHttpMethod(normalizedMethod) ? normalizedMethod : undefined;
|
|
25
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./status";
|