@dtudury/streamo 0.1.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/.claude/settings.local.json +10 -0
- package/LICENSE +661 -0
- package/README.md +194 -0
- package/ROADMAP.md +111 -0
- package/bin/streamo.js +238 -0
- package/jsconfig.json +9 -0
- package/package.json +26 -0
- package/public/apps/chat/index.html +61 -0
- package/public/apps/chat/main.js +144 -0
- package/public/apps/styles/proto.css +71 -0
- package/public/index.html +109 -0
- package/public/streamo/Addressifier.js +212 -0
- package/public/streamo/CodecRegistry.js +195 -0
- package/public/streamo/ContentMap.js +79 -0
- package/public/streamo/DESIGN.md +61 -0
- package/public/streamo/Repo.js +176 -0
- package/public/streamo/Repo.test.js +82 -0
- package/public/streamo/RepoRegistry.js +91 -0
- package/public/streamo/RepoRegistry.test.js +87 -0
- package/public/streamo/Signature.js +15 -0
- package/public/streamo/Signer.js +91 -0
- package/public/streamo/Streamo.js +392 -0
- package/public/streamo/Streamo.test.js +205 -0
- package/public/streamo/archiveSync.js +62 -0
- package/public/streamo/chat-cli.js +122 -0
- package/public/streamo/chat-server.js +60 -0
- package/public/streamo/codecs.js +400 -0
- package/public/streamo/fileSync.js +238 -0
- package/public/streamo/h.js +202 -0
- package/public/streamo/h.mount.test.js +67 -0
- package/public/streamo/h.test.js +121 -0
- package/public/streamo/mount.js +248 -0
- package/public/streamo/originSync.js +60 -0
- package/public/streamo/outletSync.js +105 -0
- package/public/streamo/registrySync.js +333 -0
- package/public/streamo/registrySync.test.js +373 -0
- package/public/streamo/s3Sync.js +99 -0
- package/public/streamo/stateFileSync.js +17 -0
- package/public/streamo/sync.test.js +98 -0
- package/public/streamo/utils/NestedSet.js +41 -0
- package/public/streamo/utils/Recaller.js +77 -0
- package/public/streamo/utils/mockDOM.js +113 -0
- package/public/streamo/utils/nextTick.js +22 -0
- package/public/streamo/utils/noble-secp256k1.js +602 -0
- package/public/streamo/utils/testing.js +90 -0
- package/public/streamo/utils.js +57 -0
- package/public/streamo/webSync.js +118 -0
- package/scripts/serve.js +15 -0
- package/smoke.test.js +132 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { NestedSet } from './NestedSet.js'
|
|
2
|
+
import { nextTick } from './nextTick.js'
|
|
3
|
+
|
|
4
|
+
export class Recaller {
|
|
5
|
+
#deps = new NestedSet()
|
|
6
|
+
#names = new Map()
|
|
7
|
+
#stack = []
|
|
8
|
+
#pending = new Set()
|
|
9
|
+
#flushing = false
|
|
10
|
+
loopLimit = 10
|
|
11
|
+
|
|
12
|
+
constructor (name) {
|
|
13
|
+
if (!name) throw new Error('Recaller must be named')
|
|
14
|
+
this.name = name
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Call f immediately while tracking any reportKeyAccess calls made during
|
|
19
|
+
* execution. Re-runs f whenever reportKeyMutation is called on a key that
|
|
20
|
+
* was accessed. Each re-run establishes a fresh set of tracked dependencies.
|
|
21
|
+
* @param {string} name
|
|
22
|
+
* @param {function} f
|
|
23
|
+
*/
|
|
24
|
+
watch (name, f) {
|
|
25
|
+
if (!name || typeof name !== 'string') throw new Error('please name watches')
|
|
26
|
+
if (typeof f !== 'function') throw new Error(`can only watch functions (${name})`)
|
|
27
|
+
this.#disassociate(f)
|
|
28
|
+
this.#names.set(f, name)
|
|
29
|
+
this.#stack.unshift(f)
|
|
30
|
+
try { f(this) } catch (e) { console.error(e) }
|
|
31
|
+
this.#stack.shift()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
unwatch (f) {
|
|
35
|
+
this.#disassociate(f)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
reportKeyAccess (target, key) {
|
|
39
|
+
const f = this.#stack[0]
|
|
40
|
+
if (typeof f !== 'function') return
|
|
41
|
+
this.#deps.add(key, target, f)
|
|
42
|
+
this.#deps.add(f, target, key)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
reportKeyMutation (target, key) {
|
|
46
|
+
const triggered = this.#deps.values(key, target)
|
|
47
|
+
if (!triggered.length) return
|
|
48
|
+
triggered.forEach(f => this.#pending.add(f))
|
|
49
|
+
if (this.#flushing) return
|
|
50
|
+
this.#flushing = true
|
|
51
|
+
nextTick(() => this.#flush())
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#disassociate (f) {
|
|
55
|
+
this.#deps.delete(f)
|
|
56
|
+
this.#names.delete(f)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#flush () {
|
|
60
|
+
let loops = 0
|
|
61
|
+
while (this.#pending.size) {
|
|
62
|
+
if (loops >= this.loopLimit) {
|
|
63
|
+
console.error(`Recaller[${this.name}]: loop limit exceeded`)
|
|
64
|
+
break
|
|
65
|
+
}
|
|
66
|
+
const batch = [...this.#pending]
|
|
67
|
+
this.#pending = new Set()
|
|
68
|
+
batch.forEach(f => {
|
|
69
|
+
const name = this.#names.get(f) ?? f.name ?? '(unnamed)'
|
|
70
|
+
this.#disassociate(f)
|
|
71
|
+
this.watch(name, f)
|
|
72
|
+
})
|
|
73
|
+
loops++
|
|
74
|
+
}
|
|
75
|
+
this.#flushing = false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal DOM mock for testing mount() in Node.
|
|
3
|
+
*
|
|
4
|
+
* Implements only the subset of DOM APIs that hx.js mount() actually calls:
|
|
5
|
+
* - createElement / createElementNS / createTextNode / createComment / createDocumentFragment
|
|
6
|
+
* - appendChild, remove, before, nextSibling, childNodes
|
|
7
|
+
* - setAttribute, getAttribute, removeAttribute, toggleAttribute
|
|
8
|
+
* - textContent (read)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class MockNode {
|
|
12
|
+
#children = []
|
|
13
|
+
#parent = null
|
|
14
|
+
|
|
15
|
+
constructor (nodeType) {
|
|
16
|
+
this.nodeType = nodeType
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get childNodes () { return [...this.#children] }
|
|
20
|
+
get parentNode () { return this.#parent }
|
|
21
|
+
|
|
22
|
+
get textContent () {
|
|
23
|
+
return this.#children.map(c => c.textContent).join('')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get nextSibling () {
|
|
27
|
+
if (!this.#parent) return null
|
|
28
|
+
const sibs = this.#parent.#children
|
|
29
|
+
return sibs[sibs.indexOf(this) + 1] ?? null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
appendChild (child) {
|
|
33
|
+
if (child.#parent) child.remove()
|
|
34
|
+
child.#parent = this
|
|
35
|
+
this.#children.push(child)
|
|
36
|
+
return child
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
remove () {
|
|
40
|
+
if (!this.#parent) return
|
|
41
|
+
this.#parent.#children = this.#parent.#children.filter(c => c !== this)
|
|
42
|
+
this.#parent = null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
before (...nodes) {
|
|
46
|
+
if (!this.#parent) return
|
|
47
|
+
const i = this.#parent.#children.indexOf(this)
|
|
48
|
+
const toInsert = []
|
|
49
|
+
for (const n of nodes) {
|
|
50
|
+
if (n.nodeType === 11) { // DocumentFragment
|
|
51
|
+
const cs = [...n.#children]
|
|
52
|
+
n.#children = []
|
|
53
|
+
cs.forEach(c => { c.#parent = this.#parent })
|
|
54
|
+
toInsert.push(...cs)
|
|
55
|
+
} else {
|
|
56
|
+
if (n.#parent) n.remove()
|
|
57
|
+
n.#parent = this.#parent
|
|
58
|
+
toInsert.push(n)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
this.#parent.#children.splice(i, 0, ...toInsert)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class MockElement extends MockNode {
|
|
66
|
+
#attrs = {}
|
|
67
|
+
|
|
68
|
+
constructor (tag) {
|
|
69
|
+
super(1)
|
|
70
|
+
this.tag = tag
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
setAttribute (name, val) { this.#attrs[name] = String(val) }
|
|
74
|
+
getAttribute (name) { return this.#attrs[name] ?? null }
|
|
75
|
+
removeAttribute (name) { delete this.#attrs[name] }
|
|
76
|
+
toggleAttribute (name, force) {
|
|
77
|
+
if (force) this.#attrs[name] = ''
|
|
78
|
+
else delete this.#attrs[name]
|
|
79
|
+
}
|
|
80
|
+
addEventListener () {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class MockText extends MockNode {
|
|
84
|
+
constructor (value) {
|
|
85
|
+
super(3)
|
|
86
|
+
this.nodeValue = value
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get textContent () { return this.nodeValue }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class MockComment extends MockNode {
|
|
93
|
+
constructor (value) {
|
|
94
|
+
super(8)
|
|
95
|
+
this.nodeValue = value
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get textContent () { return '' }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class MockFragment extends MockNode {
|
|
102
|
+
constructor () { super(11) }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const mockDocument = {
|
|
106
|
+
createElement: tag => new MockElement(tag),
|
|
107
|
+
createElementNS: (_ns, tag) => new MockElement(tag),
|
|
108
|
+
createTextNode: text => new MockText(text),
|
|
109
|
+
createComment: text => new MockComment(text),
|
|
110
|
+
createDocumentFragment: () => new MockFragment()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { MockNode }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const _next = typeof process !== 'undefined' ? process.nextTick : setTimeout
|
|
2
|
+
|
|
3
|
+
let _pending = []
|
|
4
|
+
let _scheduled = false
|
|
5
|
+
|
|
6
|
+
const flush = () => {
|
|
7
|
+
for (let i = 0; i < 10; i++) {
|
|
8
|
+
_scheduled = false
|
|
9
|
+
const batch = _pending
|
|
10
|
+
_pending = []
|
|
11
|
+
batch.forEach(f => f())
|
|
12
|
+
if (!_pending.length) return
|
|
13
|
+
}
|
|
14
|
+
console.error('nextTick: flush loop exceeded 10 iterations')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const nextTick = f => {
|
|
18
|
+
_pending.push(f)
|
|
19
|
+
if (_scheduled) return
|
|
20
|
+
_scheduled = true
|
|
21
|
+
_next(flush)
|
|
22
|
+
}
|