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.
Files changed (70) hide show
  1. package/LICENSE +661 -0
  2. package/bin/turtb.js +259 -0
  3. package/lib/display/h.js +204 -0
  4. package/lib/display/helpers.js +78 -0
  5. package/lib/display/render.js +177 -0
  6. package/lib/display/shapes.js +91 -0
  7. package/lib/turtle/Signer.js +120 -0
  8. package/lib/turtle/Signer.test.js +38 -0
  9. package/lib/turtle/TurtleBranch.js +147 -0
  10. package/lib/turtle/TurtleBranch.test.js +89 -0
  11. package/lib/turtle/TurtleDictionary.js +157 -0
  12. package/lib/turtle/TurtleDictionary.test.js +331 -0
  13. package/lib/turtle/U8aTurtle.js +203 -0
  14. package/lib/turtle/U8aTurtle.test.js +60 -0
  15. package/lib/turtle/Workspace.js +62 -0
  16. package/lib/turtle/Workspace.test.js +63 -0
  17. package/lib/turtle/codecs/CodecType.js +36 -0
  18. package/lib/turtle/codecs/CodecTypeVersion.js +37 -0
  19. package/lib/turtle/codecs/Commit.js +10 -0
  20. package/lib/turtle/codecs/CompositeCodec.js +86 -0
  21. package/lib/turtle/codecs/TreeNode.js +38 -0
  22. package/lib/turtle/codecs/codec.js +441 -0
  23. package/lib/turtle/connections/AbstractUpdater.js +176 -0
  24. package/lib/turtle/connections/TurtleBranchMultiplexer.js +102 -0
  25. package/lib/turtle/connections/TurtleBranchMultiplexer.test.js +26 -0
  26. package/lib/turtle/connections/TurtleBranchUpdater.js +47 -0
  27. package/lib/turtle/connections/TurtleDB.js +165 -0
  28. package/lib/turtle/connections/TurtleDB.test.js +45 -0
  29. package/lib/turtle/connections/TurtleTalker.js +34 -0
  30. package/lib/turtle/connections/TurtleTalker.test.js +101 -0
  31. package/lib/turtle/utils.js +192 -0
  32. package/lib/turtle/utils.test.js +158 -0
  33. package/lib/utils/Assert.js +115 -0
  34. package/lib/utils/NestedSet.js +68 -0
  35. package/lib/utils/NestedSet.test.js +30 -0
  36. package/lib/utils/OWN_KEYS.js +1 -0
  37. package/lib/utils/Recaller.js +175 -0
  38. package/lib/utils/Recaller.test.js +75 -0
  39. package/lib/utils/TestRunner.js +200 -0
  40. package/lib/utils/TestRunner.test.js +144 -0
  41. package/lib/utils/TestRunnerConstants.js +13 -0
  42. package/lib/utils/combineUint8ArrayLikes.js +18 -0
  43. package/lib/utils/combineUint8Arrays.js +23 -0
  44. package/lib/utils/components.js +88 -0
  45. package/lib/utils/crypto.js +17 -0
  46. package/lib/utils/deepEqual.js +16 -0
  47. package/lib/utils/deepEqual.test.js +27 -0
  48. package/lib/utils/fileTransformer.js +16 -0
  49. package/lib/utils/handleRedirect.js +93 -0
  50. package/lib/utils/logger.js +24 -0
  51. package/lib/utils/nextTick.js +47 -0
  52. package/lib/utils/noble-secp256k1.js +602 -0
  53. package/lib/utils/proxyWithRecaller.js +51 -0
  54. package/lib/utils/toCombinedVersion.js +14 -0
  55. package/lib/utils/toSubVersions.js +14 -0
  56. package/lib/utils/toVersionCount.js +5 -0
  57. package/lib/utils/webSocketMuxFactory.js +123 -0
  58. package/lib/utils/zabacaba.js +25 -0
  59. package/package.json +24 -0
  60. package/src/ArchiveUpdater.js +99 -0
  61. package/src/S3Updater.js +99 -0
  62. package/src/archiveSync.js +28 -0
  63. package/src/fileSync.js +155 -0
  64. package/src/getExistenceLength.js +19 -0
  65. package/src/manageCert.js +36 -0
  66. package/src/originSync.js +75 -0
  67. package/src/outletSync.js +50 -0
  68. package/src/proxyFolder.js +195 -0
  69. package/src/s3Sync.js +32 -0
  70. 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
+ })