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,102 @@
|
|
|
1
|
+
import { logError } from '../../utils/logger.js'
|
|
2
|
+
import { OPAQUE_UINT8ARRAY } from '../codecs/codec.js'
|
|
3
|
+
import { TurtleDictionary } from '../TurtleDictionary.js'
|
|
4
|
+
import { TurtleBranchUpdater } from './TurtleBranchUpdater.js'
|
|
5
|
+
import { TurtleDB } from './TurtleDB.js'
|
|
6
|
+
import { TurtleTalker } from './TurtleTalker.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import('../U8aTurtle.js').U8aTurtle} U8aTurtle
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export class TurtleBranchMultiplexer extends TurtleTalker {
|
|
13
|
+
/** @type {Object.<string, TurtleBranchUpdater>} */
|
|
14
|
+
#updatersByCpk = {}
|
|
15
|
+
#stopped = false
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} name
|
|
19
|
+
* @param {boolean} Xours
|
|
20
|
+
* @param {TurtleDB} [turtleDB=new TurtleDB(name)]
|
|
21
|
+
* @param {Recaller} recaller
|
|
22
|
+
*/
|
|
23
|
+
constructor (name, Xours, turtleDB = new TurtleDB(name), recaller) {
|
|
24
|
+
super(name, Xours, recaller)
|
|
25
|
+
this.turtleDB = turtleDB
|
|
26
|
+
this.outgoingDictionary = new TurtleDictionary(`TurtleBranchMultiplexer"${name}".outgoingDictionary`, recaller)
|
|
27
|
+
this.appendGeneratedIncomingForever() // don't await
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
stop () {
|
|
31
|
+
this.#stopped = true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async appendGeneratedIncomingForever () {
|
|
35
|
+
try {
|
|
36
|
+
for await (const u8aTurtle of this.incomingBranch.u8aTurtleGenerator()) {
|
|
37
|
+
const incoming = u8aTurtle.lookup()
|
|
38
|
+
if (!incoming) continue
|
|
39
|
+
const { address, /* name, */ publicKey } = incoming
|
|
40
|
+
if (!(address || publicKey)) {
|
|
41
|
+
throw new Error('address or publicKey required')
|
|
42
|
+
}
|
|
43
|
+
const uint8Array = u8aTurtle.lookup(address)
|
|
44
|
+
const turtleBranchUpdater = await this.getTurtleBranchUpdater(this.name, publicKey)
|
|
45
|
+
turtleBranchUpdater.incomingBranch.append(uint8Array)
|
|
46
|
+
// const uint8ArrayAddresses = turtleBranchUpdater.incomingBranch.lookup('uint8ArrayAddresses')
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
logError(() => console.error(error))
|
|
50
|
+
throw error
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {Uint8Array} uint8Array
|
|
56
|
+
* @param {string} name
|
|
57
|
+
* @param {string} publicKey
|
|
58
|
+
* @param {TurtleBranchUpdater} turtleBranchUpdater
|
|
59
|
+
*/
|
|
60
|
+
#sendMuxedUpdate (uint8Array, name, publicKey, turtleBranchUpdater) {
|
|
61
|
+
const address = this.outgoingDictionary.upsert(uint8Array, [OPAQUE_UINT8ARRAY])
|
|
62
|
+
const update = { address, name, publicKey }
|
|
63
|
+
this.outgoingDictionary.upsert(update)
|
|
64
|
+
this.outgoingDictionary.squash(this.outgoingBranch.index + 1)
|
|
65
|
+
this.outgoingBranch.u8aTurtle = this.outgoingDictionary.u8aTurtle
|
|
66
|
+
// const uint8ArrayAddresses = turtleBranchUpdater.outgoingBranch.lookup('uint8ArrayAddresses')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {string} name
|
|
71
|
+
* @param {string} publicKey
|
|
72
|
+
* @returns {TurtleBranchUpdater}
|
|
73
|
+
*/
|
|
74
|
+
async getTurtleBranchUpdater (name = '', publicKey = '', turtleBranch) {
|
|
75
|
+
if (!name && !publicKey) throw new Error('no name or publicKey')
|
|
76
|
+
publicKey ||= name
|
|
77
|
+
name ||= publicKey
|
|
78
|
+
if (!this.#updatersByCpk[publicKey]) {
|
|
79
|
+
this.#updatersByCpk[publicKey] = (async () => {
|
|
80
|
+
// logTrace(() => console.log({ publicKey }))
|
|
81
|
+
turtleBranch ??= await this.turtleDB.summonBoundTurtleBranch(publicKey, name)
|
|
82
|
+
const updater = new TurtleBranchUpdater(name, turtleBranch, publicKey, this.Xours)
|
|
83
|
+
;(async () => {
|
|
84
|
+
for await (const u8aTurtle of updater.outgoingBranch.u8aTurtleGenerator()) {
|
|
85
|
+
if (this.#stopped) break
|
|
86
|
+
this.#sendMuxedUpdate(u8aTurtle.uint8Array, name, publicKey, updater)
|
|
87
|
+
}
|
|
88
|
+
})()
|
|
89
|
+
updater.start()
|
|
90
|
+
return updater
|
|
91
|
+
})()
|
|
92
|
+
}
|
|
93
|
+
return this.#updatersByCpk[publicKey]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @type {Array.<string>}
|
|
98
|
+
*/
|
|
99
|
+
get publicKeys () {
|
|
100
|
+
return Object.keys(this.#updatersByCpk)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { tics } from '../../utils/nextTick.js'
|
|
2
|
+
import { globalTestRunner, urlToName } from '../../utils/TestRunner.js'
|
|
3
|
+
import { Signer } from '../Signer.js'
|
|
4
|
+
import { Workspace } from '../Workspace.js'
|
|
5
|
+
import { TurtleBranchMultiplexer } from './TurtleBranchMultiplexer.js'
|
|
6
|
+
|
|
7
|
+
globalTestRunner.describe(urlToName(import.meta.url), suite => {
|
|
8
|
+
suite.it('syncs multiple SimpleAsyncTurtleBranch', async ({ assert }) => {
|
|
9
|
+
const signer = new Signer('username', 'password')
|
|
10
|
+
const name = 'test'
|
|
11
|
+
const keys = await signer.makeKeysFor(name)
|
|
12
|
+
const aTBMux = new TurtleBranchMultiplexer('a', true)
|
|
13
|
+
const aTBUpdater = await aTBMux.getTurtleBranchUpdater('test-a', keys.publicKey)
|
|
14
|
+
|
|
15
|
+
const workspace = new Workspace(name, signer, aTBUpdater.turtleBranch.recaller, aTBUpdater.turtleBranch)
|
|
16
|
+
await workspace.commit(1, 'one')
|
|
17
|
+
await tics(1) // let the commit make it into aTBUpdater's outgoing branch (or else it will start settled at 0)
|
|
18
|
+
|
|
19
|
+
const bTBMux = new TurtleBranchMultiplexer('b')
|
|
20
|
+
const bTBUpdater = await bTBMux.getTurtleBranchUpdater('test-b', keys.publicKey)
|
|
21
|
+
aTBMux.connect(bTBMux)
|
|
22
|
+
|
|
23
|
+
await bTBUpdater.settle
|
|
24
|
+
assert.equal(bTBUpdater.turtleBranch.lookup().document.message, 'one')
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { logDebug } from '../../utils/logger.js'
|
|
2
|
+
import { Recaller } from '../../utils/Recaller.js'
|
|
3
|
+
import { findCommonAncestor } from '../U8aTurtle.js'
|
|
4
|
+
import { AbstractUpdater } from './AbstractUpdater.js'
|
|
5
|
+
|
|
6
|
+
/** @typedef {import('../TurtleBranch.js').TurtleBranch} TurtleBranch */
|
|
7
|
+
|
|
8
|
+
const allUpdaters = new Set()
|
|
9
|
+
|
|
10
|
+
export class TurtleBranchUpdater extends AbstractUpdater {
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} name
|
|
13
|
+
* @param {TurtleBranch} turtleBranch
|
|
14
|
+
* @param {string} publicKey
|
|
15
|
+
* @param {boolean} [Xours=false]
|
|
16
|
+
* @param {Recaller} [recaller=new Recaller(`${name}.recaller`)]
|
|
17
|
+
*/
|
|
18
|
+
constructor (name, turtleBranch, publicKey, Xours, recaller = new Recaller(name)) {
|
|
19
|
+
super(name, publicKey, Xours, recaller)
|
|
20
|
+
allUpdaters.add(this)
|
|
21
|
+
this.turtleBranch = turtleBranch
|
|
22
|
+
/** @type {U8aTurtle} */
|
|
23
|
+
let lastU8aTurtle
|
|
24
|
+
this.turtleBranch.recaller.watch(name, () => {
|
|
25
|
+
if (this.turtleBranch.u8aTurtle !== lastU8aTurtle) {
|
|
26
|
+
const incomingUint8ArrayAddresses = this.incomingBranch.lookup()?.uint8ArrayAddresses
|
|
27
|
+
if (incomingUint8ArrayAddresses?.length && !this.turtleBranch.u8aTurtle?.hasAncestor?.(lastU8aTurtle)) {
|
|
28
|
+
const commonAncestor = findCommonAncestor(lastU8aTurtle, this.turtleBranch.u8aTurtle)
|
|
29
|
+
if (commonAncestor) {
|
|
30
|
+
incomingUint8ArrayAddresses.length = Math.min(incomingUint8ArrayAddresses.length, commonAncestor.index + 1)
|
|
31
|
+
} else {
|
|
32
|
+
incomingUint8ArrayAddresses.length = 0
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
logDebug(() => console.log(`TurtleBranchUpdater(#${allUpdaters.size}): ${name}`))
|
|
36
|
+
this.update(incomingUint8ArrayAddresses)
|
|
37
|
+
lastU8aTurtle = this.turtleBranch.u8aTurtle
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getUint8ArraysLength () { return this.turtleBranch.index + 1 }
|
|
43
|
+
async setUint8ArraysLength (length) { this.turtleBranch.u8aTurtle = length ? this.turtleBranch.u8aTurtle?.getAncestorByIndex?.(length - 1) : undefined }
|
|
44
|
+
async getUint8Array (index) { return this.turtleBranch.u8aTurtle?.getAncestorByIndex?.(index)?.uint8Array }
|
|
45
|
+
async pushUint8Array (uint8Array) { return this.turtleBranch.append(uint8Array) }
|
|
46
|
+
async popUint8Array () { this.turtleBranch.u8aTurtle = this.turtleBranch.u8aTurtle.parent }
|
|
47
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { logDebug, logError, logWarn } from '../../utils/logger.js'
|
|
2
|
+
import { Recaller } from '../../utils/Recaller.js'
|
|
3
|
+
import { TurtleBranch } from '../TurtleBranch.js'
|
|
4
|
+
import { Workspace } from '../Workspace.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('../Signer.js').Signer} Signer
|
|
8
|
+
* @typedef {(turtleBranchStatus) => Promise.<void>} Binding
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* turtleBranchPromise: Promise.<TurtleBranch>,
|
|
11
|
+
* publicKey: string,
|
|
12
|
+
* tags: Set.<any>,
|
|
13
|
+
* turtleBranch: TurtleBranch,
|
|
14
|
+
* bindingInProgress: Binding,
|
|
15
|
+
* bindings: Set.<Binding>
|
|
16
|
+
* }} TurtleBranchStatus
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const STATUSES_OWN_KEYS = Symbol('TurtleDB instance changed')
|
|
20
|
+
|
|
21
|
+
export class TurtleDB {
|
|
22
|
+
/** @type {Object.<string, TurtleBranchStatus>} */
|
|
23
|
+
#statuses = {}
|
|
24
|
+
/** @type {Array.<Binding>} */
|
|
25
|
+
#bindings = []
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} name
|
|
29
|
+
* @param {Recaller} recaller
|
|
30
|
+
*/
|
|
31
|
+
constructor (name, recaller = new Recaller(name)) {
|
|
32
|
+
this.name = name
|
|
33
|
+
this.recaller = recaller
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {Signer} signer
|
|
38
|
+
* @param {string} name
|
|
39
|
+
*/
|
|
40
|
+
async makeWorkspace (signer, name) {
|
|
41
|
+
try {
|
|
42
|
+
const { publicKey } = await signer.makeKeysFor(name)
|
|
43
|
+
logDebug(() => console.log({ publicKey }))
|
|
44
|
+
const turtleBranch = await this.summonBoundTurtleBranch(publicKey, name)
|
|
45
|
+
return new Workspace(name, signer, this.recaller, turtleBranch)
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logError(() => console.error(error))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* get a TurtleBranch and init if required
|
|
53
|
+
* @param {string} publicKey
|
|
54
|
+
* @param {string} [name=publicKey]
|
|
55
|
+
* @return {Promise.<TurtleBranch>}
|
|
56
|
+
*/
|
|
57
|
+
async summonBoundTurtleBranch (publicKey, name = publicKey) {
|
|
58
|
+
if (!publicKey) throw new Error('TurtleBranch must have publicKey')
|
|
59
|
+
const status = this.getStatus(publicKey, name)
|
|
60
|
+
if (status.turtleBranch.name === publicKey) status.turtleBranch.name = name
|
|
61
|
+
return status.turtleBranchPromise
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {Binding} binding
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
bind (binding) {
|
|
69
|
+
if (this.#bindings.includes(binding)) {
|
|
70
|
+
logWarn(() => console.warn('binding already added'))
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
this.#bindings.push(binding)
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {Binding} binding
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
unbind (binding) {
|
|
82
|
+
if (!this.#bindings.includes(binding)) {
|
|
83
|
+
logWarn(() => console.warn('binding already removed'))
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
this.#bindings = this.#bindings.filter(_binding => _binding !== binding)
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} publicKey
|
|
92
|
+
* @param {string} tag
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
*/
|
|
95
|
+
tag (publicKey, tag) {
|
|
96
|
+
if (!this.getStatus(publicKey).tags.has(tag)) {
|
|
97
|
+
this.getStatus(publicKey).tags.add(tag)
|
|
98
|
+
this.recaller.reportKeyMutation(this, STATUSES_OWN_KEYS, 'tag', this.name)
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {string} publicKey
|
|
106
|
+
* @param {string} tag
|
|
107
|
+
* @returns {boolean}
|
|
108
|
+
*/
|
|
109
|
+
untag (publicKey, tag) {
|
|
110
|
+
if (this.getStatus(publicKey).tags.has(tag)) {
|
|
111
|
+
this.getStatus(publicKey).tags.delete(tag)
|
|
112
|
+
this.recaller.reportKeyMutation(this, STATUSES_OWN_KEYS, 'untag', this.name)
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {string} publicKey
|
|
120
|
+
* @returns {TurtleBranchStatus}
|
|
121
|
+
*/
|
|
122
|
+
getStatus (publicKey, name = publicKey) {
|
|
123
|
+
this.recaller.reportKeyAccess(this, STATUSES_OWN_KEYS, 'getStatus', this.name)
|
|
124
|
+
let status = this.#statuses[publicKey]
|
|
125
|
+
if (!status) {
|
|
126
|
+
const turtleBranch = new TurtleBranch(name, this.recaller)
|
|
127
|
+
status = {
|
|
128
|
+
publicKey,
|
|
129
|
+
tags: new Set(),
|
|
130
|
+
turtleBranch,
|
|
131
|
+
bindingInProgress: null,
|
|
132
|
+
bindings: new Set()
|
|
133
|
+
}
|
|
134
|
+
this.#statuses[publicKey] = status
|
|
135
|
+
this.recaller.reportKeyMutation(this, STATUSES_OWN_KEYS, 'getStatus', this.name)
|
|
136
|
+
status.turtleBranchPromise = (async () => {
|
|
137
|
+
try {
|
|
138
|
+
for (const binding of this.#bindings) {
|
|
139
|
+
status.bindingInProgress = binding
|
|
140
|
+
await binding(status)
|
|
141
|
+
status.bindings.add(binding)
|
|
142
|
+
}
|
|
143
|
+
return turtleBranch
|
|
144
|
+
} catch (error) {
|
|
145
|
+
logError(() => console.error(error))
|
|
146
|
+
}
|
|
147
|
+
})()
|
|
148
|
+
}
|
|
149
|
+
return this.#statuses[publicKey]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {Set.<string>} tags
|
|
154
|
+
* @returns {Array.<string>}
|
|
155
|
+
*/
|
|
156
|
+
getPublicKeys (tags) {
|
|
157
|
+
this.recaller.reportKeyAccess(this, STATUSES_OWN_KEYS, 'getPublicKeys', this.name)
|
|
158
|
+
const allKeys = Object.keys(this.#statuses)
|
|
159
|
+
if (!tags) return allKeys
|
|
160
|
+
return allKeys.filter(publicKey => {
|
|
161
|
+
const status = this.#statuses[publicKey]
|
|
162
|
+
return tags.intersection(status.tags).size === tags.size
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { tics } from '../../utils/nextTick.js'
|
|
2
|
+
import { globalTestRunner, urlToName } from '../../utils/TestRunner.js'
|
|
3
|
+
import { TurtleDB } from './TurtleDB.js'
|
|
4
|
+
|
|
5
|
+
globalTestRunner.describe(urlToName(import.meta.url), suite => {
|
|
6
|
+
suite.it('correctly makes branches', async ({ assert }) => {
|
|
7
|
+
const turtleDB = new TurtleDB('test1')
|
|
8
|
+
turtleDB.bind(async status => {
|
|
9
|
+
status.turtleBranch.x ??= []
|
|
10
|
+
status.turtleBranch.x.push('a')
|
|
11
|
+
})
|
|
12
|
+
turtleDB.bind(async status => {
|
|
13
|
+
status.turtleBranch.x ??= []
|
|
14
|
+
status.turtleBranch.x.push('b')
|
|
15
|
+
})
|
|
16
|
+
const branchA = await turtleDB.summonBoundTurtleBranch('abc123', 'A')
|
|
17
|
+
assert.equal(branchA.x, ['a', 'b'])
|
|
18
|
+
})
|
|
19
|
+
suite.it('filters by tag', async ({ assert }) => {
|
|
20
|
+
const turtleDB = new TurtleDB('test2')
|
|
21
|
+
await turtleDB.summonBoundTurtleBranch('a')
|
|
22
|
+
turtleDB.getStatus('a').tags.add('x')
|
|
23
|
+
await turtleDB.summonBoundTurtleBranch('b')
|
|
24
|
+
await turtleDB.summonBoundTurtleBranch('c')
|
|
25
|
+
turtleDB.tag('c', 'x')
|
|
26
|
+
const allKeys = turtleDB.getPublicKeys()
|
|
27
|
+
assert.equal(allKeys, ['a', 'b', 'c'])
|
|
28
|
+
const xKeys = turtleDB.getPublicKeys(new Set(['x']))
|
|
29
|
+
assert.equal(xKeys, ['a', 'c'])
|
|
30
|
+
})
|
|
31
|
+
suite.it('notifies on changes', async ({ assert }) => {
|
|
32
|
+
const turtleDB = new TurtleDB('test3')
|
|
33
|
+
const outputs = []
|
|
34
|
+
turtleDB.recaller.watch('test3', () => {
|
|
35
|
+
outputs.push([turtleDB.getPublicKeys(), turtleDB.getPublicKeys(new Set(['x']))])
|
|
36
|
+
})
|
|
37
|
+
await turtleDB.summonBoundTurtleBranch('a')
|
|
38
|
+
await turtleDB.summonBoundTurtleBranch('b')
|
|
39
|
+
await tics()
|
|
40
|
+
assert.equal(outputs, [[[], []], [['a', 'b'], []]])
|
|
41
|
+
turtleDB.tag('a', 'x')
|
|
42
|
+
await tics()
|
|
43
|
+
assert.equal(outputs, [[[], []], [['a', 'b'], []], [['a', 'b'], ['a']]])
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Recaller } from '../../utils/Recaller.js'
|
|
2
|
+
import { TurtleBranch } from '../TurtleBranch.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {{ts: Date, uint8ArrayAddresses: Array.<number>}} TurtleTalk
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class TurtleTalker {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} name
|
|
11
|
+
* @param {boolean} [Xours=false]
|
|
12
|
+
* @param {Recaller} [recaller=new Recaller(`${name}.recaller`)]
|
|
13
|
+
*/
|
|
14
|
+
constructor (
|
|
15
|
+
name,
|
|
16
|
+
Xours = false,
|
|
17
|
+
recaller = new Recaller(`${name}.recaller`)
|
|
18
|
+
) {
|
|
19
|
+
this.name = name
|
|
20
|
+
this.Xours = Xours
|
|
21
|
+
this.recaller = recaller
|
|
22
|
+
this.outgoingBranch = new TurtleBranch(`${name}.outgoingBranch`, recaller)
|
|
23
|
+
this.incomingBranch = new TurtleBranch(`${name}.incomingBranch`, recaller)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @param {TurtleTalk|TurtleBranch} connection */
|
|
27
|
+
connect (connection) {
|
|
28
|
+
this.makeReadableStream().pipeTo(connection.makeWritableStream())
|
|
29
|
+
connection.makeReadableStream().pipeTo(this.makeWritableStream())
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
makeReadableStream () { return this.outgoingBranch.makeReadableStream() }
|
|
33
|
+
makeWritableStream () { return this.incomingBranch.makeWritableStream() }
|
|
34
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { tics } from '../../utils/nextTick.js'
|
|
2
|
+
import { globalTestRunner, urlToName } from '../../utils/TestRunner.js'
|
|
3
|
+
import { Signer } from '../Signer.js'
|
|
4
|
+
import { TurtleBranch } from '../TurtleBranch.js'
|
|
5
|
+
import { Workspace } from '../Workspace.js'
|
|
6
|
+
import { TurtleBranchUpdater } from './TurtleBranchUpdater.js'
|
|
7
|
+
|
|
8
|
+
const commitSettle = async () => {
|
|
9
|
+
// console.log(' -- commit settle')
|
|
10
|
+
await tics(10) // , 'a sending, b verifying and updating')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
globalTestRunner.describe(urlToName(import.meta.url), suite => {
|
|
14
|
+
suite.it('syncs SimpleAsyncTurtleBranch', async ({ assert }) => {
|
|
15
|
+
const signer = new Signer('test-user', 'p@$$w0rd')
|
|
16
|
+
const aWorkspace = new Workspace('test', signer)
|
|
17
|
+
const keys = await signer.makeKeysFor(aWorkspace.name)
|
|
18
|
+
const aTalker = new TurtleBranchUpdater('aTalker', aWorkspace.committedBranch, keys.publicKey)
|
|
19
|
+
const b = new TurtleBranch('b')
|
|
20
|
+
const bTalker = new TurtleBranchUpdater('bTalker', b, keys.publicKey, true)
|
|
21
|
+
|
|
22
|
+
aTalker.connect(bTalker)
|
|
23
|
+
aTalker.start()
|
|
24
|
+
bTalker.start()
|
|
25
|
+
await tics(1) // , 'talkers icebreaking')
|
|
26
|
+
|
|
27
|
+
await aWorkspace.commit(42, 'everything', false)
|
|
28
|
+
await commitSettle()
|
|
29
|
+
assert.equal(b.lookup()?.document?.value, 42)
|
|
30
|
+
|
|
31
|
+
await aWorkspace.commit(Math.E, 'euler', false)
|
|
32
|
+
await commitSettle()
|
|
33
|
+
assert.equal(b.lookup()?.document?.value, Math.E)
|
|
34
|
+
|
|
35
|
+
await aWorkspace.commit(Math.PI, 'pi', false)
|
|
36
|
+
await commitSettle()
|
|
37
|
+
assert.equal(b.lookup()?.document?.value, Math.PI)
|
|
38
|
+
|
|
39
|
+
const unbrokenBranch = new TurtleBranch('unbroken', undefined, aWorkspace.committedBranch.u8aTurtle)
|
|
40
|
+
|
|
41
|
+
const brokenWorkspace = new Workspace('broken', signer, aWorkspace.committedBranch.recaller, aWorkspace.committedBranch)
|
|
42
|
+
// const brokenKeys = await signer.makeKeysFor(brokenWorkspace.name)
|
|
43
|
+
await brokenWorkspace.commit(2, 'two', false)
|
|
44
|
+
await commitSettle()
|
|
45
|
+
assert.equal(b.lookup()?.document?.value, Math.PI)
|
|
46
|
+
|
|
47
|
+
const unbrokenWorkspace = new Workspace('test', signer, unbrokenBranch.recaller, unbrokenBranch)
|
|
48
|
+
await unbrokenWorkspace.commit(4, 'four', false)
|
|
49
|
+
aTalker.turtleBranch.u8aTurtle = unbrokenWorkspace.u8aTurtle
|
|
50
|
+
await commitSettle()
|
|
51
|
+
assert.equal(b.lookup()?.document?.value, 4)
|
|
52
|
+
|
|
53
|
+
const a2 = new TurtleBranch('a2', undefined, aWorkspace.committedBranch.u8aTurtle)
|
|
54
|
+
const a2Workspace = new Workspace('test', signer, a2.recaller, a2)
|
|
55
|
+
const aTalker2 = new TurtleBranchUpdater('aTalker2', a2, keys.publicKey)
|
|
56
|
+
const b2 = new TurtleBranch('b2', undefined, b.u8aTurtle)
|
|
57
|
+
const bTalker2 = new TurtleBranchUpdater('bTalker2', b2, keys.publicKey)
|
|
58
|
+
aTalker2.connect(bTalker2)
|
|
59
|
+
aTalker2.start()
|
|
60
|
+
bTalker2.start()
|
|
61
|
+
await tics(1) //, 'talkers icebreaking')
|
|
62
|
+
assert.equal(b2.lookup()?.document?.value, 4)
|
|
63
|
+
assert.isAbove(b2.length, bTalker2.outgoingBranch.length) // we have more data than we received
|
|
64
|
+
|
|
65
|
+
a2Workspace.commit(5, 'five', false)
|
|
66
|
+
await commitSettle()
|
|
67
|
+
assert.equal(b2.lookup()?.document?.value, 5)
|
|
68
|
+
assert.isAbove(b2.length, bTalker2.outgoingBranch.length) // we have more data than we received from this connection
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
suite.it('handles bigger changes', async ({ assert }) => {
|
|
72
|
+
const signer = new Signer('username', 'password')
|
|
73
|
+
const aWorkspace = new Workspace('test', signer)
|
|
74
|
+
const keys = await signer.makeKeysFor(aWorkspace.name)
|
|
75
|
+
await aWorkspace.commit(1, 'one', false)
|
|
76
|
+
await aWorkspace.commit(2, 'two', false)
|
|
77
|
+
await aWorkspace.commit(3, 'three', false)
|
|
78
|
+
const clone = new TurtleBranch('clone', undefined, aWorkspace.u8aTurtle.clone())
|
|
79
|
+
|
|
80
|
+
const originalTalker = new TurtleBranchUpdater('originalTalker', aWorkspace.committedBranch, keys.publicKey, true)
|
|
81
|
+
const cloneTalker = new TurtleBranchUpdater('cloneTalker', clone, keys.publicKey)
|
|
82
|
+
|
|
83
|
+
cloneTalker.connect(originalTalker)
|
|
84
|
+
await commitSettle()
|
|
85
|
+
originalTalker.start()
|
|
86
|
+
cloneTalker.start()
|
|
87
|
+
await commitSettle()
|
|
88
|
+
assert.equal(JSON.stringify(originalTalker.turtleBranch.lookup()), JSON.stringify(cloneTalker.turtleBranch.lookup()))
|
|
89
|
+
await aWorkspace.commit(4, 'four', false)
|
|
90
|
+
await commitSettle()
|
|
91
|
+
assert.equal(JSON.stringify(originalTalker.turtleBranch.lookup()), JSON.stringify(cloneTalker.turtleBranch.lookup()))
|
|
92
|
+
|
|
93
|
+
const bWorkspace = new Workspace('test', signer)
|
|
94
|
+
await bWorkspace.commit(5, 'five', false)
|
|
95
|
+
await bWorkspace.commit(6, 'six', false)
|
|
96
|
+
await bWorkspace.commit(7, 'seven', false)
|
|
97
|
+
originalTalker.turtleBranch.u8aTurtle = bWorkspace.u8aTurtle
|
|
98
|
+
await commitSettle()
|
|
99
|
+
assert.equal(JSON.stringify(originalTalker.turtleBranch.lookup()), JSON.stringify(cloneTalker.turtleBranch.lookup()))
|
|
100
|
+
})
|
|
101
|
+
})
|