@e280/stz 0.0.0-3 → 0.0.0-30

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