@dtudury/streamo 1.0.1 → 3.0.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/README.md CHANGED
@@ -56,8 +56,7 @@ streamo --env-file .env
56
56
  ### Streamo — reactive append-only store
57
57
 
58
58
  ```js
59
- import { Streamo } from '@dtudury/streamo/public/streamo/Streamo.js'
60
- import { Recaller } from '@dtudury/streamo/public/streamo/utils/Recaller.js'
59
+ import { Streamo } from '@dtudury/streamo'
61
60
 
62
61
  const store = new Streamo()
63
62
  store.set({ name: 'alice', score: 42 })
@@ -72,7 +71,7 @@ Values are encoded with a self-describing codec (strings, numbers, dates, boolea
72
71
  `Repo` wraps a `Streamo` so every `set()` becomes a commit — message, date, data address, and parent pointer. The raw commit log is what syncs over the wire.
73
72
 
74
73
  ```js
75
- import { Repo } from '@dtudury/streamo/public/streamo/Repo.js'
74
+ import { Repo } from '@dtudury/streamo'
76
75
 
77
76
  const repo = new Repo()
78
77
  repo.attachSigner(signer, 'my-dataset') // auto-sign every commit
@@ -87,8 +86,7 @@ Signature chunks travel in the byte stream automatically — peers running `regi
87
86
  ### Signer — deterministic identity
88
87
 
89
88
  ```js
90
- import { Signer } from '@dtudury/streamo/public/streamo/Signer.js'
91
- import { bytesToHex } from '@dtudury/streamo/public/streamo/utils.js'
89
+ import { Signer, bytesToHex } from '@dtudury/streamo'
92
90
 
93
91
  const signer = new Signer('alice', 'my-password')
94
92
  const { publicKey } = await signer.keysFor('my-dataset')
@@ -100,8 +98,7 @@ Keys are derived with PBKDF2 so the same username + password always produces the
100
98
  ### RepoRegistry — multi-repo store
101
99
 
102
100
  ```js
103
- import { RepoRegistry } from '@dtudury/streamo/public/streamo/RepoRegistry.js'
104
- import { archiveSync } from '@dtudury/streamo/public/streamo/archiveSync.js'
101
+ import { RepoRegistry, Repo, archiveSync } from '@dtudury/streamo'
105
102
 
106
103
  const registry = new RepoRegistry(async key => {
107
104
  const repo = new Repo()
@@ -115,7 +112,7 @@ const repo = await registry.open(publicKeyHex)
115
112
  ### registrySync — peer sync over WebSocket
116
113
 
117
114
  ```js
118
- import { registrySync } from '@dtudury/streamo/public/streamo/registrySync.js'
115
+ import { registrySync } from '@dtudury/streamo'
119
116
 
