@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.
Files changed (49) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/LICENSE +661 -0
  3. package/README.md +194 -0
  4. package/ROADMAP.md +111 -0
  5. package/bin/streamo.js +238 -0
  6. package/jsconfig.json +9 -0
  7. package/package.json +26 -0
  8. package/public/apps/chat/index.html +61 -0
  9. package/public/apps/chat/main.js +144 -0
  10. package/public/apps/styles/proto.css +71 -0
  11. package/public/index.html +109 -0
  12. package/public/streamo/Addressifier.js +212 -0
  13. package/public/streamo/CodecRegistry.js +195 -0
  14. package/public/streamo/ContentMap.js +79 -0
  15. package/public/streamo/DESIGN.md +61 -0
  16. package/public/streamo/Repo.js +176 -0
  17. package/public/streamo/Repo.test.js +82 -0
  18. package/public/streamo/RepoRegistry.js +91 -0
  19. package/public/streamo/RepoRegistry.test.js +87 -0
  20. package/public/streamo/Signature.js +15 -0
  21. package/public/streamo/Signer.js +91 -0
  22. package/public/streamo/Streamo.js +392 -0
  23. package/public/streamo/Streamo.test.js +205 -0
  24. package/public/streamo/archiveSync.js +62 -0
  25. package/public/streamo/chat-cli.js +122 -0
  26. package/public/streamo/chat-server.js +60 -0
  27. package/public/streamo/codecs.js +400 -0
  28. package/public/streamo/fileSync.js +238 -0
  29. package/public/streamo/h.js +202 -0
  30. package/public/streamo/h.mount.test.js +67 -0
  31. package/public/streamo/h.test.js +121 -0
  32. package/public/streamo/mount.js +248 -0
  33. package/public/streamo/originSync.js +60 -0
  34. package/public/streamo/outletSync.js +105 -0
  35. package/public/streamo/registrySync.js +333 -0
  36. package/public/streamo/registrySync.test.js +373 -0
  37. package/public/streamo/s3Sync.js +99 -0
  38. package/public/streamo/stateFileSync.js +17 -0
  39. package/public/streamo/sync.test.js +98 -0
  40. package/public/streamo/utils/NestedSet.js +41 -0
  41. package/public/streamo/utils/Recaller.js +77 -0
  42. package/public/streamo/utils/mockDOM.js +113 -0
  43. package/public/streamo/utils/nextTick.js +22 -0
  44. package/public/streamo/utils/noble-secp256k1.js +602 -0
  45. package/public/streamo/utils/testing.js +90 -0
  46. package/public/streamo/utils.js +57 -0
  47. package/public/streamo/webSync.js +118 -0
  48. package/scripts/serve.js +15 -0
  49. 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
+ }