@e280/stz 0.0.0-3 → 0.0.0-31
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 +285 -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 +5 -6
- 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/deadline.d.ts +2 -2
- package/x/deadline.js +4 -4
- package/x/deadline.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/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
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
import {Bytes} from "../bytes.js"
|
|
3
|
+
import {Bytename} from "./bytename.js"
|
|
4
|
+
import {Science, test, expect} from "@e280/science"
|
|
5
|
+
|
|
6
|
+
const deadbeef = new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF])
|
|
7
|
+
|
|
8
|
+
function good(bytes: Uint8Array) {
|
|
9
|
+
expect(Bytes.eq(bytes, deadbeef)).ok()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default Science.suite({
|
|
13
|
+
"bytes->string->bytes": test(async() => {
|
|
14
|
+
const text = Bytename.fromBytes(deadbeef)
|
|
15
|
+
expect(text).is("ribmug.hilmun")
|
|
16
|
+
good(Bytename.toBytes(text))
|
|
17
|
+
}),
|
|
18
|
+
|
|
19
|
+
"zero bytes": test(async() => {
|
|
20
|
+
const text = Bytename.fromBytes(new Uint8Array([]))
|
|
21
|
+
expect(text).is("")
|
|
22
|
+
}),
|
|
23
|
+
|
|
24
|
+
"one byte": test(async() => {
|
|
25
|
+
const text = Bytename.fromBytes(new Uint8Array([0x00]))
|
|
26
|
+
expect(text).is("doz")
|
|
27
|
+
}),
|
|
28
|
+
|
|
29
|
+
"groupings": test(async() => {
|
|
30
|
+
const bytes = new Uint8Array([...deadbeef, ...deadbeef])
|
|
31
|
+
const text = Bytename.fromBytes(bytes, {
|
|
32
|
+
groupSize: 2,
|
|
33
|
+
groupSeparator: " ",
|
|
34
|
+
wordSeparator: ".",
|
|
35
|
+
})
|
|
36
|
+
expect(text).is("ribmug.hilmun ribmug.hilmun")
|
|
37
|
+
}),
|
|
38
|
+
|
|
39
|
+
"wordsep": Science.suite({
|
|
40
|
+
"underscore": test(async() => good(Bytename.toBytes("ribmug_hilmun"))),
|
|
41
|
+
"uppercase": test(async() => good(Bytename.toBytes("RIBMUG_HILMUN"))),
|
|
42
|
+
"dots": test(async() => good(Bytename.toBytes("ribmug.hilmun"))),
|
|
43
|
+
"crushed": test(async() => good(Bytename.toBytes("ribmughilmun"))),
|
|
44
|
+
"space": test(async() => good(Bytename.toBytes("ribmug hilmun"))),
|
|
45
|
+
"spaces": test(async() => good(Bytename.toBytes("ribmug hilmun"))),
|
|
46
|
+
"whitespace": test(async() => good(Bytename.toBytes("\n ribmug \n \t \n hilmun \n"))),
|
|
47
|
+
}),
|
|
48
|
+
})
|
|
49
|
+
|
|
@@ -1,19 +1,39 @@
|
|
|
1
1
|
|
|
2
|
+
import {Hex} from "../hex.js"
|
|
2
3
|
import {Bytes} from "../bytes.js"
|
|
3
4
|
import {prefixes} from "./utils/prefixes.js"
|
|
4
5
|
import {suffixes} from "./utils/suffixes.js"
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export type BytenameOptions = {
|
|
8
|
+
groupSize: number
|
|
9
|
+
wordSeparator: string
|
|
10
|
+
groupSeparator: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Bytename is a friendly presentation format for arbitrary binary data.
|
|
15
|
+
* - looks like "midsen.picmyn.widrep.baclut dotreg.filtyp.nosnus.siptev"
|
|
16
|
+
* - each byte maps to a three-letter triplet
|
|
17
|
+
* - all delimiters are just sugar, parser doesn't care
|
|
18
|
+
*/
|
|
19
|
+
export const Bytename = {
|
|
20
|
+
defaults: (<BytenameOptions>{
|
|
21
|
+
groupSize: 4,
|
|
22
|
+
wordSeparator: ".",
|
|
23
|
+
groupSeparator: " ",
|
|
24
|
+
}),
|
|
9
25
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return this.
|
|
26
|
+
random(byteCount: number, options?: Partial<BytenameOptions>) {
|
|
27
|
+
const bytes = Bytes.random(byteCount)
|
|
28
|
+
return this.fromBytes(bytes, options)
|
|
13
29
|
},
|
|
14
30
|
|
|
15
|
-
|
|
16
|
-
const
|
|
31
|
+
fromBytes(bytes: Uint8Array, options: Partial<BytenameOptions> = {}) {
|
|
32
|
+
const {
|
|
33
|
+
groupSize = Bytename.defaults.groupSize,
|
|
34
|
+
wordSeparator = Bytename.defaults.wordSeparator,
|
|
35
|
+
groupSeparator = Bytename.defaults.groupSeparator,
|
|
36
|
+
} = options
|
|
17
37
|
|
|
18
38
|
const words: string[] = []
|
|
19
39
|
let currentWord: string[] = []
|
|
@@ -37,8 +57,8 @@ export const Barname = {
|
|
|
37
57
|
return grouped.join(groupSeparator)
|
|
38
58
|
},
|
|
39
59
|
|
|
40
|
-
|
|
41
|
-
const letters =
|
|
60
|
+
toBytes(bytename: string) {
|
|
61
|
+
const letters = bytename
|
|
42
62
|
.toLowerCase()
|
|
43
63
|
.replace(/[^a-z]/g, "") // strip everything except letters
|
|
44
64
|
|
|
@@ -58,5 +78,13 @@ export const Barname = {
|
|
|
58
78
|
return number
|
|
59
79
|
}))
|
|
60
80
|
},
|
|
81
|
+
|
|
82
|
+
toHex(bytename: string) {
|
|
83
|
+
return Hex.fromBytes(Bytename.toBytes(bytename))
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
fromHex(hex: string, options?: Partial<BytenameOptions>) {
|
|
87
|
+
return Bytename.fromBytes(Hex.toBytes(hex), options)
|
|
88
|
+
},
|
|
61
89
|
}
|
|
62
90
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
import {Science, test, expect} from "@e280/science"
|
|
3
|
+
import {Hex} from "../hex.js"
|
|
4
|
+
import {Bytes} from "../bytes.js"
|
|
5
|
+
import {Thumbprint} from "./thumbprint.js"
|
|
6
|
+
|
|
7
|
+
const sampleThumbprint = "nodlyn.fasrep.habbud.ralwel.Avo7gFmdWMRHkwsD149mcaBoZdS69iXuJ"
|
|
8
|
+
const sampleHex = "88e8c3fad1028fcf6ce5ac491578850f4d833336feca03b608265501c3019d59"
|
|
9
|
+
const sampleBytes = Hex.toBytes(sampleHex)
|
|
10
|
+
|
|
11
|
+
function good(bytes: Uint8Array) {
|
|
12
|
+
expect(Bytes.eq(bytes, sampleBytes)).ok()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default Science.suite({
|
|
16
|
+
"bytes->string->bytes": test(async() => {
|
|
17
|
+
const text = Thumbprint.fromBytes(sampleBytes)
|
|
18
|
+
expect(text).is(sampleThumbprint)
|
|
19
|
+
good(Thumbprint.toBytes(text))
|
|
20
|
+
}),
|
|
21
|
+
|
|
22
|
+
"zero bytes": test(async() => {
|
|
23
|
+
const text = Thumbprint.fromBytes(new Uint8Array([]))
|
|
24
|
+
expect(text).is("")
|
|
25
|
+
expect(Bytes.eq(Thumbprint.toBytes(text), new Uint8Array([]))).ok()
|
|
26
|
+
}),
|
|
27
|
+
|
|
28
|
+
"one byte": test(async() => {
|
|
29
|
+
const text = Thumbprint.fromBytes(new Uint8Array([0x00]))
|
|
30
|
+
expect(text).is("doz")
|
|
31
|
+
expect(Bytes.eq(Thumbprint.toBytes(text), new Uint8Array([0x00]))).ok()
|
|
32
|
+
}),
|
|
33
|
+
|
|
34
|
+
"partially": Science.suite({
|
|
35
|
+
"normal": test(async() => expect(Thumbprint.toBytes(sampleThumbprint).length).ok()),
|
|
36
|
+
"nothing": test(async() => expect(Thumbprint.toBytes("").length).is(0)),
|
|
37
|
+
"no-bulk": test(async() => expect(Thumbprint.toBytes("nodlyn.fasrep").length).ok()),
|
|
38
|
+
"one-byte": test(async() => expect(Thumbprint.toBytes("nod").length).is(1)),
|
|
39
|
+
"three-byte": test(async() => expect(Thumbprint.toBytes("nodlynfas").length).is(3)),
|
|
40
|
+
"one-byte-sigil": test(async() => expect(Thumbprint.toBytes("nod::39gfeGFAAnBzH5pkT7EdoETMUMAekG9h1iymk6k").length).ok()),
|
|
41
|
+
}),
|
|
42
|
+
|
|
43
|
+
"tolerance": Science.suite({
|
|
44
|
+
"normal": test(async() => good(Thumbprint.toBytes("nodlyn.fasrep::39gfeGFAAnBzH5pkT7EdoETMUMAekG9h1iymk6k"))),
|
|
45
|
+
"coolio": test(async() => good(Thumbprint.toBytes("nodlyn.fasrep..39gfeGFAAnBzH5pkT7EdoETMUMAekG9h1iymk6k"))),
|
|
46
|
+
"spaces": test(async() => good(Thumbprint.toBytes("nodlyn fasrep 39gfeGFAAnBzH5pkT7EdoETMUMAekG9h1iymk6k"))),
|
|
47
|
+
"dots": test(async() => good(Thumbprint.toBytes("nodlyn.fasrep.39gfeGFAAnBzH5pkT7EdoETMUMAekG9h1iymk6k"))),
|
|
48
|
+
"one-space": test(async() => good(Thumbprint.toBytes("nodlynfasrep 39gfeGFAAnBzH5pkT7EdoETMUMAekG9h1iymk6k"))),
|
|
49
|
+
"one-newline": test(async() => good(Thumbprint.toBytes("nodlynfasrep\n39gfeGFAAnBzH5pkT7EdoETMUMAekG9h1iymk6k"))),
|
|
50
|
+
"whitespace": test(async() => good(Thumbprint.toBytes("\t\n nodlyn\n fasrep \n\n\t\n 39gfeGFAAnBzH5pkT7EdoETMUMAekG9h1iymk6k\n\t"))),
|
|
51
|
+
}),
|
|
52
|
+
})
|
|
53
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
|
|
2
|
+
import {Hex} from "../hex.js"
|
|
3
|
+
import {Base58} from "../base58.js"
|
|
4
|
+
import {Bytename} from "./bytename.js"
|
|
5
|
+
|
|
6
|
+
export type ThumbprintOptions = {
|
|
7
|
+
delimiter: string
|
|
8
|
+
sigilBytes: number
|
|
9
|
+
previewBytes: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ThumbprintData = {
|
|
13
|
+
bytes: Uint8Array
|
|
14
|
+
thumbprint: string
|
|
15
|
+
preview: string
|
|
16
|
+
bulk: string
|
|
17
|
+
sigil: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Thumbprint is a friendly presentation format for arbitrary binary data.
|
|
22
|
+
* - looks like "nodlyn.fasrep.habbud.ralwel.Avo7gFmdWMRHkwsD149mcaBoZdS69iXuJ"
|
|
23
|
+
* - the preview: "nodlyn.fasrep.habbud.ralwel" (bytename format)
|
|
24
|
+
* - the bulk: "Avo7gFmdWMRHkwsD149mcaBoZdS69iXuJ" (base58 format)
|
|
25
|
+
* - the sigil: "nodlyn.fasrep" (shorter part of the bytename)
|
|
26
|
+
* - originally designed to be a nice way to present 256-bit ids, but can actually represent any number of bytes
|
|
27
|
+
*/
|
|
28
|
+
export const Thumbprint = {
|
|
29
|
+
defaults: (<ThumbprintOptions>{
|
|
30
|
+
delimiter: ".",
|
|
31
|
+
sigilBytes: 4,
|
|
32
|
+
previewBytes: 8,
|
|
33
|
+
}),
|
|
34
|
+
|
|
35
|
+
toBytes(thumbprint: string) {
|
|
36
|
+
thumbprint = thumbprint.trim()
|
|
37
|
+
const beans = thumbprint.split(/[^a-zA-Z0-9]+/m)
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.map(s => s.trim())
|
|
40
|
+
|
|
41
|
+
if (beans.length < 2)
|
|
42
|
+
return Bytename.toBytes(beans.join(""))
|
|
43
|
+
|
|
44
|
+
const bulk = beans.pop()!
|
|
45
|
+
const preview = beans.join("")
|
|
46
|
+
|
|
47
|
+
return new Uint8Array([
|
|
48
|
+
...Bytename.toBytes(preview),
|
|
49
|
+
...Base58.toBytes(bulk),
|
|
50
|
+
])
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
parse(thumbprint: string, options?: Partial<ThumbprintOptions>): ThumbprintData {
|
|
54
|
+
const bytes = Thumbprint.toBytes(thumbprint)
|
|
55
|
+
return Thumbprint.build.fromBytes(bytes, options)
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
build: {
|
|
59
|
+
fromBytes(bytes: Uint8Array, options: Partial<ThumbprintOptions> = {}): ThumbprintData {
|
|
60
|
+
const {delimiter, previewBytes, sigilBytes}
|
|
61
|
+
= {...Thumbprint.defaults, ...options}
|
|
62
|
+
|
|
63
|
+
const yoink = (b: number) => (bytes.length > 0)
|
|
64
|
+
? Bytename.fromBytes(bytes.slice(0, b), {
|
|
65
|
+
wordSeparator: delimiter,
|
|
66
|
+
groupSeparator: delimiter,
|
|
67
|
+
})
|
|
68
|
+
: ""
|
|
69
|
+
|
|
70
|
+
const sigil = yoink(sigilBytes)
|
|
71
|
+
const preview = yoink(previewBytes)
|
|
72
|
+
|
|
73
|
+
const bulk = (bytes.length > previewBytes)
|
|
74
|
+
? Base58.fromBytes(bytes.slice(previewBytes))
|
|
75
|
+
: ""
|
|
76
|
+
|
|
77
|
+
const thumbprint = [preview, bulk]
|
|
78
|
+
.filter(s => s.length > 0)
|
|
79
|
+
.join(delimiter)
|
|
80
|
+
|
|
81
|
+
return {bytes, thumbprint, preview, bulk, sigil}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
fromHex(hex: string, options?: Partial<ThumbprintOptions>) {
|
|
85
|
+
const bytes = Hex.toBytes(hex)
|
|
86
|
+
return Thumbprint.build.fromBytes(bytes, options)
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
toHex(thumbprint: string) {
|
|
91
|
+
const bytes = Thumbprint.toBytes(thumbprint)
|
|
92
|
+
return Hex.fromBytes(bytes)
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
fromBytes(bytes: Uint8Array, options?: Partial<ThumbprintOptions>) {
|
|
96
|
+
return Thumbprint.build.fromBytes(bytes, options).thumbprint
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
fromHex(hex: string, options?: Partial<ThumbprintOptions>) {
|
|
100
|
+
return Thumbprint.fromBytes(Hex.toBytes(hex), options)
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
sigil: {
|
|
104
|
+
fromHex(hex: string, options?: Partial<ThumbprintOptions>) {
|
|
105
|
+
return Thumbprint.build.fromHex(hex, options).sigil
|
|
106
|
+
},
|
|
107
|
+
fromBytes(bytes: Uint8Array, options?: Partial<ThumbprintOptions>) {
|
|
108
|
+
return Thumbprint.build.fromBytes(bytes, options).sigil
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
package/s/data/bytes.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
export const Bytes = {
|
|
2
|
+
export const Bytes = Object.freeze({
|
|
3
|
+
eq(a: Uint8Array, b: Uint8Array) {
|
|
4
|
+
if (a.length !== b.length)
|
|
5
|
+
return false
|
|
6
|
+
for (let i = 0; i <= a.length; i++) {
|
|
7
|
+
if (a.at(i) !== b.at(i))
|
|
8
|
+
return false
|
|
9
|
+
}
|
|
10
|
+
return true
|
|
11
|
+
},
|
|
12
|
+
|
|
3
13
|
random(count: number) {
|
|
4
14
|
return crypto.getRandomValues(new Uint8Array(count))
|
|
5
15
|
},
|
|
6
|
-
}
|
|
16
|
+
})
|
|
7
17
|
|