@e280/stz 0.0.0-20 → 0.0.0-22
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 +56 -7
- package/package.json +7 -6
- 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.ts +2 -2
- package/s/data/bytename/thumbprint.test.ts +1 -1
- package/s/data/bytename/thumbprint.ts +5 -5
- package/s/data/bytes.ts +2 -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/index.ts +1 -0
- package/s/tests.test.ts +12 -10
- 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.js +2 -2
- package/x/data/bytename/bytename.js.map +1 -1
- package/x/data/bytename/thumbprint.js +5 -5
- package/x/data/bytename/thumbprint.js.map +1 -1
- package/x/data/bytename/thumbprint.test.js +1 -1
- package/x/data/bytename/thumbprint.test.js.map +1 -1
- package/x/data/bytes.d.ts +2 -2
- package/x/data/bytes.js +2 -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/index.d.ts +1 -0
- package/x/index.js +1 -0
- package/x/index.js.map +1 -1
- package/x/tests.test.js +12 -10
- package/x/tests.test.js.map +1 -1
- package/s/data/anka.ts +0 -66
- package/x/data/anka.d.ts +0 -5
- package/x/data/anka.js +0 -53
- package/x/data/anka.js.map +0 -1
package/README.md
CHANGED
|
@@ -81,8 +81,57 @@ standard library of environment-agnostic typescript functions we use basically e
|
|
|
81
81
|
|
|
82
82
|
<br/>
|
|
83
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
|
+
|
|
84
133
|
## Bytename
|
|
85
|
-
|
|
134
|
+
> friendly string encoding for binary data
|
|
86
135
|
|
|
87
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
|
|
88
137
|
|
|
@@ -105,12 +154,12 @@ import {Bytename} from "@e280/stz"
|
|
|
105
154
|
0xDE, 0xAD, 0xBE, 0xEF,
|
|
106
155
|
])
|
|
107
156
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
157
|
+
Bytename.fromBytes(bytes, {
|
|
158
|
+
groupSize: 2, // default is 4
|
|
159
|
+
groupSeparator: " ",
|
|
160
|
+
wordSeparator: ".",
|
|
161
|
+
})
|
|
162
|
+
// "ribmug.hilmun ribmug.hilmun"
|
|
114
163
|
```
|
|
115
164
|
|
|
116
165
|
<br/>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e280/stz",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-22",
|
|
4
4
|
"description": "everyday ts fns for everything",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chase Moskal <chasemoskal@gmail.com>",
|
|
@@ -13,17 +13,18 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "run-s _clean _links _tsc",
|
|
15
15
|
"test": "node x/tests.test.js",
|
|
16
|
+
"test-watch": "node --watch x/tests.test.js",
|
|
17
|
+
"test-inspect": "node inspect x/tests.test.js",
|
|
16
18
|
"count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +",
|
|
17
|
-
"
|
|
19
|
+
"watch": "run-p _tscw test-watch",
|
|
18
20
|
"_clean": "rm -rf x && mkdir x",
|
|
19
21
|
"_links": "ln -s \"$(realpath node_modules)\" x/node_modules",
|
|
20
22
|
"_tsc": "tsc",
|
|
21
|
-
"_tscw": "tsc -w"
|
|
22
|
-
"_testw": "node --watch x/tests.test.js"
|
|
23
|
+
"_tscw": "tsc -w"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
|
-
"@e280/science": "^0.0.
|
|
26
|
-
"@types/node": "^22.15.
|
|
26
|
+
"@e280/science": "^0.0.5",
|
|
27
|
+
"@types/node": "^22.15.23",
|
|
27
28
|
"npm-run-all": "^4.1.5",
|
|
28
29
|
"typescript": "^5.8.3"
|
|
29
30
|
},
|
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
|
|
package/s/data/base64.ts
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
|
|
2
2
|
import {Bytes} from "./bytes.js"
|
|
3
3
|
|
|
4
|
-
export const Base64 = {
|
|
5
|
-
|
|
6
|
-
string(bytes: Uint8Array) {
|
|
4
|
+
export const Base64 = Object.freeze({
|
|
5
|
+
fromBytes(bytes: Uint8Array) {
|
|
7
6
|
return (typeof btoa === "function")
|
|
8
7
|
? btoa(String.fromCharCode(...bytes))
|
|
9
8
|
: Buffer.from(bytes).toString("base64")
|
|
10
9
|
},
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
toBytes(string: string) {
|
|
13
12
|
return (typeof atob === "function")
|
|
14
13
|
? Uint8Array.from(atob(string), char => char.charCodeAt(0))
|
|
15
14
|
: Uint8Array.from(Buffer.from(string, "base64"))
|
|
16
15
|
},
|
|
17
16
|
|
|
18
17
|
random(count = 32) {
|
|
19
|
-
return this.
|
|
18
|
+
return this.fromBytes(Bytes.random(count))
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
/** @deprecated rename to "fromBytes" */
|
|
22
|
+
string(bytes: Uint8Array) {
|
|
23
|
+
return Base64.fromBytes(bytes)
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
/** @deprecated rename to "toBytes" */
|
|
27
|
+
bytes(string: string) {
|
|
28
|
+
return Base64.toBytes(string)
|
|
20
29
|
},
|
|
21
|
-
}
|
|
30
|
+
})
|
|
22
31
|
|
package/s/data/base64url.ts
CHANGED
|
@@ -3,25 +3,34 @@ import {Bytes} from "./bytes.js"
|
|
|
3
3
|
import {Base64} from "./base64.js"
|
|
4
4
|
|
|
5
5
|
export const Base64url = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return Base64.string(bytes)
|
|
6
|
+
fromBytes(bytes: Uint8Array) {
|
|
7
|
+
return Base64.fromBytes(bytes)
|
|
9
8
|
.replace(/\+/g, "-")
|
|
10
9
|
.replace(/\//g, "_")
|
|
11
10
|
.replace(/=+$/g, "")
|
|
12
11
|
},
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
toBytes(string: string) {
|
|
15
14
|
let b64 = string
|
|
16
15
|
.replace(/-/g, "+")
|
|
17
16
|
.replace(/_/g, "/")
|
|
18
17
|
if (b64.length % 4 !== 0)
|
|
19
18
|
b64 = b64.padEnd(b64.length + (4 - b64.length % 4) % 4, "=")
|
|
20
|
-
return Base64.
|
|
19
|
+
return Base64.toBytes(b64)
|
|
21
20
|
},
|
|
22
21
|
|
|
23
22
|
random(count = 32) {
|
|
24
|
-
return this.
|
|
23
|
+
return this.fromBytes(Bytes.random(count))
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
/** @deprecated renamed to "fromBytes" */
|
|
27
|
+
string(bytes: Uint8Array) {
|
|
28
|
+
return Base64url.fromBytes(bytes)
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/** @deprecated renamed to "toBytes" */
|
|
32
|
+
bytes(string: string) {
|
|
33
|
+
return Base64url.toBytes(string)
|
|
25
34
|
},
|
|
26
35
|
}
|
|
27
36
|
|
|
@@ -80,11 +80,11 @@ export const Bytename = {
|
|
|
80
80
|
},
|
|
81
81
|
|
|
82
82
|
toHex(bytename: string) {
|
|
83
|
-
return Hex.
|
|
83
|
+
return Hex.fromBytes(Bytename.toBytes(bytename))
|
|
84
84
|
},
|
|
85
85
|
|
|
86
86
|
fromHex(hex: string, options?: Partial<BytenameOptions>) {
|
|
87
|
-
return Bytename.fromBytes(Hex.
|
|
87
|
+
return Bytename.fromBytes(Hex.toBytes(hex), options)
|
|
88
88
|
},
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -6,7 +6,7 @@ import {Thumbprint} from "./thumbprint.js"
|
|
|
6
6
|
|
|
7
7
|
const sampleThumbprint = "nodlyn.fasrep.habbud.ralwel.Avo7gFmdWMRHkwsD149mcaBoZdS69iXuJ"
|
|
8
8
|
const sampleHex = "88e8c3fad1028fcf6ce5ac491578850f4d833336feca03b608265501c3019d59"
|
|
9
|
-
const sampleBytes = Hex.
|
|
9
|
+
const sampleBytes = Hex.toBytes(sampleHex)
|
|
10
10
|
|
|
11
11
|
function good(bytes: Uint8Array) {
|
|
12
12
|
expect(Bytes.eq(bytes, sampleBytes)).ok()
|
|
@@ -46,7 +46,7 @@ export const Thumbprint = {
|
|
|
46
46
|
|
|
47
47
|
return new Uint8Array([
|
|
48
48
|
...Bytename.toBytes(preview),
|
|
49
|
-
...Base58.
|
|
49
|
+
...Base58.toBytes(bulk),
|
|
50
50
|
])
|
|
51
51
|
},
|
|
52
52
|
|
|
@@ -71,7 +71,7 @@ export const Thumbprint = {
|
|
|
71
71
|
const preview = yoink(previewBytes)
|
|
72
72
|
|
|
73
73
|
const bulk = (bytes.length > previewBytes)
|
|
74
|
-
? Base58.
|
|
74
|
+
? Base58.fromBytes(bytes.slice(previewBytes))
|
|
75
75
|
: ""
|
|
76
76
|
|
|
77
77
|
const thumbprint = [preview, bulk]
|
|
@@ -82,14 +82,14 @@ export const Thumbprint = {
|
|
|
82
82
|
},
|
|
83
83
|
|
|
84
84
|
fromHex(hex: string, options?: Partial<ThumbprintOptions>) {
|
|
85
|
-
const bytes = Hex.
|
|
85
|
+
const bytes = Hex.toBytes(hex)
|
|
86
86
|
return Thumbprint.build.fromBytes(bytes, options)
|
|
87
87
|
},
|
|
88
88
|
},
|
|
89
89
|
|
|
90
90
|
toHex(thumbprint: string) {
|
|
91
91
|
const bytes = Thumbprint.toBytes(thumbprint)
|
|
92
|
-
return Hex.
|
|
92
|
+
return Hex.fromBytes(bytes)
|
|
93
93
|
},
|
|
94
94
|
|
|
95
95
|
fromBytes(bytes: Uint8Array, options?: Partial<ThumbprintOptions>) {
|
|
@@ -97,7 +97,7 @@ export const Thumbprint = {
|
|
|
97
97
|
},
|
|
98
98
|
|
|
99
99
|
fromHex(hex: string, options?: Partial<ThumbprintOptions>) {
|
|
100
|
-
return Thumbprint.fromBytes(Hex.
|
|
100
|
+
return Thumbprint.fromBytes(Hex.toBytes(hex), options)
|
|
101
101
|
},
|
|
102
102
|
|
|
103
103
|
sigil: {
|
package/s/data/bytes.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
export const Bytes = {
|
|
2
|
+
export const Bytes = Object.freeze({
|
|
3
3
|
eq(a: Uint8Array, b: Uint8Array) {
|
|
4
4
|
if (a.length !== b.length)
|
|
5
5
|
return false
|
|
@@ -13,5 +13,5 @@ export const Bytes = {
|
|
|
13
13
|
random(count: number) {
|
|
14
14
|
return crypto.getRandomValues(new Uint8Array(count))
|
|
15
15
|
},
|
|
16
|
-
}
|
|
16
|
+
})
|
|
17
17
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
|
|
2
|
+
import {Science, test, expect} from "@e280/science"
|
|
3
|
+
import {ob} from "../ob.js"
|
|
4
|
+
import {Txt} from "./txt.js"
|
|
5
|
+
import {Hex} from "./hex.js"
|
|
6
|
+
import {Bytes} from "./bytes.js"
|
|
7
|
+
import {BaseX} from "./base-x.js"
|
|
8
|
+
import {Base58} from "./base58.js"
|
|
9
|
+
import {Base64} from "./base64.js"
|
|
10
|
+
import {Base64url} from "./base64url.js"
|
|
11
|
+
|
|
12
|
+
const sampleBytes = Hex.toBytes("9960cd633a46acfe8307d8a400e842da0d930a75fb8188e0f5da264e4b6b4e5b")
|
|
13
|
+
|
|
14
|
+
type ByteUtil = {
|
|
15
|
+
toBytes: (string: string) => Uint8Array
|
|
16
|
+
fromBytes: (bytes: Uint8Array) => string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type NumberUtil = {
|
|
20
|
+
toInteger: (s: string) => number
|
|
21
|
+
fromInteger: (n: number) => string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function testBytes(util: ByteUtil) {
|
|
25
|
+
return async() => {
|
|
26
|
+
const string = util.fromBytes(sampleBytes)
|
|
27
|
+
const recreated = util.toBytes(string)
|
|
28
|
+
expect(Bytes.eq(sampleBytes, recreated)).ok()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function testNumbers(util: NumberUtil) {
|
|
33
|
+
return {
|
|
34
|
+
positive: async() => {
|
|
35
|
+
const original = 123456789
|
|
36
|
+
const string = util.fromInteger(original)
|
|
37
|
+
const recreated = util.toInteger(string)
|
|
38
|
+
expect(original).is(recreated)
|
|
39
|
+
expect(util.toInteger("")).is(0)
|
|
40
|
+
},
|
|
41
|
+
negative: async() => {
|
|
42
|
+
const original = -123456789
|
|
43
|
+
const string = util.fromInteger(original)
|
|
44
|
+
const recreated = util.toInteger(string)
|
|
45
|
+
expect(original).is(recreated)
|
|
46
|
+
expect(util.toInteger("")).is(0)
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function testBoth(util: ByteUtil & NumberUtil) {
|
|
52
|
+
return {
|
|
53
|
+
bytes: testBytes(util),
|
|
54
|
+
numbers: testNumbers(util),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function testCompat(alpha: ByteUtil, bravo: ByteUtil) {
|
|
59
|
+
return async() => {
|
|
60
|
+
expect(alpha.fromBytes(sampleBytes))
|
|
61
|
+
.is(bravo.fromBytes(sampleBytes))
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default Science.suite({
|
|
66
|
+
"Base58": testBytes(Base58),
|
|
67
|
+
"Base64": testBytes(Base64),
|
|
68
|
+
"Base64url": testBytes(Base64url),
|
|
69
|
+
"Hex": testBytes(Hex),
|
|
70
|
+
|
|
71
|
+
"Txt": test(async() => {
|
|
72
|
+
const original = `build or die. 💻>☠️ 命火工`
|
|
73
|
+
const bytes = Txt.toBytes(original)
|
|
74
|
+
const recreated = Txt.fromBytes(bytes)
|
|
75
|
+
expect(original).is(recreated)
|
|
76
|
+
}),
|
|
77
|
+
|
|
78
|
+
"BaseX": Science.suite({
|
|
79
|
+
lexicons: Science.suite(
|
|
80
|
+
ob(BaseX.lexicons).map(lex => testBoth(new BaseX(lex)))
|
|
81
|
+
),
|
|
82
|
+
compat: Science.suite({
|
|
83
|
+
"Hex": testCompat(Hex, new BaseX(BaseX.lexicons.hex)),
|
|
84
|
+
"Base64": testCompat(Base64, new BaseX(BaseX.lexicons.base64)),
|
|
85
|
+
"Base64url": testCompat(Base64url, new BaseX(BaseX.lexicons.base64url)),
|
|
86
|
+
}),
|
|
87
|
+
"base64 has padding": test(async() => {
|
|
88
|
+
const s = new BaseX(BaseX.lexicons.base64).fromBytes(sampleBytes)
|
|
89
|
+
expect(s.endsWith("=")).ok()
|
|
90
|
+
}),
|
|
91
|
+
"antagonistic base64 positive integer": test(async() => {
|
|
92
|
+
const base64url = new BaseX(BaseX.lexicons.base64url)
|
|
93
|
+
expect(base64url.toInteger(base64url.fromInteger(62))).is(62)
|
|
94
|
+
expect(base64url.toInteger(base64url.fromInteger(4030))).is(4030)
|
|
95
|
+
}),
|
|
96
|
+
}),
|
|
97
|
+
})
|
|
98
|
+
|
package/s/data/hex.ts
CHANGED
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
|
|
12
12
|
import {Bytes} from "./bytes.js"
|
|
13
13
|
|
|
14
|
-
export const Hex = {
|
|
15
|
-
|
|
14
|
+
export const Hex = Object.freeze({
|
|
15
|
+
fromBytes(bytes: Uint8Array) {
|
|
16
16
|
return [...bytes]
|
|
17
17
|
.map(byte => byte.toString(16).padStart(2, "0"))
|
|
18
18
|
.join("")
|
|
19
19
|
},
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
toBytes(string: string) {
|
|
22
22
|
if (string.length % 2 !== 0)
|
|
23
23
|
throw new Error("must have even number of hex characters")
|
|
24
24
|
const bytes = new Uint8Array(string.length / 2)
|
|
@@ -29,7 +29,17 @@ export const Hex = {
|
|
|
29
29
|
|
|
30
30
|
/** generate a random hex string. byteCount defaults to 32. */
|
|
31
31
|
random(byteCount = 32) {
|
|
32
|
-
return this.
|
|
32
|
+
return this.fromBytes(Bytes.random(byteCount))
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/** @deprecated renamed to `fromBytes` */
|
|
36
|
+
string(bytes: Uint8Array) {
|
|
37
|
+
return Hex.fromBytes(bytes)
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/** @deprecated renamed to `toBytes` */
|
|
41
|
+
bytes(string: string) {
|
|
42
|
+
return Hex.toBytes(string)
|
|
33
43
|
},
|
|
34
|
-
}
|
|
44
|
+
})
|
|
35
45
|
|
package/s/data/txt.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
|
|
2
|
-
export const Txt = {
|
|
3
|
-
|
|
2
|
+
export const Txt = Object.freeze({
|
|
3
|
+
fromBytes(bytes: Uint8Array) {
|
|
4
4
|
return new TextDecoder().decode(bytes)
|
|
5
5
|
},
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
toBytes(string: string) {
|
|
8
8
|
return new TextEncoder().encode(string)
|
|
9
9
|
},
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
/** @deprecated renamed to `fromBytes` */
|
|
12
|
+
string(bytes: Uint8Array) {
|
|
13
|
+
return Txt.fromBytes(bytes)
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
/** @deprecated renamed to `toBytes` */
|
|
17
|
+
bytes(string: string) {
|
|
18
|
+
return Txt.toBytes(string)
|
|
19
|
+
},
|
|
20
|
+
})
|
|
11
21
|
|
package/s/index.ts
CHANGED
package/s/tests.test.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
|
|
2
2
|
import {Science} from "@e280/science"
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
4
|
+
import clone from "./clone/clone.test.js"
|
|
5
|
+
import thumbprint from "./data/bytename/thumbprint.test.js"
|
|
6
|
+
import debounce from "./debounce/debounce.test.js"
|
|
7
|
+
import bytename from "./data/bytename/bytename.test.js"
|
|
8
|
+
import deep from "./deep/deep.test.js"
|
|
9
|
+
import data from "./data/data.test.js"
|
|
9
10
|
|
|
10
11
|
await Science.run({
|
|
11
|
-
clone
|
|
12
|
-
bytename
|
|
13
|
-
thumbprint
|
|
14
|
-
debounce
|
|
15
|
-
deep
|
|
12
|
+
clone,
|
|
13
|
+
bytename,
|
|
14
|
+
thumbprint,
|
|
15
|
+
debounce,
|
|
16
|
+
deep,
|
|
17
|
+
data,
|
|
16
18
|
})
|
|
17
19
|
|