@e280/stz 0.0.0-3 → 0.0.0-30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +170 -0
- package/package.json +14 -7
- package/s/clone/clone.test.ts +123 -123
- package/s/coalesce.ts +1 -0
- package/s/data/base-x.ts +170 -0
- package/s/data/base58.ts +15 -5
- package/s/data/base64.ts +15 -6
- package/s/data/base64url.ts +15 -6
- package/s/data/bytename/bytename.test.ts +49 -0
- package/s/data/{barname/barname.ts → bytename/bytename.ts} +38 -10
- package/s/data/bytename/index.ts +4 -0
- package/s/data/bytename/thumbprint.test.ts +53 -0
- package/s/data/bytename/thumbprint.ts +112 -0
- package/s/data/bytes.ts +12 -2
- package/s/data/data.test.ts +98 -0
- package/s/data/hex.ts +15 -5
- package/s/data/txt.ts +14 -4
- package/s/deadline.ts +1 -0
- package/s/debounce/debounce.test.ts +73 -73
- package/s/deep/deep.test.ts +8 -8
- package/s/deep/parts/equal.test.ts +123 -124
- package/s/{defer-promise.ts → defer.ts} +2 -2
- package/s/drill.ts +2 -4
- package/s/ev.ts +20 -0
- package/s/id-counter.ts +9 -0
- package/s/index.ts +16 -2
- package/s/is.ts +10 -4
- package/s/loopy.ts +12 -0
- package/s/map-g.ts +2 -2
- package/s/nap.ts +1 -0
- package/s/once.ts +14 -0
- package/s/pubsub.ts +24 -6
- package/s/queue/queue.test.ts +67 -0
- package/s/queue/queue.ts +21 -0
- package/s/repeat.ts +25 -0
- package/s/tests.test.ts +21 -0
- package/s/time.ts +22 -0
- package/x/clone/clone.test.d.ts +15 -1
- package/x/clone/clone.test.js +106 -124
- package/x/clone/clone.test.js.map +1 -1
- package/x/coalesce.d.ts +1 -0
- package/x/coalesce.js +1 -0
- package/x/coalesce.js.map +1 -1
- package/x/data/base-x.d.ts +47 -0
- package/x/data/base-x.js +147 -0
- package/x/data/base-x.js.map +1 -0
- package/x/data/base58.d.ts +5 -1
- package/x/data/base58.js +13 -5
- package/x/data/base58.js.map +1 -1
- package/x/data/base64.d.ts +7 -3
- package/x/data/base64.js +13 -5
- package/x/data/base64.js.map +1 -1
- package/x/data/base64url.d.ts +5 -1
- package/x/data/base64url.js +13 -5
- package/x/data/base64url.js.map +1 -1
- package/x/data/bytename/bytename.d.ts +19 -0
- package/x/data/{barname/barname.js → bytename/bytename.js} +27 -11
- package/x/data/bytename/bytename.js.map +1 -0
- package/x/data/bytename/bytename.test.d.ts +17 -0
- package/x/data/bytename/bytename.test.js +41 -0
- package/x/data/bytename/bytename.test.js.map +1 -0
- package/x/data/bytename/index.d.ts +2 -0
- package/x/data/bytename/index.js +3 -0
- package/x/data/bytename/index.js.map +1 -0
- package/x/data/bytename/thumbprint.d.ts +36 -0
- package/x/data/bytename/thumbprint.js +79 -0
- package/x/data/bytename/thumbprint.js.map +1 -0
- package/x/data/bytename/thumbprint.test.d.ts +24 -0
- package/x/data/bytename/thumbprint.test.js +45 -0
- package/x/data/bytename/thumbprint.test.js.map +1 -0
- package/x/data/bytename/utils/prefixes.js.map +1 -0
- package/x/data/bytename/utils/suffixes.js.map +1 -0
- package/x/data/bytes.d.ts +3 -2
- package/x/data/bytes.js +11 -2
- package/x/data/bytes.js.map +1 -1
- package/x/data/data.test.d.ts +69 -0
- package/x/data/data.test.js +77 -0
- package/x/data/data.test.js.map +1 -0
- package/x/data/hex.d.ts +8 -4
- package/x/data/hex.js +13 -5
- package/x/data/hex.js.map +1 -1
- package/x/data/txt.d.ts +6 -2
- package/x/data/txt.js +12 -4
- package/x/data/txt.js.map +1 -1
- package/x/debounce/debounce.test.d.ts +8 -1
- package/x/debounce/debounce.test.js +70 -74
- package/x/debounce/debounce.test.js.map +1 -1
- package/x/deep/deep.test.d.ts +21 -1
- package/x/deep/deep.test.js +5 -9
- package/x/deep/deep.test.js.map +1 -1
- package/x/deep/parts/equal.test.d.ts +19 -1
- package/x/deep/parts/equal.test.js +107 -125
- package/x/deep/parts/equal.test.js.map +1 -1
- package/x/{defer-promise.d.ts → defer.d.ts} +2 -2
- package/x/{defer-promise.js → defer.js} +2 -2
- package/x/defer.js.map +1 -0
- package/x/drill.d.ts +1 -3
- package/x/drill.js +2 -4
- package/x/drill.js.map +1 -1
- package/x/ev.d.ts +6 -0
- package/x/ev.js +10 -0
- package/x/ev.js.map +1 -0
- package/x/id-counter.d.ts +5 -0
- package/x/id-counter.js +10 -0
- package/x/id-counter.js.map +1 -0
- package/x/index.d.ts +13 -2
- package/x/index.js +13 -2
- package/x/index.js.map +1 -1
- package/x/is.d.ts +6 -4
- package/x/is.js +6 -4
- package/x/is.js.map +1 -1
- package/x/loopy.d.ts +2 -0
- package/x/loopy.js +10 -0
- package/x/loopy.js.map +1 -0
- package/x/map-g.d.ts +3 -3
- package/x/map-g.js +1 -1
- package/x/map-g.js.map +1 -1
- package/x/nap.d.ts +1 -0
- package/x/nap.js +1 -0
- package/x/nap.js.map +1 -1
- package/x/once.d.ts +1 -0
- package/x/once.js +12 -0
- package/x/once.js.map +1 -0
- package/x/pubsub.d.ts +12 -1
- package/x/pubsub.js +7 -5
- package/x/pubsub.js.map +1 -1
- package/x/queue/queue.d.ts +4 -0
- package/x/queue/queue.js +14 -0
- package/x/queue/queue.js.map +1 -0
- package/x/queue/queue.test.d.ts +8 -0
- package/x/queue/queue.test.js +57 -0
- package/x/queue/queue.test.js.map +1 -0
- package/x/repeat.d.ts +5 -0
- package/x/repeat.js +23 -0
- package/x/repeat.js.map +1 -0
- package/x/tests.test.js +18 -0
- package/x/tests.test.js.map +1 -0
- package/x/time.d.ts +18 -0
- package/x/time.js +19 -0
- package/x/time.js.map +1 -0
- package/readme.md +0 -85
- package/s/data/anka.ts +0 -66
- package/s/data/barname/badge.ts +0 -78
- package/s/data/barname/demo.ts +0 -49
- package/s/data/barname/index.ts +0 -4
- package/x/data/anka.d.ts +0 -5
- package/x/data/anka.js +0 -53
- package/x/data/anka.js.map +0 -1
- package/x/data/barname/badge.d.ts +0 -25
- package/x/data/barname/badge.js +0 -64
- package/x/data/barname/badge.js.map +0 -1
- package/x/data/barname/barname.d.ts +0 -7
- package/x/data/barname/barname.js.map +0 -1
- package/x/data/barname/demo.js +0 -45
- package/x/data/barname/demo.js.map +0 -1
- package/x/data/barname/index.d.ts +0 -2
- package/x/data/barname/index.js +0 -3
- package/x/data/barname/index.js.map +0 -1
- package/x/data/barname/utils/prefixes.js.map +0 -1
- package/x/data/barname/utils/suffixes.js.map +0 -1
- package/x/defer-promise.js.map +0 -1
- /package/{license → LICENSE} +0 -0
- /package/s/data/{barname → bytename}/utils/prefixes.ts +0 -0
- /package/s/data/{barname → bytename}/utils/suffixes.ts +0 -0
- /package/x/data/{barname → bytename}/utils/prefixes.d.ts +0 -0
- /package/x/data/{barname → bytename}/utils/prefixes.js +0 -0
- /package/x/data/{barname → bytename}/utils/suffixes.d.ts +0 -0
- /package/x/data/{barname → bytename}/utils/suffixes.js +0 -0
- /package/x/{data/barname/demo.d.ts → tests.test.d.ts} +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
|
|
2
|
+
# `@e280/stz`
|
|
3
|
+
standard library of environment-agnostic typescript functions we use basically everywhere
|
|
4
|
+
- zero dependencies
|
|
5
|
+
|
|
6
|
+
<br/>
|
|
7
|
+
|
|
8
|
+
## MapG
|
|
9
|
+
### extended js map
|
|
10
|
+
- `map.require`
|
|
11
|
+
```ts
|
|
12
|
+
import {MapG} from "@e280/stz"
|
|
13
|
+
|
|
14
|
+
const map = new MapG<number, string>([
|
|
15
|
+
[1, "hello"],
|
|
16
|
+
[2, "world"],
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
// throws error if the value is undefined
|
|
20
|
+
const value = map.require(1)
|
|
21
|
+
```
|
|
22
|
+
- `map.guarantee`
|
|
23
|
+
```ts
|
|
24
|
+
// if the value is undefined, the new value "rofl" is set and returned
|
|
25
|
+
const value = map.guarantee(3, () => "rofl")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
<br/>
|
|
29
|
+
|
|
30
|
+
## pub and sub
|
|
31
|
+
### ergonomic event emitters
|
|
32
|
+
- make a publisher fn
|
|
33
|
+
```ts
|
|
34
|
+
import {pub} from "@e280/stz"
|
|
35
|
+
|
|
36
|
+
// create a pub fn
|
|
37
|
+
const sendMessage = pub<[string]>()
|
|
38
|
+
|
|
39
|
+
// subscribe to it
|
|
40
|
+
sendMessage.sub(m => console.log(m))
|
|
41
|
+
|
|
42
|
+
// publish to it
|
|
43
|
+
sendMessage("hello")
|
|
44
|
+
```
|
|
45
|
+
- make a subscriber fn *(see how it's just the reverse of pub?)*
|
|
46
|
+
```ts
|
|
47
|
+
import {sub} from "@e280/stz"
|
|
48
|
+
|
|
49
|
+
// create a sub fn
|
|
50
|
+
const onMessage = sub<[string]>()
|
|
51
|
+
|
|
52
|
+
// subscribe to it
|
|
53
|
+
onMessage(m => console.log(m))
|
|
54
|
+
|
|
55
|
+
// publish to it
|
|
56
|
+
onMessage.pub("hello")
|
|
57
|
+
```
|
|
58
|
+
- the pub and sub are the same, but have differing invoke signatures
|
|
59
|
+
- i seem to use `sub` more often
|
|
60
|
+
- both have some extra functionality
|
|
61
|
+
```ts
|
|
62
|
+
// pub fns return a promise, to wait for all async subscribers
|
|
63
|
+
await sendMessage("hello")
|
|
64
|
+
await onMessage.pub("hello")
|
|
65
|
+
|
|
66
|
+
// sub fns return an unsub fn
|
|
67
|
+
const unsub1 = onMessage(m => console.log(m))
|
|
68
|
+
unsub1() // unsubscribe that listener
|
|
69
|
+
|
|
70
|
+
const unsub2 = sendMessage.sub(m => console.log(m))
|
|
71
|
+
unsub2() // unsubscribe that listener
|
|
72
|
+
|
|
73
|
+
// you can clear all subscribers from a pub or a sub
|
|
74
|
+
sendMessage.clear()
|
|
75
|
+
onMessage.clear()
|
|
76
|
+
|
|
77
|
+
// instead of a 'once' fn we simply await next()
|
|
78
|
+
await onMessage.next()
|
|
79
|
+
await sendMessage.next()
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
<br/>
|
|
83
|
+
|
|
84
|
+
## Data utilities
|
|
85
|
+
> codecs for representing data in different ways
|
|
86
|
+
|
|
87
|
+
### BaseX
|
|
88
|
+
- make a BaseX instance
|
|
89
|
+
```ts
|
|
90
|
+
import {BaseX} from "@e280/stz"
|
|
91
|
+
|
|
92
|
+
const hex = new BaseX(BaseX.lexicons.hex)
|
|
93
|
+
```
|
|
94
|
+
- convert between strings and binary
|
|
95
|
+
```ts
|
|
96
|
+
hex.toBytes("9960cd633a46acfe8307d8a400e842da0d930a75fb8188e0f5da264e4b6b4e5b")
|
|
97
|
+
// Uint8Array
|
|
98
|
+
|
|
99
|
+
hex.fromBytes(bytes)
|
|
100
|
+
// string
|
|
101
|
+
```
|
|
102
|
+
- you can also convert between strings and integers
|
|
103
|
+
```ts
|
|
104
|
+
hex.fromInteger(Date.now())
|
|
105
|
+
// "197140ac804"
|
|
106
|
+
|
|
107
|
+
hex.toInteger(hex)
|
|
108
|
+
// 1748387940356
|
|
109
|
+
```
|
|
110
|
+
- available lexicons include
|
|
111
|
+
- base2
|
|
112
|
+
- hex
|
|
113
|
+
- base36
|
|
114
|
+
- base58
|
|
115
|
+
- base62
|
|
116
|
+
- base64 (with standard padding)
|
|
117
|
+
- base64url
|
|
118
|
+
- you can make insanely compact timestamps like this:
|
|
119
|
+
```ts
|
|
120
|
+
import {BaseX} from "@e280/stz"
|
|
121
|
+
|
|
122
|
+
const base62 = new BaseX(BaseX.lexicons.base62)
|
|
123
|
+
|
|
124
|
+
base62.fromInteger(Date.now() / 1000)
|
|
125
|
+
// "1uK3au"
|
|
126
|
+
```
|
|
127
|
+
- `1748388028` base10 epoch seconds (10 chars)
|
|
128
|
+
- `1uK3au` base62 epoch seconds (6 chars)
|
|
129
|
+
- *nice*
|
|
130
|
+
|
|
131
|
+
<br/>
|
|
132
|
+
|
|
133
|
+
## Bytename
|
|
134
|
+
> friendly string encoding for binary data
|
|
135
|
+
|
|
136
|
+
a bytename looks like `"midsen.picmyn.widrep.baclut dotreg.filtyp.nosnus.siptev"`. that's 16 bytes. each byte maps to a three-letter triplet
|
|
137
|
+
|
|
138
|
+
the bytename parser (`Bytename.toBytes`) ignores all non-alphabetic characters. thus `midsen.picmyn`, `midsenpicmyn`, and `mid@sen$pic@myn` are all equal.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import {Bytename} from "@e280/stz"
|
|
142
|
+
```
|
|
143
|
+
- ```ts
|
|
144
|
+
Bytename.fromBytes(new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF]))
|
|
145
|
+
// "ribmug.hilmun"
|
|
146
|
+
```
|
|
147
|
+
- ```ts
|
|
148
|
+
Bytename.toBytes("ribmug.hilmun")
|
|
149
|
+
// Uint8Array, 4 bytes
|
|
150
|
+
```
|
|
151
|
+
- ```ts
|
|
152
|
+
const bytes = new Uint8Array([
|
|
153
|
+
0xDE, 0xAD, 0xBE, 0xEF,
|
|
154
|
+
0xDE, 0xAD, 0xBE, 0xEF,
|
|
155
|
+
])
|
|
156
|
+
|
|
157
|
+
Bytename.fromBytes(bytes, {
|
|
158
|
+
groupSize: 2, // default is 4
|
|
159
|
+
groupSeparator: " ",
|
|
160
|
+
wordSeparator: ".",
|
|
161
|
+
})
|
|
162
|
+
// "ribmug.hilmun ribmug.hilmun"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
<br/>
|
|
166
|
+
|
|
167
|
+
## 💖 stz is made with open source love
|
|
168
|
+
reward us with github stars
|
|
169
|
+
build with us at https://e280.org/ but only if you're cool
|
|
170
|
+
|
package/package.json
CHANGED
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e280/stz",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-30",
|
|
4
4
|
"description": "everyday ts fns for everything",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chase Moskal <chasemoskal@gmail.com>",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"main": "x/index.js",
|
|
8
|
+
"main": "./x/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./x/index.js"
|
|
11
|
+
},
|
|
9
12
|
"files": [
|
|
10
13
|
"x",
|
|
11
14
|
"s"
|
|
12
15
|
],
|
|
13
16
|
"scripts": {
|
|
14
17
|
"build": "run-s _clean _links _tsc",
|
|
18
|
+
"test": "node x/tests.test.js",
|
|
19
|
+
"test-watch": "node --watch x/tests.test.js",
|
|
20
|
+
"test-inspect": "node inspect x/tests.test.js",
|
|
21
|
+
"count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +",
|
|
22
|
+
"watch": "run-p _tscw test-watch",
|
|
15
23
|
"_clean": "rm -rf x && mkdir x",
|
|
16
24
|
"_links": "ln -s \"$(realpath node_modules)\" x/node_modules",
|
|
17
25
|
"_tsc": "tsc",
|
|
18
|
-
"
|
|
19
|
-
"count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +",
|
|
20
|
-
"test": "exit 0"
|
|
26
|
+
"_tscw": "tsc -w"
|
|
21
27
|
},
|
|
22
28
|
"devDependencies": {
|
|
23
|
-
"@
|
|
29
|
+
"@e280/science": "^0.0.5",
|
|
30
|
+
"@types/node": "^24.2.0",
|
|
24
31
|
"npm-run-all": "^4.1.5",
|
|
25
|
-
"typescript": "^5.
|
|
32
|
+
"typescript": "^5.9.2"
|
|
26
33
|
},
|
|
27
34
|
"keywords": [
|
|
28
35
|
"standard",
|
package/s/clone/clone.test.ts
CHANGED
|
@@ -1,124 +1,124 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
1
|
+
|
|
2
|
+
import {clone} from "./clone.js"
|
|
3
|
+
import {Science, expect} from "@e280/science"
|
|
4
|
+
|
|
5
|
+
function checkMapOrSetEquality(original: any, cloned: any) {
|
|
6
|
+
const originalEntries = JSON.stringify(Array.from(original))
|
|
7
|
+
const clonedEntries = JSON.stringify(Array.from(cloned))
|
|
8
|
+
expect(clonedEntries).is(originalEntries)
|
|
9
|
+
expect(cloned).not.is(original) // check for deep cloning
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default Science.suite({
|
|
13
|
+
|
|
14
|
+
async "clone primitive types"() {
|
|
15
|
+
expect(clone(123)).is(123)
|
|
16
|
+
expect(clone("abc")).is("abc")
|
|
17
|
+
expect(clone(true)).is(true)
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async "clone arrays"() {
|
|
21
|
+
const array = [1, 2, 3]
|
|
22
|
+
const clonedArray = clone(array)
|
|
23
|
+
expect(JSON.stringify(clonedArray)).is(JSON.stringify(array))
|
|
24
|
+
expect(clonedArray).not.is(array) // check for deep cloning
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
async "clone plain objects"() {
|
|
28
|
+
const object = {foo: "bar", baz: 123}
|
|
29
|
+
const clonedObject = clone(object)
|
|
30
|
+
expect(JSON.stringify(clonedObject)).is(JSON.stringify(object))
|
|
31
|
+
expect(clonedObject).not.is(object) // check for deep cloning
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async "clone Map"() {
|
|
35
|
+
const map = new Map()
|
|
36
|
+
map.set("key", "value")
|
|
37
|
+
const clonedMap = clone(map)
|
|
38
|
+
expect(map.get("key")).is("value")
|
|
39
|
+
expect(clonedMap).not.is(map)
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
async "clone Set"() {
|
|
43
|
+
const set = new Set()
|
|
44
|
+
set.add("value")
|
|
45
|
+
const clonedSet = clone(set)
|
|
46
|
+
expect(clonedSet.has("value")).ok()
|
|
47
|
+
expect(clonedSet).not.is(set)
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async "clone Date"() {
|
|
51
|
+
const date = new Date()
|
|
52
|
+
const clonedDate = clone(date)
|
|
53
|
+
expect(clonedDate.getTime()).is(date.getTime())
|
|
54
|
+
expect(clonedDate).not.is(date) // check for deep cloning
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
async "clone objects with circular reference"() {
|
|
58
|
+
const object: any = {foo: "bar"}
|
|
59
|
+
object.self = object // create circular reference
|
|
60
|
+
let error: any
|
|
61
|
+
try {
|
|
62
|
+
clone(object)
|
|
63
|
+
} catch (e) {
|
|
64
|
+
error = e
|
|
65
|
+
}
|
|
66
|
+
expect(error).not.is(undefined)
|
|
67
|
+
expect(error instanceof Error).ok()
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async "clone nested objects"() {
|
|
71
|
+
const object = {foo: "bar", inner: {baz: 123, deeper: {qux: true}}}
|
|
72
|
+
const clonedObject = clone(object)
|
|
73
|
+
expect(JSON.stringify(clonedObject)).is(JSON.stringify(object))
|
|
74
|
+
expect(clonedObject).not.is(object) // check for deep cloning
|
|
75
|
+
expect(clonedObject.inner).not.is(object.inner) // check for deep cloning
|
|
76
|
+
expect(clonedObject.inner.deeper).not.is(object.inner.deeper) // check for deep cloning
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async "clone array of objects"() {
|
|
80
|
+
const array = [{foo: "bar"}, {baz: 123}, {qux: true}]
|
|
81
|
+
const clonedArray = clone(array)
|
|
82
|
+
expect(JSON.stringify(clonedArray)).is(JSON.stringify(array))
|
|
83
|
+
expect(clonedArray).not.is(array) // check for deep cloning
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async "clone object with array"() {
|
|
87
|
+
const object = {foo: "bar", array: [1, 2, 3]}
|
|
88
|
+
const clonedObject = clone(object)
|
|
89
|
+
expect(JSON.stringify(clonedObject)).is(JSON.stringify(object))
|
|
90
|
+
expect(clonedObject).not.is(object) // check for deep cloning
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async "clone object with Map"() {
|
|
94
|
+
const map = new Map()
|
|
95
|
+
map.set("key", "value")
|
|
96
|
+
const object = {foo: "bar", map}
|
|
97
|
+
const clonedObject = clone(object)
|
|
98
|
+
expect(JSON.stringify(clonedObject.foo)).is(JSON.stringify(object.foo))
|
|
99
|
+
checkMapOrSetEquality(object.map, clonedObject.map)
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
async "clone object with duplicate sibling references"() {
|
|
103
|
+
const dupeObject = { id: 1, name: "H4CK3RM4N" }
|
|
104
|
+
const data = { obj1: dupeObject, obj2: dupeObject }
|
|
105
|
+
|
|
106
|
+
let didThrow = false
|
|
107
|
+
let clonedData: typeof data = {} as typeof data
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
clonedData = clone(data)
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
didThrow = true
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
expect(didThrow).not.is(true)
|
|
117
|
+
expect(clonedData.obj1).not.is(clonedData.obj2)
|
|
118
|
+
expect(clonedData.obj1.id).is(1)
|
|
119
|
+
expect(clonedData.obj1.name).is("H4CK3RM4N")
|
|
120
|
+
expect(clonedData.obj2.id).is(1)
|
|
121
|
+
expect(clonedData.obj2.name).is("H4CK3RM4N")
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
124
|
|
package/s/coalesce.ts
CHANGED
package/s/data/base-x.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
|
|
2
|
+
import {Bytes} from "./bytes.js"
|
|
3
|
+
|
|
4
|
+
export type Lexicon = {
|
|
5
|
+
characters: string
|
|
6
|
+
negativePrefix?: string
|
|
7
|
+
padding?: {
|
|
8
|
+
character: string
|
|
9
|
+
size: number
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class BaseX {
|
|
14
|
+
static lexicons = Object.freeze({
|
|
15
|
+
base2: {characters: "01"},
|
|
16
|
+
hex: {characters: "0123456789abcdef"},
|
|
17
|
+
base36: {characters: "0123456789abcdefghijklmnopqrstuvwxyz"},
|
|
18
|
+
base58: {characters: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"},
|
|
19
|
+
base62: {characters: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"},
|
|
20
|
+
base64url: {
|
|
21
|
+
negativePrefix: "~",
|
|
22
|
+
characters: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
|
|
23
|
+
},
|
|
24
|
+
base64: {
|
|
25
|
+
characters: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
|
|
26
|
+
padding: {character: "=", size: 4},
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
private lookup: Record<string, number>
|
|
31
|
+
private negativePrefix: string
|
|
32
|
+
|
|
33
|
+
constructor(public lexicon: Lexicon) {
|
|
34
|
+
this.lookup = Object.fromEntries(
|
|
35
|
+
[...lexicon.characters].map((char, i) => [char, i])
|
|
36
|
+
)
|
|
37
|
+
this.negativePrefix = lexicon.negativePrefix ?? "-"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
toBytes(s: string): Uint8Array {
|
|
41
|
+
const bitsPerChar = Math.log2(this.lexicon.characters.length)
|
|
42
|
+
if (Number.isInteger(bitsPerChar)) {
|
|
43
|
+
// Bitstream mode (for power-of-2 lexicons)
|
|
44
|
+
let bitBuffer = 0
|
|
45
|
+
let bitCount = 0
|
|
46
|
+
const output: number[] = []
|
|
47
|
+
|
|
48
|
+
for (const char of s) {
|
|
49
|
+
if (char === this.lexicon.padding?.character) continue
|
|
50
|
+
const val = this.lookup[char]
|
|
51
|
+
if (val === undefined) throw new Error(`Invalid character: ${char}`)
|
|
52
|
+
bitBuffer = (bitBuffer << bitsPerChar) | val
|
|
53
|
+
bitCount += bitsPerChar
|
|
54
|
+
|
|
55
|
+
while (bitCount >= 8) {
|
|
56
|
+
bitCount -= 8
|
|
57
|
+
output.push((bitBuffer >> bitCount) & 0xFF)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return new Uint8Array(output)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Radix mode fallback
|
|
65
|
+
let num = 0n
|
|
66
|
+
const base = BigInt(this.lexicon.characters.length)
|
|
67
|
+
let negative = false
|
|
68
|
+
if (s.startsWith(this.negativePrefix)) {
|
|
69
|
+
s = s.slice(this.negativePrefix.length)
|
|
70
|
+
negative = true
|
|
71
|
+
}
|
|
72
|
+
for (const char of s) {
|
|
73
|
+
const val = this.lookup[char]
|
|
74
|
+
if (val === undefined) throw new Error(`Invalid character: ${char}`)
|
|
75
|
+
num = num * base + BigInt(val)
|
|
76
|
+
}
|
|
77
|
+
const out: number[] = []
|
|
78
|
+
while (num > 0n) {
|
|
79
|
+
out.unshift(Number(num % 256n))
|
|
80
|
+
num = num / 256n
|
|
81
|
+
}
|
|
82
|
+
return new Uint8Array(out)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fromBytes(bytes: Uint8Array): string {
|
|
86
|
+
const bitsPerChar = Math.log2(this.lexicon.characters.length)
|
|
87
|
+
if (Number.isInteger(bitsPerChar)) {
|
|
88
|
+
// Bitstream mode (for power-of-2 lexicons)
|
|
89
|
+
let bitBuffer = 0
|
|
90
|
+
let bitCount = 0
|
|
91
|
+
let out = ""
|
|
92
|
+
|
|
93
|
+
for (const byte of bytes) {
|
|
94
|
+
bitBuffer = (bitBuffer << 8) | byte
|
|
95
|
+
bitCount += 8
|
|
96
|
+
|
|
97
|
+
while (bitCount >= bitsPerChar) {
|
|
98
|
+
bitCount -= bitsPerChar
|
|
99
|
+
const index = (bitBuffer >> bitCount) & ((1 << bitsPerChar) - 1)
|
|
100
|
+
out += this.lexicon.characters[index]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 🩹 flush remaining bits
|
|
105
|
+
if (bitCount > 0) {
|
|
106
|
+
const index = (bitBuffer << (bitsPerChar - bitCount)) & ((1 << bitsPerChar) - 1)
|
|
107
|
+
out += this.lexicon.characters[index]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Add padding if applicable
|
|
111
|
+
if (this.lexicon.padding) {
|
|
112
|
+
while (out.length % this.lexicon.padding.size !== 0)
|
|
113
|
+
out += this.lexicon.padding.character
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return out
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Radix mode fallback
|
|
120
|
+
let num = 0n
|
|
121
|
+
for (const byte of bytes)
|
|
122
|
+
num = (num << 8n) + BigInt(byte)
|
|
123
|
+
|
|
124
|
+
if (num === 0n) return this.lexicon.characters[0]
|
|
125
|
+
|
|
126
|
+
const base = BigInt(this.lexicon.characters.length)
|
|
127
|
+
let out = ""
|
|
128
|
+
while (num > 0n) {
|
|
129
|
+
out = this.lexicon.characters[Number(num % base)] + out
|
|
130
|
+
num = num / base
|
|
131
|
+
}
|
|
132
|
+
return out
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
toInteger(s: string) {
|
|
136
|
+
if (!s) return 0
|
|
137
|
+
let n = 0n
|
|
138
|
+
let negative = false
|
|
139
|
+
const base = BigInt(this.lexicon.characters.length)
|
|
140
|
+
if (s.startsWith(this.negativePrefix)) {
|
|
141
|
+
s = s.slice(this.negativePrefix.length)
|
|
142
|
+
negative = true
|
|
143
|
+
}
|
|
144
|
+
for (const char of s) {
|
|
145
|
+
const value = this.lookup[char]
|
|
146
|
+
if (value === undefined) throw new Error(`Invalid character: ${char}`)
|
|
147
|
+
n = n * base + BigInt(value)
|
|
148
|
+
}
|
|
149
|
+
return Number(negative ? -n : n)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fromInteger(n: number) {
|
|
153
|
+
n = Math.floor(n)
|
|
154
|
+
const negative = n < 0
|
|
155
|
+
let num = BigInt(negative ? -n : n)
|
|
156
|
+
if (num === 0n) return this.lexicon.characters[0]
|
|
157
|
+
const base = BigInt(this.lexicon.characters.length)
|
|
158
|
+
let out = ""
|
|
159
|
+
while (num > 0n) {
|
|
160
|
+
out = this.lexicon.characters[Number(num % base)] + out
|
|
161
|
+
num = num / base
|
|
162
|
+
}
|
|
163
|
+
return negative ? `${this.negativePrefix}${out}` : out
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
random(count = 32) {
|
|
167
|
+
return this.fromBytes(Bytes.random(count))
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
package/s/data/base58.ts
CHANGED
|
@@ -16,8 +16,8 @@ const base = 58
|
|
|
16
16
|
const characters = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
17
17
|
|
|
18
18
|
export const Base58 = Object.freeze({
|
|
19
|
-
|
|
20
|
-
let intVal = BigInt("0x" + Hex.
|
|
19
|
+
fromBytes(bytes: Uint8Array) {
|
|
20
|
+
let intVal = BigInt("0x" + Hex.fromBytes(bytes))
|
|
21
21
|
let encoded = ""
|
|
22
22
|
|
|
23
23
|
while (intVal > 0) {
|
|
@@ -34,7 +34,7 @@ export const Base58 = Object.freeze({
|
|
|
34
34
|
return encoded
|
|
35
35
|
},
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
toBytes(string: string) {
|
|
38
38
|
let intVal = BigInt(0)
|
|
39
39
|
|
|
40
40
|
for (const char of string) {
|
|
@@ -45,7 +45,7 @@ export const Base58 = Object.freeze({
|
|
|
45
45
|
|
|
46
46
|
let hex = intVal.toString(16)
|
|
47
47
|
if (hex.length % 2 !== 0) hex = "0" + hex
|
|
48
|
-
const bytes = Hex.
|
|
48
|
+
const bytes = Hex.toBytes(hex)
|
|
49
49
|
|
|
50
50
|
let leadingZeroes = 0
|
|
51
51
|
for (const char of string) {
|
|
@@ -59,7 +59,17 @@ export const Base58 = Object.freeze({
|
|
|
59
59
|
},
|
|
60
60
|
|
|
61
61
|
random(count = 32) {
|
|
62
|
-
return this.
|
|
62
|
+
return this.fromBytes(Bytes.random(count))
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/** @deprecated renamed to `fromBytes` */
|
|
66
|
+
string(bytes: Uint8Array) {
|
|
67
|
+
return Base58.fromBytes(bytes)
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/** @deprecated renamed to `toBytes` */
|
|
71
|
+
bytes(string: string) {
|
|
72
|
+
return Base58.toBytes(string)
|
|
63
73
|
},
|
|
64
74
|
})
|
|
65
75
|
|