120
117
  const session = await registrySync(registry, 'localhost', 8080, {
121
118
  // only sync repos you care about
@@ -137,9 +134,7 @@ session.announce(myKey, rootKey) // tell interested peers about your repo
137
134
  ### h + mount — reactive UI
138
135
 
139
136
  ```js
140
- import { h } from '@dtudury/streamo/public/streamo/h.js'
141
- import { mount } from '@dtudury/streamo/public/streamo/mount.js'
142
- import { Recaller } from '@dtudury/streamo/public/streamo/utils/Recaller.js'
137
+ import { h, mount, Recaller } from '@dtudury/streamo'
143
138
 
144
139
  const recaller = new Recaller('app')
145
140
 
@@ -153,10 +148,13 @@ mount(h`
153
148
 
154
149
  Functions interpolated as `${() => ...}` are reactive cells — they re-run automatically whenever the data they read changes. No virtual DOM diffing; only the exact DOM nodes bound to changed data update. Elements are recycled across re-renders by `data-key` (or tag as a fallback), so user input and focus survive list reorders. SVG namespaces propagate automatically — `` h`<svg><path d="..."/></svg>` `` works without any extra wiring. `class` accepts an array (`['btn', isActive && 'active']`) or an object (`{btn: true, active: false}`); falsy entries are filtered out.
155
150
 
151
+ > **For lists that can reorder**, always set `data-key` on each item — the unkeyed positional fallback will recycle elements by tag in document order, which can attach the wrong DOM node (and any user focus/input on it) to the wrong vnode after a reorder.
152
+
156
153
  Any function can be used directly as a tag — it receives `{ ...attrs, children }` and returns virtual nodes:
157
154
 
158
155
  ```js
159
- import { StreamoComponent, componentKey, defineComponent } from '@dtudury/streamo/public/streamo/StreamoComponent.js'
156
+ // StreamoComponent extends HTMLElement, so it's only importable in a browser context:
157
+ import { StreamoComponent, componentKey, defineComponent } from '@dtudury/streamo/StreamoComponent.js'
160
158
 
161
159
  function Card ({ title, children }) {
162
160
  return h`<div class="card"><h2>${title}</h2>${children}</div>`
@@ -179,17 +177,27 @@ For hot-reloading, `componentKey(prefix, address)` and `defineComponent(name, fn
179
177
  | `s3Sync` | replicate chunks to S3-compatible object storage |
180
178
  | `stateFileSync` | write repo state as JSON on every change |
181
179
 
182
- ## chat example
180
+ ## the all-in-one demo
181
+
182
+ The chat server is also the website server. Run it once and you get the
183
+ homepage, chat app, **and** the repo explorer all on the same origin:
183
184
 
184
185
  ```bash
185
- # start the server its public key becomes the room key
186
+ # start the all-in-one demo server
186
187
  STREAMO_NAME=my-chat STREAMO_USERNAME=relay STREAMO_PASSWORD=secret \
187
188
  node public/apps/chat/server.js
188
189
 
189
- # join from the browser
190
+ # homepage with app cards
191
+ open http://localhost:8080/
192
+
193
+ # chat
190
194
  open http://localhost:8080/apps/chat/
191
195
 
192
- # join from the terminal
196
+ # repo explorer leave it open in another tab to watch commits roll in
197
+ # as you chat
198
+ open http://localhost:8080/apps/explorer/
199
+
200
+ # join chat from the terminal
193
201
  node public/streamo/chat-cli.js alice secret localhost 8080
194
202
  ```
195
203
 
@@ -198,7 +206,7 @@ Each participant owns their own message stream. The server is just another strea
198
206
  ## tests
199
207
 
200
208
  ```bash
201
- node --test # all tests
209
+ npm test # all tests
202
210
  node --test public/streamo/Repo.test.js # single file
203
211
  ```
204
212
 
package/index.js ADDED
@@ -0,0 +1,32 @@
1
+ // Public API. Most users want named imports from here:
2
+ //
3
+ // import { Repo, Signer, registrySync } from '@dtudury/streamo'
4
+ //
5
+ // For advanced/internal use, subpath imports also work:
6
+ //
7
+ // import { Recaller } from '@dtudury/streamo/utils/Recaller.js'
8
+ //
9
+ // StreamoComponent is intentionally NOT re-exported here: it extends
10
+ // HTMLElement at module-load time and cannot be imported in Node. Browser
11
+ // consumers should subpath-import it directly:
12
+ //
13
+ // import { StreamoComponent, defineComponent } from '@dtudury/streamo/StreamoComponent.js'
14
+
15
+ export { Streamo, ConflictError, changedPaths } from './public/streamo/Streamo.js'
16
+ export { Repo } from './public/streamo/Repo.js'
17
+ export { Signer, verifySignature } from './public/streamo/Signer.js'
18
+ export { Signature } from './public/streamo/Signature.js'
19
+ export { RepoRegistry } from './public/streamo/RepoRegistry.js'
20
+ export { registrySync, handleRegistryPeer } from './public/streamo/registrySync.js'
21
+ export { archiveSync } from './public/streamo/archiveSync.js'
22
+ export { fileSync } from './public/streamo/fileSync.js'
23
+ export { originSync } from './public/streamo/originSync.js'
24
+ export { outletSync, attachStreamSync } from './public/streamo/outletSync.js'
25
+ export { s3Sync } from './public/streamo/s3Sync.js'
26
+ export { stateFileSync } from './public/streamo/stateFileSync.js'
27
+ export { webSync } from './public/streamo/webSync.js'
28
+ export { StreamoServer } from './public/streamo/StreamoServer.js'
29
+ export { h, HElement, HText } from './public/streamo/h.js'
30
+ export { mount, dismount } from './public/streamo/mount.js'
31
+ export { Recaller } from './public/streamo/utils/Recaller.js'
32
+ export { bytesToHex, hexToBytes } from './public/streamo/utils.js'
package/package.json CHANGED
@@ -1,15 +1,28 @@
1
1
  {
2
2
  "name": "@dtudury/streamo",
3
- "version": "1.0.1",
3
+ "version": "3.0.0",
4
4
  "description": "peer-to-peer sync where your data and identity belong to you, not the server",
5
5
  "keywords": ["p2p", "peer-to-peer", "sync", "reactive", "content-addressed", "websocket", "signed", "append-only", "offline-first", "cryptographic", "identity"],
6
6
  "repository": "git@github.com:dtudury/streamo.git",
7
7
  "author": "David Tudury <david.tudury@gmail.com>",
8
8
  "license": "AGPL-3.0-only",
9
+ "type": "module",
10
+ "main": "./index.js",
11
+ "exports": {
12
+ ".": "./index.js",
13
+ "./*": "./public/streamo/*"
14
+ },
15
+ "files": [
16
+ "index.js",
17
+ "bin/",
18
+ "public/",
19
+ "!**/*.test.js",
20
+ "!public/streamo/utils/testing.js",
21
+ "!public/streamo/utils/mockDOM.js"
22
+ ],
9
23
  "bin": {
10
24
  "streamo": "./bin/streamo.js"
11
25
  },
12
- "type": "module",
13
26
  "dependencies": {
14
27
  "@aws-sdk/client-s3": "^3.958.0",
15
28
  "@gerhobbelt/gitignore-parser": "^0.2.0-9",
@@ -22,6 +35,7 @@
22
35
  "ws": "^8.18.3"
23
36
  },
24
37
  "scripts": {
38
+ "test": "node --test",
25
39
  "serve": "node bin/streamo.js --env-file .env.dev --web 3000 --interactive",
26
40
  "chat": "node public/apps/chat/server.js --env-file .env.dev"
27
41
  }
@@ -0,0 +1,118 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>streamo explorer</title>
7
+ <link rel="stylesheet" href="/apps/styles/proto.css">
8
+ <style>
9
+ body { max-width: 60rem; margin: 0 auto; padding: 2rem 1.25rem; }
10
+
11
+ .header { display: flex; align-items: baseline; gap: 0.75rem; margin-bottom: 0.25rem; }
12
+ .wordmark { font-size: 1.6rem; letter-spacing: -0.02em; }
13
+ .crumbs { font-size: 0.85rem; color: var(--ink-dim); }
14
+ .back { cursor: pointer; color: var(--ink-dim); font-size: 0.85rem; display: inline-block; margin-bottom: 1rem; }
15
+ .back:hover { color: var(--ink); }
16
+
17
+ h2 { font-size: 1.05rem; font-weight: 600; margin: 1.25rem 0 0.5rem; }
18
+ h2 .dim { font-weight: 400; font-size: 0.9rem; }
19
+
20
+ .row {
21
+ display: grid;
22
+ grid-template-columns: 1fr 12rem 14rem;
23
+ gap: 0.75rem;
24
+ align-items: baseline;
25
+ padding: 0.55rem 0.75rem;
26
+ border: 1.5px solid transparent;
27
+ border-radius: var(--radius);
28
+ cursor: pointer;
29
+ }
30
+ .row:hover { border-color: var(--ink); background: rgba(254, 240, 138, 0.4); }
31
+ .row + .row { border-top-color: var(--rule); }
32
+ .row:hover + .row { border-top-color: transparent; }
33
+
34
+ /* signature rows show 4 columns: kind, range, hex, addr */
35
+ .row.signature { grid-template-columns: 4rem 1fr 1fr 6rem; }
36
+ .row.commit { grid-template-columns: 4rem 1fr 12rem 6rem; }
37
+
38
+ .row .mono { font-size: 0.85rem; }
39
+ .row .when { font-size: 0.78rem; color: var(--ink-dim); }
40
+ .row .msg { font-size: 0.85rem; }
41
+ .row .kind {
42
+ font-size: 0.7rem;
43
+ text-transform: uppercase;
44
+ letter-spacing: 0.08em;
45
+ color: var(--ink-dim);
46
+ border: 1px solid var(--rule);
47
+ border-radius: 999px;
48
+ padding: 0.05rem 0.5rem;
49
+ text-align: center;
50
+ align-self: center;
51
+ }
52
+ .row.commit .kind { color: var(--accent); border-color: var(--accent); }
53
+ .row.signature .kind { color: var(--warn); border-color: var(--warn); }
54
+
55
+ .empty { color: var(--ink-dim); padding: 0.5rem 0.75rem; font-size: 0.9rem; }
56
+
57
+ /* key/value table for the at-view */
58
+ .kv { width: 100%; border-collapse: collapse; font-size: 0.85rem; margin: 0.75rem 0; }
59
+ .kv td { padding: 0.4rem 0.6rem; vertical-align: top; }
60
+ .kv tr + tr td { border-top: 1px dashed var(--rule); }
61
+ .kv td:first-child {
62
+ color: var(--ink-dim);
63
+ width: 8rem;
64
+ font-size: 0.78rem;
65
+ text-transform: uppercase;
66
+ letter-spacing: 0.06em;
67
+ }
68
+
69
+ /* clickable variant — whole row is the click target */
70
+ .kv.clickable tr { cursor: pointer; }
71
+ .kv.clickable tr:hover td { background: rgba(254, 240, 138, 0.4); }
72
+ .kv.clickable td:last-child { color: var(--accent); text-align: right; }
73
+
74
+ .addr-link {
75
+ font-family: monospace;
76
+ font-size: 0.85rem;
77
+ color: var(--accent);
78
+ cursor: pointer;
79
+ text-decoration: underline dotted;
80
+ }
81
+ .addr-link:hover { background: var(--flash); text-decoration-style: solid; }
82
+
83
+ .paths { list-style: none; padding: 0; }
84
+ .paths li { padding: 0.2rem 0.5rem; font-size: 0.85rem; }
85
+ .paths li + li { border-top: 1px dashed var(--rule); }
86
+
87
+ h3 { font-size: 0.9rem; font-weight: 600; margin: 1.25rem 0 0.5rem; }
88
+ h3 .dim { font-weight: 400; font-size: 0.85rem; }
89
+
90
+ .conn { font-size: 0.75rem; color: var(--ink-dim); margin-bottom: 1.5rem; }
91
+ .conn.ok { color: #16a34a; }
92
+ .conn.err { color: #dc2626; }
93
+
94
+ .keyfull { font-family: monospace; font-size: 0.78rem; color: var(--ink-dim); word-break: break-all; }
95
+
96
+ pre.value {
97
+ font-family: monospace;
98
+ font-size: 0.8rem;
99
+ background: var(--rule);
100
+ border-radius: var(--radius);
101
+ padding: 1rem;
102
+ overflow-x: auto;
103
+ white-space: pre-wrap;
104
+ word-break: break-word;
105
+ }
106
+ </style>
107
+ </head>
108
+ <body>
109
+ <div class="header">
110
+ <div class="wordmark">streamo</div>
111
+ <div class="crumbs">explorer</div>
112
+ </div>
113
+ <div id="conn" class="conn">connecting…</div>
114
+ <div id="app"></div>
115
+
116
+ <script type="module" src="./main.js"></script>
117
+ </body>
118
+ </html>