@e280/stz 0.0.0-2 → 0.0.0-20
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 → README.md} +42 -6
- package/package.json +8 -5
- package/s/clone/clone.test.ts +123 -123
- package/s/coalesce.ts +6 -0
- 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 +10 -0
- package/s/deadline.ts +7 -4
- 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 +20 -0
- package/s/id-counter.ts +9 -0
- package/s/index.ts +11 -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/pubsub.ts +20 -2
- package/s/tests.test.ts +17 -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 +2 -0
- package/x/coalesce.js +5 -0
- package/x/coalesce.js.map +1 -0
- 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 +1 -0
- package/x/data/bytes.js +9 -0
- package/x/data/bytes.js.map +1 -1
- package/x/deadline.d.ts +3 -2
- package/x/deadline.js +8 -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 +4 -0
- package/x/drill.js +12 -0
- package/x/drill.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 +9 -2
- package/x/index.js +9 -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/pubsub.d.ts +11 -0
- package/x/pubsub.js +4 -2
- package/x/pubsub.js.map +1 -1
- package/x/tests.test.js +14 -0
- package/x/tests.test.js.map +1 -0
- 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/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 → README.md}
RENAMED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
|
|
2
2
|
# `@e280/stz`
|
|
3
3
|
standard library of environment-agnostic typescript functions we use basically everywhere
|
|
4
|
+
- zero dependencies
|
|
4
5
|
|
|
5
6
|
<br/>
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
### MapG — an extension of js Map with handy methods
|
|
8
|
+
## MapG
|
|
9
|
+
### extended js map
|
|
11
10
|
- `map.require`
|
|
12
11
|
```ts
|
|
13
12
|
import {MapG} from "@e280/stz"
|
|
@@ -26,7 +25,10 @@ stz has many more tools than documented below, see their [sourcecode here in s/]
|
|
|
26
25
|
const value = map.guarantee(3, () => "rofl")
|
|
27
26
|
```
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
<br/>
|
|
29
|
+
|
|
30
|
+
## pub and sub
|
|
31
|
+
### ergonomic event emitters
|
|
30
32
|
- make a publisher fn
|
|
31
33
|
```ts
|
|
32
34
|
import {pub} from "@e280/stz"
|
|
@@ -79,7 +81,41 @@ stz has many more tools than documented below, see their [sourcecode here in s/]
|
|
|
79
81
|
|
|
80
82
|
<br/>
|
|
81
83
|
|
|
82
|
-
##
|
|
84
|
+
## Bytename
|
|
85
|
+
### friendly string encoding for binary data
|
|
86
|
+
|
|
87
|
+
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
|
+
|
|
89
|
+
the bytename parser (`Bytename.toBytes`) ignores all non-alphabetic characters. thus `midsen.picmyn`, `midsenpicmyn`, and `mid@sen$pic@myn` are all equal.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import {Bytename} from "@e280/stz"
|
|
93
|
+
```
|
|
94
|
+
- ```ts
|
|
95
|
+
Bytename.fromBytes(new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF]))
|
|
96
|
+
// "ribmug.hilmun"
|
|
97
|
+
```
|
|
98
|
+
- ```ts
|
|
99
|
+
Bytename.toBytes("ribmug.hilmun")
|
|
100
|
+
// Uint8Array, 4 bytes
|
|
101
|
+
```
|
|
102
|
+
- ```ts
|
|
103
|
+
const bytes = new Uint8Array([
|
|
104
|
+
0xDE, 0xAD, 0xBE, 0xEF,
|
|
105
|
+
0xDE, 0xAD, 0xBE, 0xEF,
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
Bytename.fromBytes(bytes, {
|
|
109
|
+
groupSize: 2, // default is 4
|
|
110
|
+
groupSeparator: " ",
|
|
111
|
+
wordSeparator: ".",
|
|
112
|
+
})
|
|
113
|
+
// "ribmug.hilmun ribmug.hilmun"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
<br/>
|
|
117
|
+
|
|
118
|
+
## 💖 stz is made with open source love
|
|
83
119
|
reward us with github stars
|
|
84
120
|
build with us at https://e280.org/ but only if you're cool
|
|
85
121
|
|
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-20",
|
|
4
4
|
"description": "everyday ts fns for everything",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chase Moskal <chasemoskal@gmail.com>",
|
|
@@ -12,15 +12,18 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "run-s _clean _links _tsc",
|
|
15
|
+
"test": "node x/tests.test.js",
|
|
16
|
+
"count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +",
|
|
17
|
+
"start": "run-p _tscw _testw",
|
|
15
18
|
"_clean": "rm -rf x && mkdir x",
|
|
16
19
|
"_links": "ln -s \"$(realpath node_modules)\" x/node_modules",
|
|
17
20
|
"_tsc": "tsc",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"test": "exit 0"
|
|
21
|
+
"_tscw": "tsc -w",
|
|
22
|
+
"_testw": "node --watch x/tests.test.js"
|
|
21
23
|
},
|
|
22
24
|
"devDependencies": {
|
|
23
|
-
"@
|
|
25
|
+
"@e280/science": "^0.0.4",
|
|
26
|
+
"@types/node": "^22.15.21",
|
|
24
27
|
"npm-run-all": "^4.1.5",
|
|
25
28
|
"typescript": "^5.8.3"
|
|
26
29
|
},
|
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
ADDED
|
@@ -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.string(Bytename.toBytes(bytename))
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
fromHex(hex: string, options?: Partial<BytenameOptions>) {
|
|
87
|
+
return Bytename.fromBytes(Hex.bytes(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.bytes(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.bytes(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.string(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.bytes(hex)
|
|
86
|
+
return Thumbprint.build.fromBytes(bytes, options)
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
toHex(thumbprint: string) {
|
|
91
|
+
const bytes = Thumbprint.toBytes(thumbprint)
|
|
92
|
+
return Hex.string(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.bytes(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,5 +1,15 @@
|
|
|
1
1
|
|
|
2
2
|
export const Bytes = {
|
|
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
|
},
|