@e280/stz 0.0.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/license +23 -0
- package/package.json +40 -0
- package/readme.md +85 -0
- package/s/clone/clone.test.ts +124 -0
- package/s/clone/clone.ts +41 -0
- package/s/concurrent.ts +12 -0
- package/s/data/anka.ts +66 -0
- package/s/data/barname/badge.ts +78 -0
- package/s/data/barname/barname.ts +62 -0
- package/s/data/barname/demo.ts +49 -0
- package/s/data/barname/index.ts +4 -0
- package/s/data/barname/utils/prefixes.ts +20 -0
- package/s/data/barname/utils/suffixes.ts +20 -0
- package/s/data/base58.ts +65 -0
- package/s/data/base64.ts +22 -0
- package/s/data/base64url.ts +27 -0
- package/s/data/bytes.ts +7 -0
- package/s/data/hex.ts +35 -0
- package/s/data/nomen/demo.ts +8 -0
- package/s/data/nomen/grammar.ts +41 -0
- package/s/data/nomen/index.ts +5 -0
- package/s/data/nomen/nomen.ts +34 -0
- package/s/data/nomen/presets/falrysk.ts +112 -0
- package/s/data/nomen/types.ts +9 -0
- package/s/data/txt.ts +11 -0
- package/s/deadline.ts +24 -0
- package/s/debounce/debounce.test.ts +73 -0
- package/s/debounce/debounce.ts +54 -0
- package/s/debounce/types.ts +8 -0
- package/s/dedupe.ts +5 -0
- package/s/deep/deep.test.ts +8 -0
- package/s/deep/deep.ts +11 -0
- package/s/deep/parts/equal.test.ts +124 -0
- package/s/deep/parts/equal.ts +49 -0
- package/s/deep/parts/freeze.ts +34 -0
- package/s/defer-promise.ts +29 -0
- package/s/escape-regex.ts +5 -0
- package/s/hat.ts +17 -0
- package/s/index.ts +23 -0
- package/s/is.ts +27 -0
- package/s/map-g.ts +52 -0
- package/s/nap.ts +5 -0
- package/s/ob.ts +66 -0
- package/s/pipe.ts +24 -0
- package/s/pubsub.ts +68 -0
- package/s/trash.ts +30 -0
- package/x/clone/clone.d.ts +1 -0
- package/x/clone/clone.js +26 -0
- package/x/clone/clone.js.map +1 -0
- package/x/clone/clone.test.d.ts +1 -0
- package/x/clone/clone.test.js +125 -0
- package/x/clone/clone.test.js.map +1 -0
- package/x/concurrent.d.ts +1 -0
- package/x/concurrent.js +7 -0
- package/x/concurrent.js.map +1 -0
- package/x/data/anka.d.ts +5 -0
- package/x/data/anka.js +53 -0
- package/x/data/anka.js.map +1 -0
- package/x/data/barname/badge.d.ts +25 -0
- package/x/data/barname/badge.js +64 -0
- package/x/data/barname/badge.js.map +1 -0
- package/x/data/barname/barname.d.ts +7 -0
- package/x/data/barname/barname.js +49 -0
- package/x/data/barname/barname.js.map +1 -0
- package/x/data/barname/demo.d.ts +1 -0
- package/x/data/barname/demo.js +45 -0
- package/x/data/barname/demo.js.map +1 -0
- package/x/data/barname/index.d.ts +2 -0
- package/x/data/barname/index.js +3 -0
- package/x/data/barname/index.js.map +1 -0
- package/x/data/barname/utils/prefixes.d.ts +1 -0
- package/x/data/barname/utils/prefixes.js +19 -0
- package/x/data/barname/utils/prefixes.js.map +1 -0
- package/x/data/barname/utils/suffixes.d.ts +1 -0
- package/x/data/barname/utils/suffixes.js +19 -0
- package/x/data/barname/utils/suffixes.js.map +1 -0
- package/x/data/base58.d.ts +5 -0
- package/x/data/base58.js +58 -0
- package/x/data/base58.js.map +1 -0
- package/x/data/base64.d.ts +5 -0
- package/x/data/base64.js +17 -0
- package/x/data/base64.js.map +1 -0
- package/x/data/base64url.d.ts +5 -0
- package/x/data/base64url.js +22 -0
- package/x/data/base64url.js.map +1 -0
- package/x/data/bytes.d.ts +3 -0
- package/x/data/bytes.js +6 -0
- package/x/data/bytes.js.map +1 -0
- package/x/data/hex.d.ts +6 -0
- package/x/data/hex.js +30 -0
- package/x/data/hex.js.map +1 -0
- package/x/data/nomen/demo.d.ts +1 -0
- package/x/data/nomen/demo.js +5 -0
- package/x/data/nomen/demo.js.map +1 -0
- package/x/data/nomen/grammar.d.ts +11 -0
- package/x/data/nomen/grammar.js +32 -0
- package/x/data/nomen/grammar.js.map +1 -0
- package/x/data/nomen/index.d.ts +3 -0
- package/x/data/nomen/index.js +4 -0
- package/x/data/nomen/index.js.map +1 -0
- package/x/data/nomen/nomen.d.ts +11 -0
- package/x/data/nomen/nomen.js +29 -0
- package/x/data/nomen/nomen.js.map +1 -0
- package/x/data/nomen/presets/falrysk.d.ts +5 -0
- package/x/data/nomen/presets/falrysk.js +99 -0
- package/x/data/nomen/presets/falrysk.js.map +1 -0
- package/x/data/nomen/types.d.ts +6 -0
- package/x/data/nomen/types.js +2 -0
- package/x/data/nomen/types.js.map +1 -0
- package/x/data/txt.d.ts +4 -0
- package/x/data/txt.js +9 -0
- package/x/data/txt.js.map +1 -0
- package/x/deadline.d.ts +6 -0
- package/x/deadline.js +17 -0
- package/x/deadline.js.map +1 -0
- package/x/debounce/debounce.d.ts +2 -0
- package/x/debounce/debounce.js +37 -0
- package/x/debounce/debounce.js.map +1 -0
- package/x/debounce/debounce.test.d.ts +1 -0
- package/x/debounce/debounce.test.js +75 -0
- package/x/debounce/debounce.test.js.map +1 -0
- package/x/debounce/types.d.ts +2 -0
- package/x/debounce/types.js +2 -0
- package/x/debounce/types.js.map +1 -0
- package/x/dedupe.d.ts +1 -0
- package/x/dedupe.js +4 -0
- package/x/dedupe.js.map +1 -0
- package/x/deep/deep.d.ts +7 -0
- package/x/deep/deep.js +9 -0
- package/x/deep/deep.js.map +1 -0
- package/x/deep/deep.test.d.ts +1 -0
- package/x/deep/deep.test.js +10 -0
- package/x/deep/deep.test.js.map +1 -0
- package/x/deep/parts/equal.d.ts +1 -0
- package/x/deep/parts/equal.js +39 -0
- package/x/deep/parts/equal.js.map +1 -0
- package/x/deep/parts/equal.test.d.ts +1 -0
- package/x/deep/parts/equal.test.js +126 -0
- package/x/deep/parts/equal.test.js.map +1 -0
- package/x/deep/parts/freeze.d.ts +1 -0
- package/x/deep/parts/freeze.js +24 -0
- package/x/deep/parts/freeze.js.map +1 -0
- package/x/defer-promise.d.ts +10 -0
- package/x/defer-promise.js +15 -0
- package/x/defer-promise.js.map +1 -0
- package/x/escape-regex.d.ts +1 -0
- package/x/escape-regex.js +4 -0
- package/x/escape-regex.js.map +1 -0
- package/x/hat.d.ts +6 -0
- package/x/hat.js +16 -0
- package/x/hat.js.map +1 -0
- package/x/index.d.ts +20 -0
- package/x/index.js +21 -0
- package/x/index.js.map +1 -0
- package/x/is.d.ts +10 -0
- package/x/is.js +11 -0
- package/x/is.js.map +1 -0
- package/x/map-g.d.ts +17 -0
- package/x/map-g.js +40 -0
- package/x/map-g.js.map +1 -0
- package/x/nap.d.ts +1 -0
- package/x/nap.js +2 -0
- package/x/nap.js.map +1 -0
- package/x/ob.d.ts +14 -0
- package/x/ob.js +21 -0
- package/x/ob.js.map +1 -0
- package/x/pipe.d.ts +8 -0
- package/x/pipe.js +16 -0
- package/x/pipe.js.map +1 -0
- package/x/pubsub.d.ts +18 -0
- package/x/pubsub.js +40 -0
- package/x/pubsub.js.map +1 -0
- package/x/trash.d.ts +14 -0
- package/x/trash.js +25 -0
- package/x/trash.js.map +1 -0
package/license
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
MIT License
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025 Chase Moskal
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
23
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@e280/stz",
|
|
3
|
+
"version": "0.0.0-0",
|
|
4
|
+
"description": "everyday ts fns for everything",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Chase Moskal <chasemoskal@gmail.com>",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "x/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"x",
|
|
11
|
+
"s"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "run-s _clean _links _tsc",
|
|
15
|
+
"_clean": "rm -rf x && mkdir x",
|
|
16
|
+
"_links": "ln -s \"$(realpath node_modules)\" x/node_modules",
|
|
17
|
+
"_tsc": "tsc",
|
|
18
|
+
"start": "exit 0",
|
|
19
|
+
"count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +",
|
|
20
|
+
"test": "exit 0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.14.1",
|
|
24
|
+
"npm-run-all": "^4.1.5",
|
|
25
|
+
"typescript": "^5.8.3"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"standard",
|
|
29
|
+
"std",
|
|
30
|
+
"steeze"
|
|
31
|
+
],
|
|
32
|
+
"homepage": "https://github.com/e280/stz#readme",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/e280/stz.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/e280/stz/issues"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
|
|
2
|
+
# `@e280/stz`
|
|
3
|
+
standard library of environment-agnostic typescript functions we use basically everywhere
|
|
4
|
+
|
|
5
|
+
<br/>
|
|
6
|
+
|
|
7
|
+
## the tools
|
|
8
|
+
stz has many more tools than documented below, see their [sourcecode here in s/](./s/)
|
|
9
|
+
|
|
10
|
+
### MapG — an extension of js Map with handy methods
|
|
11
|
+
- `map.require`
|
|
12
|
+
```ts
|
|
13
|
+
import {MapG} from "@e280/stz"
|
|
14
|
+
|
|
15
|
+
const map = new MapG<number, string>([
|
|
16
|
+
[1, "hello"],
|
|
17
|
+
[2, "world"],
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
// throws error if the value is undefined
|
|
21
|
+
const value = map.require(1)
|
|
22
|
+
```
|
|
23
|
+
- `map.guarantee`
|
|
24
|
+
```ts
|
|
25
|
+
// if the value is undefined, the new value "rofl" is set and returned
|
|
26
|
+
const value = map.guarantee(3, () => "rofl")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### pub and sub — minimal ergonomic event system
|
|
30
|
+
- make a publisher fn
|
|
31
|
+
```ts
|
|
32
|
+
import {pub} from "@e280/stz"
|
|
33
|
+
|
|
34
|
+
// create a pub fn
|
|
35
|
+
const sendMessage = pub<[string]>()
|
|
36
|
+
|
|
37
|
+
// subscribe to it
|
|
38
|
+
sendMessage.sub(m => console.log(m))
|
|
39
|
+
|
|
40
|
+
// publish to it
|
|
41
|
+
sendMessage("hello")
|
|
42
|
+
```
|
|
43
|
+
- make a subscriber fn *(see how it's just the reverse of pub?)*
|
|
44
|
+
```ts
|
|
45
|
+
import {sub} from "@e280/stz"
|
|
46
|
+
|
|
47
|
+
// create a sub fn
|
|
48
|
+
const onMessage = sub<[string]>()
|
|
49
|
+
|
|
50
|
+
// subscribe to it
|
|
51
|
+
onMessage(m => console.log(m))
|
|
52
|
+
|
|
53
|
+
// publish to it
|
|
54
|
+
onMessage.pub("hello")
|
|
55
|
+
```
|
|
56
|
+
- the pub and sub are the same, but have differing invoke signatures
|
|
57
|
+
- i seem to use `sub` more often
|
|
58
|
+
- both have some extra functionality
|
|
59
|
+
```ts
|
|
60
|
+
// pub fns return a promise, to wait for all async subscribers
|
|
61
|
+
await sendMessage("hello")
|
|
62
|
+
await onMessage.pub("hello")
|
|
63
|
+
|
|
64
|
+
// sub fns return an unsub fn
|
|
65
|
+
const unsub1 = onMessage(m => console.log(m))
|
|
66
|
+
unsub1() // unsubscribe that listener
|
|
67
|
+
|
|
68
|
+
const unsub2 = sendMessage.sub(m => console.log(m))
|
|
69
|
+
unsub2() // unsubscribe that listener
|
|
70
|
+
|
|
71
|
+
// you can clear all subscribers from a pub or a sub
|
|
72
|
+
sendMessage.clear()
|
|
73
|
+
onMessage.clear()
|
|
74
|
+
|
|
75
|
+
// you can subscribe to only one next call
|
|
76
|
+
onMessage.once(m => console.log(m))
|
|
77
|
+
sendMessage.once(m => console.log(m))
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
<br/>
|
|
81
|
+
|
|
82
|
+
## 💖 made with open source love
|
|
83
|
+
reward us with github stars
|
|
84
|
+
build with us at https://e280.org/ but only if you're cool
|
|
85
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
//
|
|
2
|
+
// import {Suite, expect} from "cynic"
|
|
3
|
+
// import {clone} from "./clone.js"
|
|
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).equals(originalEntries)
|
|
9
|
+
// expect(cloned).not.equals(original) // check for deep cloning
|
|
10
|
+
// }
|
|
11
|
+
//
|
|
12
|
+
// export default <Suite>{
|
|
13
|
+
//
|
|
14
|
+
// async "clone primitive types"() {
|
|
15
|
+
// expect(clone(123)).equals(123)
|
|
16
|
+
// expect(clone("abc")).equals("abc")
|
|
17
|
+
// expect(clone(true)).equals(true)
|
|
18
|
+
// },
|
|
19
|
+
//
|
|
20
|
+
// async "clone arrays"() {
|
|
21
|
+
// const array = [1, 2, 3]
|
|
22
|
+
// const clonedArray = clone(array)
|
|
23
|
+
// expect(JSON.stringify(clonedArray)).equals(JSON.stringify(array))
|
|
24
|
+
// expect(clonedArray).not.equals(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)).equals(JSON.stringify(object))
|
|
31
|
+
// expect(clonedObject).not.equals(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")).equals("value")
|
|
39
|
+
// expect(clonedMap).not.equals(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.equals(set)
|
|
48
|
+
// },
|
|
49
|
+
//
|
|
50
|
+
// async "clone Date"() {
|
|
51
|
+
// const date = new Date()
|
|
52
|
+
// const clonedDate = clone(date)
|
|
53
|
+
// expect(clonedDate.getTime()).equals(date.getTime())
|
|
54
|
+
// expect(clonedDate).not.equals(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.equals(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)).equals(JSON.stringify(object))
|
|
74
|
+
// expect(clonedObject).not.equals(object) // check for deep cloning
|
|
75
|
+
// expect(clonedObject.inner).not.equals(object.inner) // check for deep cloning
|
|
76
|
+
// expect(clonedObject.inner.deeper).not.equals(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)).equals(JSON.stringify(array))
|
|
83
|
+
// expect(clonedArray).not.equals(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)).equals(JSON.stringify(object))
|
|
90
|
+
// expect(clonedObject).not.equals(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)).equals(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.equals(true)
|
|
117
|
+
// expect(clonedData.obj1).not.equals(clonedData.obj2)
|
|
118
|
+
// expect(clonedData.obj1.id).equals(1)
|
|
119
|
+
// expect(clonedData.obj1.name).equals("H4CK3RM4N")
|
|
120
|
+
// expect(clonedData.obj2.id).equals(1)
|
|
121
|
+
// expect(clonedData.obj2.name).equals("H4CK3RM4N")
|
|
122
|
+
// },
|
|
123
|
+
// }
|
|
124
|
+
|
package/s/clone/clone.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
|
|
2
|
+
export function clone<T>(data: T, refs = new Set<any>()): T {
|
|
3
|
+
|
|
4
|
+
if (refs.has(data))
|
|
5
|
+
throw new Error("cannot clone circular reference")
|
|
6
|
+
|
|
7
|
+
let copy = undefined as T
|
|
8
|
+
|
|
9
|
+
if (typeof data === "function" || (data !== null && typeof data === "object")) {
|
|
10
|
+
refs.add(data)
|
|
11
|
+
|
|
12
|
+
if (Array.isArray(data))
|
|
13
|
+
copy = data.map(d => clone(d, new Set(refs))) as T
|
|
14
|
+
|
|
15
|
+
else if (data.constructor === Object)
|
|
16
|
+
copy = Object.fromEntries(
|
|
17
|
+
Object.entries(data)
|
|
18
|
+
.map(([key, d]) => [key, clone(d, new Set(refs))])
|
|
19
|
+
) as T
|
|
20
|
+
|
|
21
|
+
else if (data instanceof Map)
|
|
22
|
+
copy = new Map(Array.from(data, ([key, val]) => [key, clone(val, new Set(refs))])) as T
|
|
23
|
+
|
|
24
|
+
else if (data instanceof Set)
|
|
25
|
+
copy = new Set(Array.from(data, val => clone(val, new Set(refs)))) as T
|
|
26
|
+
|
|
27
|
+
else if (data instanceof Date)
|
|
28
|
+
copy = new Date(data.getTime()) as T
|
|
29
|
+
|
|
30
|
+
else
|
|
31
|
+
copy = data
|
|
32
|
+
|
|
33
|
+
refs.delete(data) // Remove current data from refs once we're done with this branch of the object tree
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
else
|
|
37
|
+
copy = data
|
|
38
|
+
|
|
39
|
+
return copy
|
|
40
|
+
}
|
|
41
|
+
|
package/s/concurrent.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
export async function concurrent<O extends Record<string, Promise<any>>>(
|
|
3
|
+
ob: O,
|
|
4
|
+
) {
|
|
5
|
+
const entries = Object.entries(ob)
|
|
6
|
+
const promises = entries.map(([,promise]) => promise)
|
|
7
|
+
const values = await Promise.all(promises)
|
|
8
|
+
return (
|
|
9
|
+
Object.fromEntries(entries.map(([key], index) => [key, values[index]]))
|
|
10
|
+
) as {[K in keyof O]: Awaited<O[K]>}
|
|
11
|
+
}
|
|
12
|
+
|
package/s/data/anka.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
|
|
4
|
+
"anka" is an alternative to base64 that i totally made up.
|
|
5
|
+
- it looks cool because it features characters from the indian subcontinent.
|
|
6
|
+
- each byte is represented by one character, which makes a 256 bit id incomparably short at only 32 characters.
|
|
7
|
+
- however, it's probably a bad idea, for these negative reasons:
|
|
8
|
+
1. many of these characters are actually 2-or-3-byte sequences (so even though anka is visually more compact than base64 to the user, it's actually more data in terms of bytes).
|
|
9
|
+
2. it's horrible for url-encoding.
|
|
10
|
+
3. it's horrible for trying to manually copy by typing.
|
|
11
|
+
|
|
12
|
+
256-bit anka ids look like this:
|
|
13
|
+
|
|
14
|
+
ஒഐওଆઊসਅവஎଉखਬശயഘसചവਭଏரঊझഠடଓઐഴঠണവઞ
|
|
15
|
+
ठକநइஎਦਏஒഊটਹஜഴयભকயঙਪഉजकঝഐवઠসഏਓਡਟऋ
|
|
16
|
+
થঞठખஉધഫரਜऐફരਪਙਗഭਣഎफઇমએরଔડਉதਤஅदઘച
|
|
17
|
+
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import {Bytes} from "./bytes.js"
|
|
21
|
+
|
|
22
|
+
const characters = [...new Set([
|
|
23
|
+
// devanagari
|
|
24
|
+
..."अआइईउऊऋएऐओऔकखगघङचछजझञटठडढणतथदधनपफबभमयरलवशषसह",
|
|
25
|
+
|
|
26
|
+
// tamil
|
|
27
|
+
..."அஆஇஈஉஊஎஏஐஒஓகஙசஜஞடணதநபமயரலவழளறன",
|
|
28
|
+
|
|
29
|
+
// bengali
|
|
30
|
+
..."অআইঈউঊএঐওকখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহ",
|
|
31
|
+
|
|
32
|
+
// gurmukhi
|
|
33
|
+
..."ਅਆਇਈਉਊਏਐਓਕਖਗਘਙਚਛਜਝਞਟਠਡਢਣਤਥਦਧਨਪਫਬਭਮਯਰਲਵਸਹ",
|
|
34
|
+
|
|
35
|
+
// malayalam
|
|
36
|
+
..."അആഇഈഉഊഎഏഐഒഓകഖഗഘങചഛജഝഞടഠഡഢണതഥദധനപഫബഭമയരലവശസഹളഴറ",
|
|
37
|
+
|
|
38
|
+
// gujarati
|
|
39
|
+
..."અઆઇઈઉઊએઐઓકખગઘઙચછજઝઞટઠડઢણતથદધનપફબભમયરલવશષસહ",
|
|
40
|
+
|
|
41
|
+
// odia
|
|
42
|
+
..."ଅଆଇଈଉଊଏଐଓଔକଖଗଘଙଚଛଜଝଞଟଠଡଢଣତଥଦଧନପଫବଭମଯରଲଶଷସହ",
|
|
43
|
+
])]
|
|
44
|
+
|
|
45
|
+
export const Anka = Object.freeze({
|
|
46
|
+
|
|
47
|
+
string(bytes: Uint8Array) {
|
|
48
|
+
return [...bytes]
|
|
49
|
+
.map(int => characters.at(int))
|
|
50
|
+
.join("")
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
bytes(code: string) {
|
|
54
|
+
return new Uint8Array([...code].map(char => {
|
|
55
|
+
const index = characters.indexOf(char)
|
|
56
|
+
if (index === -1)
|
|
57
|
+
throw new Error(`invalid anka character "${char}"`)
|
|
58
|
+
return index
|
|
59
|
+
}))
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
random(count = 32) {
|
|
63
|
+
return this.string(Bytes.random(count))
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
import {Hex} from "../hex.js"
|
|
3
|
+
import {Base58} from "../base58.js"
|
|
4
|
+
import {Barname} from "./barname.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Badge is a human-friendly presentation format for arbitrary binary data.
|
|
8
|
+
* - looks like "nomluc_rigpem.tg2bjNkjMh1H6M2b2EhD5V4x6XAqx9wyWddsBt"
|
|
9
|
+
* - the first bytes are shown in barname format
|
|
10
|
+
* - the rest of the data is in base58
|
|
11
|
+
* - designed to be a nice way to present 256-bit passport thumbprints
|
|
12
|
+
* - can be used for data of different lengths
|
|
13
|
+
* - preview size can be customized as 'leadCount', which defaults to 4
|
|
14
|
+
*/
|
|
15
|
+
export class Badge {
|
|
16
|
+
static readonly separator = "."
|
|
17
|
+
static readonly defaultLeadCount = 4
|
|
18
|
+
|
|
19
|
+
readonly hex: string
|
|
20
|
+
readonly string: string
|
|
21
|
+
readonly preview: string
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
public readonly bytes: Uint8Array,
|
|
25
|
+
public readonly leadCount = Badge.defaultLeadCount,
|
|
26
|
+
) {
|
|
27
|
+
|
|
28
|
+
if (leadCount < 1)
|
|
29
|
+
throw new Error(`badge leadCount must be greater than 0 (was ${leadCount})`)
|
|
30
|
+
|
|
31
|
+
this.hex = Hex.string(this.bytes)
|
|
32
|
+
this.preview = Barname.string(this.bytes.slice(0, leadCount))
|
|
33
|
+
|
|
34
|
+
this.string = (bytes.length > leadCount)
|
|
35
|
+
? `${this.preview}.${Base58.string(this.bytes.slice(leadCount))}`
|
|
36
|
+
: this.preview
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static parse(badge: string) {
|
|
40
|
+
const [barname, b58] = badge.split(Badge.separator)
|
|
41
|
+
|
|
42
|
+
// badge has a base58 component
|
|
43
|
+
if (b58) {
|
|
44
|
+
const appetizer = Barname.bytes(barname)
|
|
45
|
+
const entree = Base58.bytes(b58)
|
|
46
|
+
const bytes = new Uint8Array([...appetizer, ...entree])
|
|
47
|
+
return new this(bytes, appetizer.length)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// badge is just a barname (no base58 part)
|
|
51
|
+
else {
|
|
52
|
+
const bytes = Barname.bytes(barname)
|
|
53
|
+
return new this(bytes, bytes.length)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static fromHex(hex: string, leadCount = this.defaultLeadCount) {
|
|
58
|
+
const bytes = Hex.bytes(hex)
|
|
59
|
+
return new this(bytes, leadCount)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static string(bytes: Uint8Array, leadCount = this.defaultLeadCount) {
|
|
63
|
+
return new this(bytes, leadCount).string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static bytes(badge: string) {
|
|
67
|
+
return this.parse(badge)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static hex(badge: string) {
|
|
71
|
+
return this.parse(badge).hex
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
toString() {
|
|
75
|
+
return this.string
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
import {Bytes} from "../bytes.js"
|
|
3
|
+
import {prefixes} from "./utils/prefixes.js"
|
|
4
|
+
import {suffixes} from "./utils/suffixes.js"
|
|
5
|
+
|
|
6
|
+
const wordSeparator = "_"
|
|
7
|
+
const groupSeparator = " "
|
|
8
|
+
const defaultWordsPerGroup = 4
|
|
9
|
+
|
|
10
|
+
export const Barname = {
|
|
11
|
+
random(byteCount: number) {
|
|
12
|
+
return this.string(Bytes.random(byteCount))
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
string(bytes: Uint8Array, options?: {wordsPerGroup?: number}) {
|
|
16
|
+
const groupSize = options?.wordsPerGroup ?? defaultWordsPerGroup
|
|
17
|
+
|
|
18
|
+
const words: string[] = []
|
|
19
|
+
let currentWord: string[] = []
|
|
20
|
+
|
|
21
|
+
bytes.forEach((byte, index) => {
|
|
22
|
+
const source = ((index % 2) === 0) ? prefixes : suffixes
|
|
23
|
+
currentWord.push(source[byte]!)
|
|
24
|
+
if (currentWord.length === 2) {
|
|
25
|
+
words.push(currentWord.join(""))
|
|
26
|
+
currentWord = []
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if (currentWord.length)
|
|
31
|
+
words.push(currentWord.join(""))
|
|
32
|
+
|
|
33
|
+
const grouped = []
|
|
34
|
+
for (let i = 0; i < words.length; i += groupSize)
|
|
35
|
+
grouped.push(words.slice(i, i + groupSize).join(wordSeparator))
|
|
36
|
+
|
|
37
|
+
return grouped.join(groupSeparator)
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
bytes(string: string) {
|
|
41
|
+
const letters = string
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.replace(/[^a-z]/g, "") // strip everything except letters
|
|
44
|
+
|
|
45
|
+
const count = letters.length / 3
|
|
46
|
+
if ((count % 1) !== 0)
|
|
47
|
+
throw new Error(`invalid triplet count, ${letters.length} does not divide into triplets`)
|
|
48
|
+
|
|
49
|
+
const triplets: string[] = []
|
|
50
|
+
for (let i = 0; i < letters.length; i += 3)
|
|
51
|
+
triplets.push(letters.slice(i, i + 3))
|
|
52
|
+
|
|
53
|
+
return new Uint8Array(triplets.map((triplet, index) => {
|
|
54
|
+
const source = ((index % 2) === 0) ? prefixes : suffixes
|
|
55
|
+
const number = source.findIndex(t => t === triplet)
|
|
56
|
+
if (number === -1)
|
|
57
|
+
throw new Error(`unknown triplet ${triplet}`)
|
|
58
|
+
return number
|
|
59
|
+
}))
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
import {Hex} from "../hex.js"
|
|
3
|
+
import {Badge} from "./badge.js"
|
|
4
|
+
import {Bytes} from "../bytes.js"
|
|
5
|
+
import {Barname} from "./barname.js"
|
|
6
|
+
|
|
7
|
+
for (const _ of Array(2)) {
|
|
8
|
+
const string = Barname.random(34)
|
|
9
|
+
const bytes = Barname.bytes(string)
|
|
10
|
+
const string2 = Barname.string(bytes)
|
|
11
|
+
console.log(string.replaceAll(" ", "\n"))
|
|
12
|
+
if (string !== string2)
|
|
13
|
+
throw new Error("barname fails equality check")
|
|
14
|
+
console.log("")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
for (const _ of Array(10)) {
|
|
18
|
+
const bytes = Bytes.random(32)
|
|
19
|
+
const badge = new Badge(bytes)
|
|
20
|
+
const bytes2 = badge.bytes
|
|
21
|
+
console.log(badge.string)
|
|
22
|
+
if (Hex.string(bytes) !== Hex.string(bytes2))
|
|
23
|
+
throw new Error("badge fails equality check")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
console.log("")
|
|
28
|
+
const alpha = `
|
|
29
|
+
dacsyd_labdun_fipsub_bonmel
|
|
30
|
+
fitlys_rovbud_sipnel_sonryx
|
|
31
|
+
sitrem_lasdyr_dovpun_nibwep
|
|
32
|
+
tippet_mapnub_ticnet_famset
|
|
33
|
+
wanteg
|
|
34
|
+
`
|
|
35
|
+
const bravo = `
|
|
36
|
+
dacsyd-labdun-fipsub-bonmel;
|
|
37
|
+
[fitlys rovbud sipnel sonryx]
|
|
38
|
+
sitrem/lasdyr/dovpun/nibwep
|
|
39
|
+
tippet+mapnub+ticnet+famset
|
|
40
|
+
wanteg++
|
|
41
|
+
`
|
|
42
|
+
const alphaHex = Hex.string(Barname.bytes(alpha))
|
|
43
|
+
const bravoHex = Hex.string(Barname.bytes(bravo))
|
|
44
|
+
if (alphaHex.length === 68 && alphaHex === bravoHex)
|
|
45
|
+
console.log("*barname parsing appears resilient to whitespace and garbage*")
|
|
46
|
+
else
|
|
47
|
+
throw new Error("barname resiliency failure")
|
|
48
|
+
}
|
|
49
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
export const prefixes = [
|
|
3
|
+
"doz","mar","bin","wan","sam","lit","sig","hid","fid","lis","sog","dir","wac","sab","wis","sib",
|
|
4
|
+
"rig","sol","dop","mod","fog","lid","hop","dar","dor","lor","hod","fol","rin","tog","sil","mir",
|
|
5
|
+
"hol","pas","lac","rov","liv","dal","sat","lib","tab","han","tic","pid","tor","bol","fos","dot",
|
|
6
|
+
"los","dil","for","pil","ram","tir","win","tad","bic","dif","roc","wid","bis","das","mid","lop",
|
|
7
|
+
"ril","nar","dap","mol","san","loc","nov","sit","nid","tip","sic","rop","wit","nat","pan","min",
|
|
8
|
+
"rit","pod","mot","tam","tol","sav","pos","nap","nop","som","fin","fon","ban","mor","wor","sip",
|
|
9
|
+
"ron","nor","bot","wic","soc","wat","dol","mag","pic","dav","bid","bal","tim","tas","mal","lig",
|
|
10
|
+
"siv","tag","pad","sal","div","dac","tan","sid","fab","tar","mon","ran","nis","wol","mis","pal",
|
|
11
|
+
"las","dis","map","rab","tob","rol","lat","lon","nod","nav","fig","nom","nib","pag","sop","ral",
|
|
12
|
+
"bil","had","doc","rid","moc","pac","rav","rip","fal","tod","til","tin","hap","mic","fan","pat",
|
|
13
|
+
"tac","lab","mog","sim","son","pin","lom","ric","tap","fir","has","bos","bat","poc","hac","tid",
|
|
14
|
+
"hav","sap","lin","dib","hos","dab","bit","bar","rac","par","lod","dos","bor","toc","hil","mac",
|
|
15
|
+
"tom","dig","fil","fas","mit","hob","har","mig","hin","rad","mas","hal","rag","lag","fad","top",
|
|
16
|
+
"mop","hab","nil","nos","mil","fop","fam","dat","nol","din","hat","nac","ris","fot","rib","hoc",
|
|
17
|
+
"nim","lar","fit","wal","rap","sar","nal","mos","lan","don","dan","lad","dov","riv","bac","pol",
|
|
18
|
+
"lap","tal","pit","nam","bon","ros","ton","fod","pon","sov","noc","sor","lav","mat","mip","fip",
|
|
19
|
+
]
|
|
20
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
export const suffixes = [
|
|
3
|
+
"zod","nec","bud","wes","sev","per","sut","let","ful","pen","syt","dur","wep","ser","wyl","sun",
|
|
4
|
+
"ryp","syx","dyr","nup","heb","peg","lup","dep","dys","put","lug","hec","ryt","tyv","syd","nex",
|
|
5
|
+
"lun","mep","lut","sep","pes","del","sul","ped","tem","led","tul","met","wen","byn","hex","feb",
|
|
6
|
+
"pyl","dul","het","mev","rut","tyl","wyd","tep","bes","dex","sef","wyc","bur","der","nep","pur",
|
|
7
|
+
"rys","reb","den","nut","sub","pet","rul","syn","reg","tyd","sup","sem","wyn","rec","meg","net",
|
|
8
|
+
"sec","mul","nym","tev","web","sum","mut","nyx","rex","teb","fus","hep","ben","mus","wyx","sym",
|
|
9
|
+
"sel","ruc","dec","wex","syr","wet","dyl","myn","mes","det","bet","bel","tux","tug","myr","pel",
|
|
10
|
+
"syp","ter","meb","set","dut","deg","tex","sur","fel","tud","nux","rux","ren","wyt","nub","med",
|
|
11
|
+
"lyt","dus","neb","rum","tyn","seg","lyx","pun","res","red","fun","rev","ref","mec","ted","rus",
|
|
12
|
+
"bex","leb","dux","ryn","num","pyx","ryg","ryx","fep","tyr","tus","tyc","leg","nem","fer","mer",
|
|
13
|
+
"ten","lus","nus","syl","tec","mex","pub","rym","tuc","fyl","lep","deb","ber","mug","hut","tun",
|
|
14
|
+
"byl","sud","pem","dev","lur","def","bus","bep","run","mel","pex","dyt","byt","typ","lev","myl",
|
|
15
|
+
"wed","duc","fur","fex","nul","luc","len","ner","lex","rup","ned","lec","ryd","lyd","fen","wel",
|
|
16
|
+
"nyd","hus","rel","rud","nes","hes","fet","des","ret","dun","ler","nyr","seb","hul","ryl","lud",
|
|
17
|
+
"rem","lys","fyn","wer","ryc","sug","nys","nyl","lyn","dyn","dem","lux","fed","sed","bec","mun",
|
|
18
|
+
"lyr","tes","mud","nyt","byr","sen","weg","fyr","mur","tel","rep","teg","pec","nel","nev","fes",
|
|
19
|
+
]
|
|
20
|
+
|