turtb 0.5.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 +661 -0
- package/bin/turtb.js +259 -0
- package/lib/display/h.js +204 -0
- package/lib/display/helpers.js +78 -0
- package/lib/display/render.js +177 -0
- package/lib/display/shapes.js +91 -0
- package/lib/turtle/Signer.js +120 -0
- package/lib/turtle/Signer.test.js +38 -0
- package/lib/turtle/TurtleBranch.js +147 -0
- package/lib/turtle/TurtleBranch.test.js +89 -0
- package/lib/turtle/TurtleDictionary.js +157 -0
- package/lib/turtle/TurtleDictionary.test.js +331 -0
- package/lib/turtle/U8aTurtle.js +203 -0
- package/lib/turtle/U8aTurtle.test.js +60 -0
- package/lib/turtle/Workspace.js +62 -0
- package/lib/turtle/Workspace.test.js +63 -0
- package/lib/turtle/codecs/CodecType.js +36 -0
- package/lib/turtle/codecs/CodecTypeVersion.js +37 -0
- package/lib/turtle/codecs/Commit.js +10 -0
- package/lib/turtle/codecs/CompositeCodec.js +86 -0
- package/lib/turtle/codecs/TreeNode.js +38 -0
- package/lib/turtle/codecs/codec.js +441 -0
- package/lib/turtle/connections/AbstractUpdater.js +176 -0
- package/lib/turtle/connections/TurtleBranchMultiplexer.js +102 -0
- package/lib/turtle/connections/TurtleBranchMultiplexer.test.js +26 -0
- package/lib/turtle/connections/TurtleBranchUpdater.js +47 -0
- package/lib/turtle/connections/TurtleDB.js +165 -0
- package/lib/turtle/connections/TurtleDB.test.js +45 -0
- package/lib/turtle/connections/TurtleTalker.js +34 -0
- package/lib/turtle/connections/TurtleTalker.test.js +101 -0
- package/lib/turtle/utils.js +192 -0
- package/lib/turtle/utils.test.js +158 -0
- package/lib/utils/Assert.js +115 -0
- package/lib/utils/NestedSet.js +68 -0
- package/lib/utils/NestedSet.test.js +30 -0
- package/lib/utils/OWN_KEYS.js +1 -0
- package/lib/utils/Recaller.js +175 -0
- package/lib/utils/Recaller.test.js +75 -0
- package/lib/utils/TestRunner.js +200 -0
- package/lib/utils/TestRunner.test.js +144 -0
- package/lib/utils/TestRunnerConstants.js +13 -0
- package/lib/utils/combineUint8ArrayLikes.js +18 -0
- package/lib/utils/combineUint8Arrays.js +23 -0
- package/lib/utils/components.js +88 -0
- package/lib/utils/crypto.js +17 -0
- package/lib/utils/deepEqual.js +16 -0
- package/lib/utils/deepEqual.test.js +27 -0
- package/lib/utils/fileTransformer.js +16 -0
- package/lib/utils/handleRedirect.js +93 -0
- package/lib/utils/logger.js +24 -0
- package/lib/utils/nextTick.js +47 -0
- package/lib/utils/noble-secp256k1.js +602 -0
- package/lib/utils/proxyWithRecaller.js +51 -0
- package/lib/utils/toCombinedVersion.js +14 -0
- package/lib/utils/toSubVersions.js +14 -0
- package/lib/utils/toVersionCount.js +5 -0
- package/lib/utils/webSocketMuxFactory.js +123 -0
- package/lib/utils/zabacaba.js +25 -0
- package/package.json +24 -0
- package/src/ArchiveUpdater.js +99 -0
- package/src/S3Updater.js +99 -0
- package/src/archiveSync.js +28 -0
- package/src/fileSync.js +155 -0
- package/src/getExistenceLength.js +19 -0
- package/src/manageCert.js +36 -0
- package/src/originSync.js +75 -0
- package/src/outletSync.js +50 -0
- package/src/proxyFolder.js +195 -0
- package/src/s3Sync.js +32 -0
- package/src/webSync.js +101 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {Uint8Array} a
|
|
3
|
+
* @param {Uint8Array} b
|
|
4
|
+
* @returns boolean
|
|
5
|
+
*/
|
|
6
|
+
export function deepEqualUint8Arrays (a, b) {
|
|
7
|
+
return a === b || (a.length === b.length && a.every((value, index) => value === b[index]))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const encodeNumberToU8a = (number, minimumLength = 2) => {
|
|
11
|
+
if (typeof number !== 'number') throw new Error('addresses are numbers')
|
|
12
|
+
if (Number.isNaN(number)) throw new Error('addresses are not NaN')
|
|
13
|
+
const asBytes = [...new Uint8Array(new BigUint64Array([BigInt(number)]).buffer)]
|
|
14
|
+
while (asBytes.length > minimumLength && !asBytes[asBytes.length - 1]) asBytes.pop()
|
|
15
|
+
return new Uint8Array(asBytes)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const decodeNumberFromU8a = uint8Array => {
|
|
19
|
+
const asBytes = new Uint8Array(8)
|
|
20
|
+
asBytes.set(uint8Array)
|
|
21
|
+
return Number(new BigUint64Array(asBytes.buffer)[0])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class ValueByUint8Array {
|
|
25
|
+
/** @type {number} */
|
|
26
|
+
value
|
|
27
|
+
/** @type {Uint8Array} */
|
|
28
|
+
#appendix
|
|
29
|
+
/** @type {Object.<string, ValueByUint8Array>} */
|
|
30
|
+
#childrenByBit = {}
|
|
31
|
+
/**
|
|
32
|
+
* @param {Uint8Array} uint8Array
|
|
33
|
+
* @returns any
|
|
34
|
+
*/
|
|
35
|
+
get (uint8Array) {
|
|
36
|
+
if (!uint8Array.length) return this.value
|
|
37
|
+
else if (this.#appendix) {
|
|
38
|
+
const { bytes, bits } = ValueByUint8Array.commonPrefix(this.#appendix, uint8Array)
|
|
39
|
+
return this.#childrenByBit[bits]?.get?.(uint8Array.slice(bytes))
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {Uint8Array} uint8Array
|
|
45
|
+
* @param {any} value
|
|
46
|
+
*/
|
|
47
|
+
set (uint8Array, value) {
|
|
48
|
+
if (!uint8Array.length) {
|
|
49
|
+
if (this.value !== undefined) throw new Error('Existing value')
|
|
50
|
+
this.value = value
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
this.#appendix ??= uint8Array
|
|
54
|
+
if (!this.#appendix) this.#appendix = uint8Array
|
|
55
|
+
const { bytes, bits } = ValueByUint8Array.commonPrefix(this.#appendix, uint8Array)
|
|
56
|
+
this.#childrenByBit[bits] ??= new ValueByUint8Array()
|
|
57
|
+
this.#childrenByBit[bits].set(uint8Array.slice(bytes), value)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
toObj () {
|
|
61
|
+
if (!this.#appendix) return { value: this.value }
|
|
62
|
+
return {
|
|
63
|
+
value: this.value,
|
|
64
|
+
u8a: [...this.#appendix].map(v => `0000000${v.toString(2)}`.slice(-8)).join('.'),
|
|
65
|
+
byBit: Object.fromEntries(Object.entries(this.#childrenByBit).map(([key, value]) => [key, value.toObj()]))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
toString () {
|
|
70
|
+
return JSON.stringify(this.toObj(), null, 2)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {Uint8Array} a
|
|
75
|
+
* @param {Uint8Array} b
|
|
76
|
+
* @returns {number}
|
|
77
|
+
*/
|
|
78
|
+
static commonPrefix (a, b) {
|
|
79
|
+
let bytes = 0
|
|
80
|
+
while (bytes < a.length && bytes < b.length && a[bytes] === b[bytes]) ++bytes
|
|
81
|
+
if (bytes === a.length || bytes === b.length) return { bytes, bits: bytes * 8 }
|
|
82
|
+
const bits = 8 * bytes + Math.clz32(a[bytes] ^ b[bytes]) - 24
|
|
83
|
+
return { bytes, bits }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {string} b36
|
|
89
|
+
* @returns {bigint}
|
|
90
|
+
*/
|
|
91
|
+
export function b36ToBigInt (b36) {
|
|
92
|
+
return b36.split('').reduce(
|
|
93
|
+
(acc, char) => acc * 36n + BigInt(parseInt(char, 36)),
|
|
94
|
+
0n
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {BigInt} bigInt
|
|
100
|
+
* @returns {Uint8Array}
|
|
101
|
+
*/
|
|
102
|
+
export function bigIntToUint8Array (bigInt) {
|
|
103
|
+
let hex = bigInt.toString(16)
|
|
104
|
+
if (hex.length % 2) hex = `0${hex}`
|
|
105
|
+
return new Uint8Array(hex.match(/../g).map(hexByte => parseInt(hexByte, 16)))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {Uint8Array} uint8Array
|
|
110
|
+
* @returns {BigInt}
|
|
111
|
+
*/
|
|
112
|
+
export function uint8ArrayToBigInt (uint8Array) {
|
|
113
|
+
return uint8Array.reduce((acc, byte) => acc * 256n + BigInt(byte), 0n)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {string} b36
|
|
118
|
+
* @returns {Uint8Array}
|
|
119
|
+
*/
|
|
120
|
+
export function b36ToUint8Array (b36) {
|
|
121
|
+
return bigIntToUint8Array(b36ToBigInt(b36))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {Uint8Array} uint8Array
|
|
126
|
+
* @returns {string}
|
|
127
|
+
*/
|
|
128
|
+
export function uint8ArrayToB36 (uint8Array) {
|
|
129
|
+
return uint8ArrayToBigInt(uint8Array).toString(36)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {Object} a
|
|
134
|
+
* @param {Object} b
|
|
135
|
+
*/
|
|
136
|
+
export function softAssign (a, b) {
|
|
137
|
+
let changed = false
|
|
138
|
+
const aKeys = Object.keys(a)
|
|
139
|
+
for (const i of aKeys) {
|
|
140
|
+
if (i in b) { // softAssign any overlapping attributes
|
|
141
|
+
if (a[i] !== b[i]) {
|
|
142
|
+
if (!a[i] || !b[i]) {
|
|
143
|
+
a[i] = b[i]
|
|
144
|
+
changed = true
|
|
145
|
+
} if (a[i] instanceof Uint8Array && b[i] instanceof Uint8Array) {
|
|
146
|
+
changed = deepEqualUint8Arrays(a[i], b[i]) && changed
|
|
147
|
+
} else if (
|
|
148
|
+
typeof a[i] === 'object' && typeof b[i] === 'object' &&
|
|
149
|
+
Array.isArray(a[i]) === Array.isArray(b[i])
|
|
150
|
+
) {
|
|
151
|
+
changed = softAssign(a[i], b[i]) && changed
|
|
152
|
+
} else {
|
|
153
|
+
a[i] = b[i]
|
|
154
|
+
changed = true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} else { // remove any extra attributes
|
|
158
|
+
delete a[i]
|
|
159
|
+
changed = true
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const i in b) { // add missing attributes
|
|
163
|
+
if (!(i in a)) {
|
|
164
|
+
a[i] = b[i]
|
|
165
|
+
changed = true
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (Array.isArray(b) && a.length !== b.length) {
|
|
169
|
+
a.length = b.length
|
|
170
|
+
changed = true
|
|
171
|
+
}
|
|
172
|
+
return changed
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export const defaultHostname = 'turtledb.com'
|
|
176
|
+
|
|
177
|
+
export const pathToCpkBaleHost = path => {
|
|
178
|
+
const parts = path.split(/\//)
|
|
179
|
+
const cpk = parts.pop()
|
|
180
|
+
const balename = parts.pop() ?? cpk
|
|
181
|
+
const hostname = parts.pop() ?? defaultHostname
|
|
182
|
+
return [cpk, balename, hostname]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export const cpkBaleHostToPath = (cpk, balename = cpk, hostname = defaultHostname) => {
|
|
186
|
+
const parts = [cpk]
|
|
187
|
+
if (balename !== cpk || hostname !== defaultHostname) {
|
|
188
|
+
parts.unshift(balename)
|
|
189
|
+
if (hostname !== defaultHostname) parts.unshift(hostname)
|
|
190
|
+
}
|
|
191
|
+
return parts.join('/')
|
|
192
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { handleNextTick } from '../utils/nextTick.js'
|
|
2
|
+
import { proxyWithRecaller } from '../utils/proxyWithRecaller.js'
|
|
3
|
+
import { Recaller } from '../utils/Recaller.js'
|
|
4
|
+
import { b36ToBigInt, b36ToUint8Array, bigIntToUint8Array, cpkBaleHostToPath, decodeNumberFromU8a, encodeNumberToU8a, pathToCpkBaleHost, softAssign, uint8ArrayToB36, uint8ArrayToBigInt, ValueByUint8Array } from './utils.js'
|
|
5
|
+
import { combineUint8Arrays } from '../utils/combineUint8Arrays.js'
|
|
6
|
+
import { combineUint8ArrayLikes } from '../utils/combineUint8ArrayLikes.js'
|
|
7
|
+
import { toCombinedVersion } from '../utils/toCombinedVersion.js'
|
|
8
|
+
import { toSubVersions } from '../utils/toSubVersions.js'
|
|
9
|
+
import { toVersionCount } from '../utils/toVersionCount.js'
|
|
10
|
+
import { zabacaba } from '../utils/zabacaba.js'
|
|
11
|
+
import { globalTestRunner, urlToName } from '../utils/TestRunner.js'
|
|
12
|
+
|
|
13
|
+
globalTestRunner.describe(urlToName(import.meta.url), suite => {
|
|
14
|
+
suite.it('goes from path to host/bale/cpk and back', ({ assert }) => {
|
|
15
|
+
; [
|
|
16
|
+
['turtledb.com/a/a', ['a', 'a', 'turtledb.com'], 'a'],
|
|
17
|
+
['b.com/a/a', ['a', 'a', 'b.com'], 'b.com/a/a'],
|
|
18
|
+
['a/a', ['a', 'a', 'turtledb.com'], 'a'],
|
|
19
|
+
['b/a', ['a', 'b', 'turtledb.com'], 'b/a'],
|
|
20
|
+
['a', ['a', 'a', 'turtledb.com'], 'a']
|
|
21
|
+
].forEach(vector => {
|
|
22
|
+
const [path, cpkBaleHost, repath] = vector
|
|
23
|
+
assert.equal(pathToCpkBaleHost(path), cpkBaleHost)
|
|
24
|
+
assert.equal(cpkBaleHostToPath(...cpkBaleHost), repath)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
suite.it('softAssigns completely', ({ assert }) => {
|
|
28
|
+
const obj = { a: 1, b: 2 }
|
|
29
|
+
;[
|
|
30
|
+
{ b: 3, c: [1, 2, 3] },
|
|
31
|
+
{ c: [1, 2, 3,,,] }, // eslint-disable-line no-sparse-arrays
|
|
32
|
+
{ c: [, 2] } // eslint-disable-line no-sparse-arrays
|
|
33
|
+
].forEach(vector => {
|
|
34
|
+
softAssign(obj, vector)
|
|
35
|
+
assert.equal(obj, vector)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
suite.it('returns expected values for a zabacaba function', ({ assert }) => {
|
|
39
|
+
const expectedResults = [
|
|
40
|
+
0,
|
|
41
|
+
1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1, 5,
|
|
42
|
+
1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1, 6,
|
|
43
|
+
1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1, 5,
|
|
44
|
+
1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1, 7,
|
|
45
|
+
1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1, 5
|
|
46
|
+
]
|
|
47
|
+
for (let i = 0; i < expectedResults.length; ++i) {
|
|
48
|
+
const expected = expectedResults[i]
|
|
49
|
+
const actual = zabacaba(i)
|
|
50
|
+
assert.equal(actual, expected, `zabacaba(${i}) expected: ${expected}, actual: ${actual}`)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
suite.it('combines uint8Arrays', ({ assert }) => {
|
|
54
|
+
assert.equal(new Uint8Array([1, 2, 3, 4, 5, 6]), combineUint8Arrays([
|
|
55
|
+
new Uint8Array([1]),
|
|
56
|
+
new Uint8Array([2, 3]),
|
|
57
|
+
new Uint8Array([]),
|
|
58
|
+
new Uint8Array([4, 5, 6])
|
|
59
|
+
]))
|
|
60
|
+
})
|
|
61
|
+
suite.it('combines uint8ArrayLikes', ({ assert }) => {
|
|
62
|
+
assert.equal(new Uint8Array([1, 2, 3, 4, 5, 6]), combineUint8ArrayLikes([
|
|
63
|
+
1,
|
|
64
|
+
new Uint16Array((new Uint8Array([2, 3])).buffer),
|
|
65
|
+
new Uint8Array([]),
|
|
66
|
+
new Uint8Array([4, 5, 6])
|
|
67
|
+
]))
|
|
68
|
+
})
|
|
69
|
+
suite.it('turns versions to subversion-arrays and back', ({ assert }) => {
|
|
70
|
+
const versionParts = [2, 3, 4]
|
|
71
|
+
const versionCount = toVersionCount(versionParts)
|
|
72
|
+
for (let combinedVersion = 0; combinedVersion < versionCount; ++combinedVersion) {
|
|
73
|
+
const versionArrays = toSubVersions(combinedVersion, versionParts)
|
|
74
|
+
const _combinedVersion = toCombinedVersion(versionArrays, versionParts)
|
|
75
|
+
// console.log(versionArrays, combinedVersion)
|
|
76
|
+
assert.equal(combinedVersion, _combinedVersion, `combinedVersion: ${combinedVersion} to versionArrays${versionArrays} and back ${_combinedVersion}`)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
suite.it('stores and retrieves values by Uint8Array', ({ assert }) => {
|
|
80
|
+
const valueByUint8Array = new ValueByUint8Array()
|
|
81
|
+
const uint8Arrays = [
|
|
82
|
+
new Uint8Array([1, 2, 3]),
|
|
83
|
+
new Uint8Array([1, 2]),
|
|
84
|
+
new Uint8Array([1, 2, 3, 4]),
|
|
85
|
+
new Uint8Array([]),
|
|
86
|
+
new Uint8Array([3]),
|
|
87
|
+
new Uint8Array([3, 2]),
|
|
88
|
+
new Uint8Array([3, 1])
|
|
89
|
+
]
|
|
90
|
+
for (let i = 0; i < uint8Arrays.length; ++i) {
|
|
91
|
+
const uint8Array = uint8Arrays[i]
|
|
92
|
+
const unsetValue = valueByUint8Array.get(uint8Array)
|
|
93
|
+
assert.equal(unsetValue, undefined, `unset value for ${uint8Array} should be undefined`)
|
|
94
|
+
valueByUint8Array.set(uint8Array, i)
|
|
95
|
+
const setValue = valueByUint8Array.get(uint8Array)
|
|
96
|
+
assert.equal(setValue, i, `set value for ${uint8Array} should be ${i}`)
|
|
97
|
+
}
|
|
98
|
+
for (let i = 0; i < uint8Arrays.length; ++i) {
|
|
99
|
+
const uint8Array = uint8Arrays[i]
|
|
100
|
+
const setValue = valueByUint8Array.get(uint8Array)
|
|
101
|
+
assert.equal(setValue, i, `set value for ${uint8Array} should be ${i}`)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
suite.it('encodes and decodes numbers to and from Uint8Arrays', ({ assert }) => {
|
|
105
|
+
const u8aZero = encodeNumberToU8a(0, 2)
|
|
106
|
+
assert.equal(u8aZero.length, 2, 'zero should have minimum length')
|
|
107
|
+
assert.equal(decodeNumberFromU8a(u8aZero), 0)
|
|
108
|
+
const u8aFFFFFF = encodeNumberToU8a(0xFFFFFF, 2)
|
|
109
|
+
assert.equal(u8aFFFFFF.length, 3)
|
|
110
|
+
assert.equal(decodeNumberFromU8a(u8aFFFFFF), 0xFFFFFF)
|
|
111
|
+
})
|
|
112
|
+
suite.it('translates big ints', ({ assert }) => {
|
|
113
|
+
const bigInt = 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890n
|
|
114
|
+
const b36BigInt = bigInt.toString(36)
|
|
115
|
+
const recoveredBigInt = b36ToBigInt(b36BigInt)
|
|
116
|
+
assert.equal(bigInt, recoveredBigInt)
|
|
117
|
+
const uint8Array = bigIntToUint8Array(bigInt)
|
|
118
|
+
const recoveredFromUint8Array = uint8ArrayToBigInt(uint8Array)
|
|
119
|
+
assert.equal(bigInt, recoveredFromUint8Array)
|
|
120
|
+
const uint8Array2 = b36ToUint8Array(b36BigInt)
|
|
121
|
+
assert.equal(uint8Array, uint8Array2)
|
|
122
|
+
const b36 = uint8ArrayToB36(uint8Array2)
|
|
123
|
+
assert.equal(b36, b36BigInt)
|
|
124
|
+
})
|
|
125
|
+
suite.it('proxyWithRecaller', ({ assert }) => {
|
|
126
|
+
const recaller = new Recaller('proxyWithRecaller')
|
|
127
|
+
const proxiedArray = proxyWithRecaller([1, 2, 3], recaller)
|
|
128
|
+
const arrayIndex1Updates = []
|
|
129
|
+
const arrayLengthUpdates = []
|
|
130
|
+
recaller.watch('proxiedArray index 1', () => {
|
|
131
|
+
arrayIndex1Updates.push(proxiedArray[1])
|
|
132
|
+
})
|
|
133
|
+
recaller.watch('proxiedArray length', () => {
|
|
134
|
+
arrayLengthUpdates.push(proxiedArray.length)
|
|
135
|
+
})
|
|
136
|
+
assert.equal(arrayIndex1Updates, [2])
|
|
137
|
+
assert.equal(arrayLengthUpdates, [3])
|
|
138
|
+
handleNextTick()
|
|
139
|
+
assert.equal(arrayIndex1Updates, [2])
|
|
140
|
+
assert.equal(arrayLengthUpdates, [3])
|
|
141
|
+
proxiedArray[1] = 4
|
|
142
|
+
handleNextTick()
|
|
143
|
+
assert.equal(arrayIndex1Updates, [2, 4])
|
|
144
|
+
assert.equal(arrayLengthUpdates, [3])
|
|
145
|
+
proxiedArray[2] = 4
|
|
146
|
+
handleNextTick()
|
|
147
|
+
assert.equal(arrayIndex1Updates, [2, 4])
|
|
148
|
+
assert.equal(arrayLengthUpdates, [3])
|
|
149
|
+
proxiedArray[5] = 4
|
|
150
|
+
handleNextTick()
|
|
151
|
+
assert.equal(arrayIndex1Updates, [2, 4])
|
|
152
|
+
assert.equal(arrayLengthUpdates, [3, 6])
|
|
153
|
+
proxiedArray.pop()
|
|
154
|
+
handleNextTick()
|
|
155
|
+
assert.equal(arrayIndex1Updates, [2, 4])
|
|
156
|
+
assert.equal(arrayLengthUpdates, [3, 6, 5])
|
|
157
|
+
})
|
|
158
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { AS_REFS } from '../turtle/codecs/CodecType.js'
|
|
2
|
+
import { logInfo } from './logger.js'
|
|
3
|
+
import { TestRunnerError } from './TestRunner.js'
|
|
4
|
+
import { ASSERTION, FAILED } from './TestRunnerConstants.js'
|
|
5
|
+
|
|
6
|
+
const TON = {
|
|
7
|
+
replacer: function (_key, value) {
|
|
8
|
+
if (value instanceof Object.getPrototypeOf(Uint8Array)) return { [value.constructor.name]: [...value.values()] }
|
|
9
|
+
if (typeof value === 'bigint') return { BigInt: value.toString() }
|
|
10
|
+
if (value instanceof Date) return { Date: value.toISOString() }
|
|
11
|
+
return value
|
|
12
|
+
},
|
|
13
|
+
stringify: function (value, space = 2) {
|
|
14
|
+
return JSON.stringify(TON.replacer(undefined, value), TON.replacer, space)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class Assert {
|
|
19
|
+
/**
|
|
20
|
+
* @param {import ('./TestRunner.js').TestRunner} runner
|
|
21
|
+
*/
|
|
22
|
+
constructor (runner) {
|
|
23
|
+
this.runner = runner
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async assert (valueToCheck, passMessage = 'truthy', failMessage = `NOT ${passMessage}`, cause) {
|
|
27
|
+
if (valueToCheck) {
|
|
28
|
+
return this.runner.appendChild(passMessage, () => cause, ASSERTION)
|
|
29
|
+
} else {
|
|
30
|
+
return this.runner.appendChild(failMessage, () => { throw new TestRunnerError(failMessage, { cause }) }, ASSERTION)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async equal (actual, expected, message, debug = true) {
|
|
35
|
+
const passMessage = `${TON.stringify(actual)} === ${TON.stringify(expected)}`
|
|
36
|
+
if (actual === expected) return this.assert(true, passMessage)
|
|
37
|
+
const expectedAddress = this.runner.upserter.upsert(expected)
|
|
38
|
+
const actualAddress = this.runner.upserter.upsert(actual)
|
|
39
|
+
const failMessage = message ?? `${TON.stringify(actual)} !== ${TON.stringify(expected)}`
|
|
40
|
+
const isEqual = await this.assert(
|
|
41
|
+
expectedAddress === actualAddress,
|
|
42
|
+
passMessage,
|
|
43
|
+
failMessage,
|
|
44
|
+
{ runner: this.runner, expectedAddress, actualAddress }
|
|
45
|
+
)
|
|
46
|
+
if (isEqual.runState === FAILED) {
|
|
47
|
+
printDiff(this.runner.upserter, expectedAddress, actualAddress)
|
|
48
|
+
}
|
|
49
|
+
return isEqual
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async notEqual (actual, expected, message) {
|
|
53
|
+
const passMessage = `${TON.stringify(actual)} !== ${TON.stringify(expected)}`
|
|
54
|
+
const failMessage = message ?? `${TON.stringify(actual)} === ${TON.stringify(expected)}`
|
|
55
|
+
if (expected === actual) this.assert(false, null, failMessage)
|
|
56
|
+
const expectedAddress = this.runner.upserter.upsert(expected)
|
|
57
|
+
const actualAddress = this.runner.upserter.upsert(actual)
|
|
58
|
+
return this.assert(
|
|
59
|
+
expectedAddress !== actualAddress,
|
|
60
|
+
passMessage,
|
|
61
|
+
failMessage,
|
|
62
|
+
{ runner: this.runner, expectedAddress, actualAddress }
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async isAbove (valueToCheck, valueToBeAbove, message) {
|
|
67
|
+
return this.assert(valueToCheck > valueToBeAbove, message)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async isBelow (valueToCheck, valueToBeBelow, message) {
|
|
71
|
+
return this.assert(valueToCheck < valueToBeBelow, message)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async throw (f, message) {
|
|
75
|
+
let threw = false
|
|
76
|
+
try {
|
|
77
|
+
f()
|
|
78
|
+
} catch (error) {
|
|
79
|
+
threw = true
|
|
80
|
+
}
|
|
81
|
+
return this.assert(threw, message)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
*
|
|
87
|
+
* @param {import('../turtle/TurtleBranch.js').TurtleBranch} turtleBranch
|
|
88
|
+
* @param {number} a
|
|
89
|
+
* @param {number} b
|
|
90
|
+
* @param {string} prefix
|
|
91
|
+
*/
|
|
92
|
+
export function printDiff (turtleBranch, a, b, indent = '') {
|
|
93
|
+
if (a === undefined || b === undefined) {
|
|
94
|
+
if (a) {
|
|
95
|
+
logInfo(() => console.log(`${indent}${JSON.stringify(turtleBranch.lookup(a))} !== undefined`))
|
|
96
|
+
} else {
|
|
97
|
+
logInfo(() => console.log(`${indent}undefined !== ${JSON.stringify(turtleBranch.lookup(b))}`))
|
|
98
|
+
}
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
if (indent.length > 20) return
|
|
102
|
+
const aRefs = turtleBranch.lookup(a, AS_REFS)
|
|
103
|
+
const bRefs = turtleBranch.lookup(b, AS_REFS)
|
|
104
|
+
if (aRefs && bRefs && typeof aRefs === 'object' && typeof bRefs === 'object') {
|
|
105
|
+
const attributes = new Set([...Object.keys(aRefs), ...Object.keys(bRefs)])
|
|
106
|
+
for (const attribute of attributes) {
|
|
107
|
+
if (aRefs[attribute] !== bRefs[attribute]) {
|
|
108
|
+
logInfo(() => console.log(`${indent}${attribute} - a:${a}, b:${b}`))
|
|
109
|
+
printDiff(turtleBranch, aRefs[attribute], bRefs[attribute], `${indent} `)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
logInfo(() => console.log(`${indent}${JSON.stringify(turtleBranch.lookup(a))} !== ${JSON.stringify(turtleBranch.lookup(b))}`))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} NestedSetAsObject
|
|
3
|
+
* @property {Array} set
|
|
4
|
+
* @property {Array.<[any,NestedSetAsObject]>} map
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class NestedSet {
|
|
8
|
+
/** @property {Map.<any,NestedSet>} */
|
|
9
|
+
#map
|
|
10
|
+
/** @property {Set} */
|
|
11
|
+
#set
|
|
12
|
+
/**
|
|
13
|
+
* @param {NestedSetAsObject} nestedSetAsObject
|
|
14
|
+
*/
|
|
15
|
+
constructor (nestedSetAsObject) {
|
|
16
|
+
if (nestedSetAsObject) {
|
|
17
|
+
this.#set = new Set(nestedSetAsObject.set)
|
|
18
|
+
this.#map = new Map(nestedSetAsObject.map.map(([key, value]) => [key, new NestedSet(value)]))
|
|
19
|
+
} else {
|
|
20
|
+
this.#map = new Map()
|
|
21
|
+
this.#set = new Set()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get size () {
|
|
26
|
+
return this.values().length
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
add (root, ...rest) {
|
|
30
|
+
if (rest.length) return this.#map.set(root, this.#map.get(root) ?? new NestedSet()).get(root).add(...rest)
|
|
31
|
+
return this.#set.add(root)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get (root, ...rest) {
|
|
35
|
+
if (rest.length) return this.#map.get(root)?.get(...rest)
|
|
36
|
+
return this.#map.get(root)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
delete (root, ...rest) {
|
|
40
|
+
if (rest.length) {
|
|
41
|
+
this.#map.get(root)?.delete?.(...rest)
|
|
42
|
+
} else {
|
|
43
|
+
this.#set.delete(root)
|
|
44
|
+
this.#map.forEach(nestedSet => nestedSet.delete(root))
|
|
45
|
+
}
|
|
46
|
+
if (!this.#map.get(root)?.size) this.#map.delete(root)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
deleteBranch (root, ...rest) {
|
|
50
|
+
if (rest.length) return this.#map.get(root)?.deleteBranch(...rest)
|
|
51
|
+
return this.#map.delete(root)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
values (...path) {
|
|
55
|
+
if (path.length) return this.#map.get(path[0])?.values?.(...path.slice(1)) ?? []
|
|
56
|
+
return [...new Set([...this.#set, ...[...this.#map.keys()].map(key => this.values(key)).flat()])]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* return {NestedSetAsObject}
|
|
61
|
+
*/
|
|
62
|
+
get asObject () {
|
|
63
|
+
return {
|
|
64
|
+
set: [...this.#set],
|
|
65
|
+
map: [...this.#map].map(([key, value]) => [key, value.asObject])
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { NestedSet } from './NestedSet.js'
|
|
2
|
+
import { globalTestRunner, urlToName } from './TestRunner.js'
|
|
3
|
+
|
|
4
|
+
globalTestRunner.describe(urlToName(import.meta.url), suite => {
|
|
5
|
+
suite.it('adds, gets values, and calculates size', ({ assert }) => {
|
|
6
|
+
const nestedSet = new NestedSet()
|
|
7
|
+
nestedSet.add(1, 2, 3)
|
|
8
|
+
nestedSet.add(1, 2, 3, 3)
|
|
9
|
+
nestedSet.add(1, 4, 3)
|
|
10
|
+
nestedSet.add(1, 4, 4)
|
|
11
|
+
nestedSet.add(2, 2, 5)
|
|
12
|
+
nestedSet.add(3, 2, 3)
|
|
13
|
+
|
|
14
|
+
const nestedSetCopy = new NestedSet(nestedSet.asObject)
|
|
15
|
+
assert.equal(nestedSetCopy, nestedSet)
|
|
16
|
+
|
|
17
|
+
assert.equal(nestedSet.size, 3)
|
|
18
|
+
assert.equal(nestedSet.values(), [3, 4, 5])
|
|
19
|
+
assert.equal(nestedSet.values(1), [3, 4])
|
|
20
|
+
assert.equal(nestedSet.values(1, 2), [3])
|
|
21
|
+
assert.equal(nestedSet.values(1, 2, 3), [3])
|
|
22
|
+
assert.equal(nestedSet.values(2), [5])
|
|
23
|
+
|
|
24
|
+
nestedSet.delete(1, 2, 3)
|
|
25
|
+
assert.equal(nestedSet.values(), [3, 4, 5])
|
|
26
|
+
assert.equal(nestedSet.values(1, 2), [])
|
|
27
|
+
nestedSet.delete(1, 3)
|
|
28
|
+
assert.equal(nestedSet.values(1), [4])
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const OWN_KEYS = Symbol('ownKeys')
|