OverviewFilesCommitsIssuesDocsReleasesPeers

pgh-runtime commits / a28d35c

Initial extraction from pear-git-v3/lib/pgh-runtime.js
pgh-runtime is the per-app OTA updater embedded in pgh-installed apps. Previously lived at pear-git-v3/lib/pgh-runtime.js and depended on pear-git-v3/lib/pgh/tree-install.js. Extracted to its own package (P.10 Phase P.11a) so: - pgh's bootstrap bundle can be minimum-viable (target <50MB) - apps that embed pgh-runtime don't pull in the pgh CLI transitively - standalone testing possible with any store+fetcher implementation index.js exports: PghRuntime — EventEmitter class, start/stop lifecycle, polling, updating/updated/error events, readyToApply() installTree — utility that recursively fetches + materializes a tree hierarchy. Inlined here (no longer imported from pgh's tree-install.js) so pgh-runtime depends only on hyper-overlay for the KIND_DIR constant. Deps: hyper-overlay — KIND_DIR constant only (file: ../hyper-overlay during transition; proper version range once resolvable). Tests: 11 / 32 asserts covering lifecycle, no-release, same-hash-skip, happy path, readyToApply, error handling (fetcher throw + installTree failure), polling, standalone installTree (nested walk + skip). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a28d35c80a5ee736c6ef13f7e83aae0c0343a261
by plankatron <fleeky@gmail.com> · 2026-04-18T15:28:19.000Z

Changed files (5) +1501 -0

.gitignore +3 -0
……@@ -0,0 +1,3 @@
1node_modules/
2*.log
3.DS_Store
index.js +217 -0
……@@ -0,0 +1,217 @@
1/**
2 * pgh-runtime — per-app OTA updater.
3 *
4 * An installed app imports this module and instantiates a PghRuntime
5 * with its own upgrade source, a store handle, and a fetcher. The
6 * runtime periodically checks for a newer release and, when found,
7 * materializes the new tree into the store and emits events. The app
8 * decides when to actually apply the update (typically on next restart).
9 *
10 * Mirrors pear-runtime-updater's shape (~127 LOC) but uses hyper-overlay
11 * tree content-addressing instead of Hyperdrive.
12 *
13 * Usage:
14 *
15 * import { PghRuntime } from 'pgh-runtime'
16 * const runtime = new PghRuntime({
17 * name: 'my-app',
18 * currentTreeHash: process.env.PGH_CURRENT_TREE_HASH,
19 * fetcher,
20 * store,
21 * interval: 60 * 60 * 1000
22 * })
23 * runtime.on('updating', () => console.log('downloading...'))
24 * runtime.on('updated', (info) => console.log('ready:', info.newTreeHash))
25 * runtime.on('error', (err) => console.error(err))
26 * await runtime.start()
27 *
28 * Expected interfaces:
29 *
30 * fetcher:
31 * async getLatestRelease(name) -> { treeHash, version, ... } | null
32 * async getTree(hash) -> { entries: [...], size }
33 * async getContent(contentHash)-> Buffer
34 *
35 * store (TreeStore-compatible):
36 * init()
37 * hasTree(hash) -> boolean
38 * materialize(hash, entries, { fetchContent, fetchTree, source? })
39 *
40 * The runtime is transport-agnostic — any fetcher conforming to that
41 * shape works. See pgh's NetworkFetcher for the Hyperswarm implementation.
42 */
43
44import EventEmitter from 'events'
45
46import { KIND_DIR } from 'hyper-overlay/tree'
47
48const DEFAULT_INTERVAL = 30 * 60 * 1000 // 30 min
49
50export class PghRuntime extends EventEmitter {
51 /**
52 * @param {object} opts
53 * @param {string} opts.name app name
54 * @param {string} [opts.currentTreeHash] hex hash of the tree this app currently runs at
55 * @param {object} opts.fetcher { getTree, getContent, getLatestRelease(name) }
56 * @param {object} opts.store TreeStore-compatible (hasTree / materialize / init)
57 * @param {number} [opts.interval] ms between polls (default 30m; 0 = no polling)
58 * @param {boolean} [opts.startImmediately] do an initial check on start (default true)
59 */
60 constructor (opts = {}) {
61 super()
62 if (!opts.name) throw new Error('PghRuntime requires opts.name')
63 if (!opts.fetcher) throw new Error('PghRuntime requires opts.fetcher')
64 if (!opts.store) throw new Error('PghRuntime requires opts.store')
65
66 this.name = opts.name
67 this.currentTreeHash = opts.currentTreeHash || null
68 this.fetcher = opts.fetcher
69 this.store = opts.store
70 this.interval = opts.interval != null ? opts.interval : DEFAULT_INTERVAL
71 this.startImmediately = opts.startImmediately !== false
72
73 this.latest = null
74 this.updating = false
75 this.updated = false
76 this.newTreeHash = null
77
78 this._timer = null
79 this._started = false
80 }
81
82 async start () {
83 if (this._started) return
84 this._started = true
85 if (this.startImmediately) await this._check()
86 if (this.interval > 0) {
87 this._timer = setInterval(() => { this._check().catch(e => this.emit('error', e)) }, this.interval)
88 if (this._timer.unref) this._timer.unref()
89 }
90 }
91
92 async stop () {
93 if (!this._started) return
94 this._started = false
95 if (this._timer) { clearInterval(this._timer); this._timer = null }
96 }
97
98 /**
99 * Force an immediate check. Returns the release entry if a new tree
100 * was materialized, null otherwise.
101 */
102 async checkNow () {
103 return this._check()
104 }
105
106 async _check () {
107 let release
108 try {
109 release = await this.fetcher.getLatestRelease(this.name)
110 } catch (err) {
111 this.emit('error', err)
112 return null
113 }
114
115 if (!release) return null
116
117 // Nothing to do if we already run this tree.
118 if (this.currentTreeHash && release.treeHash === this.currentTreeHash) {
119 this.latest = release
120 return null
121 }
122 // We've already staged this tree; don't re-emit.
123 if (this.updated && release.treeHash === this.newTreeHash) {
124 return null
125 }
126
127 this.latest = release
128 this.updating = true
129 this.emit('updating', release)
130
131 try {
132 const result = await installTree(this.store, release.treeHash, this.fetcher, {
133 source: 'pgh-runtime:' + this.name
134 })
135 this.newTreeHash = release.treeHash
136 this.updating = false
137 this.updated = true
138 this.emit('updated', {
139 name: this.name,
140 newTreeHash: release.treeHash,
141 release,
142 materialized: result.materialized,
143 skipped: result.skipped
144 })
145 return release
146 } catch (err) {
147 this.updating = false
148 this.emit('error', err)
149 return null
150 }
151 }
152
153 /**
154 * Descriptor for the staged update. Apply via the app's own mechanism
155 * (pgh app update <name> --tree <hash> is the usual CLI path).
156 */
157 readyToApply () {
158 if (!this.updated) return null
159 return {
160 name: this.name,
161 previousTreeHash: this.currentTreeHash,
162 newTreeHash: this.newTreeHash,
163 release: this.latest
164 }
165 }
166}
167
168/**
169 * Walk a tree from the root, fetching each subtree and materializing
170 * content into the store. Inlined here so pgh-runtime has no dependency
171 * on the pgh CLI package — any store + fetcher that conform to the
172 * expected shapes work.
173 *
174 * @param {object} store — TreeStore-compatible
175 * @param {string} rootHash
176 * @param {object} fetcher — { getTree, getContent }
177 * @param {object} [opts]
178 * @returns {Promise<{ materialized: number, skipped: number }>}
179 */
180export async function installTree (store, rootHash, fetcher, opts = {}) {
181 store.init()
182 let materialized = 0
183 let skipped = 0
184 const seen = new Set()
185
186 async function visit (hash) {
187 if (seen.has(hash)) return
188 seen.add(hash)
189
190 if (store.hasTree(hash)) {
191 skipped++
192 return
193 }
194
195 const tree = await fetcher.getTree(hash)
196 if (!tree || !Array.isArray(tree.entries)) {
197 throw new Error('fetcher returned invalid tree for ' + hash)
198 }
199
200 for (const e of tree.entries) {
201 if (e.kind === KIND_DIR) await visit(e.childHash)
202 }
203
204 await store.materialize(hash, tree.entries, {
205 fetchContent: fetcher.getContent.bind(fetcher),
206 fetchTree: async (h) => {
207 const t = await fetcher.getTree(h)
208 return t.entries
209 },
210 source: opts.source
211 })
212 materialized++
213 }
214
215 await visit(rootHash)
216 return { materialized, skipped }
217}
package-lock.json +999 -0
……@@ -0,0 +1,999 @@
1{
2 "name": "pgh-runtime",
3 "version": "0.1.0",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "pgh-runtime",
9 "version": "0.1.0",
10 "license": "AGPL-3.0",
11 "dependencies": {
12 "hyper-overlay": "file:../hyper-overlay"
13 },
14 "devDependencies": {
15 "brittle": "^3.0.0",
16 "hypercore-crypto": "^3.0.0"
17 }
18 },
19 "../hyper-overlay": {
20 "version": "0.2.0",
21 "license": "AGPL-3.0",
22 "dependencies": {
23 "@hyperswarm/testnet": "^3.1.4",
24 "b4a": "^1.0.0",
25 "bare-fs": "^4.6.0",
26 "bare-http1": "^4.5.5",
27 "bare-native": "^0.1.2",
28 "bare-os": "^3.8.7",
29 "bare-path": "^3.0.0",
30 "bare-process": "^4.4.1",
31 "bare-zlib": "^1.3.3",
32 "blind-pairing": "^2.3.1",
33 "compact-encoding": "^2.0.0",
34 "corestore": "^7.0.0",
35 "hyperbee": "^2.0.0",
36 "hypercore-crypto": "^3.0.0",
37 "hyperswarm": "^4.0.0",
38 "localwatch": "^1.4.3",
39 "protomux": "^3.0.0",
40 "quickbit-universal": "^2.0.0",
41 "ready-resource": "^1.0.0",
42 "streamx": "^2.0.0"
43 },
44 "devDependencies": {
45 "brittle": "^3.0.0"
46 },
47 "optionalDependencies": {
48 "fuse-native": "^2.2.6"
49 }
50 },
51 "node_modules/@jridgewell/resolve-uri": {
52 "version": "3.1.2",
53 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
54 "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
55 "dev": true,
56 "license": "MIT",
57 "engines": {
58 "node": ">=6.0.0"
59 }
60 },
61 "node_modules/@jridgewell/sourcemap-codec": {
62 "version": "1.5.5",
63 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
64 "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
65 "dev": true,
66 "license": "MIT"
67 },
68 "node_modules/@jridgewell/trace-mapping": {
69 "version": "0.3.31",
70 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
71 "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
72 "dev": true,
73 "license": "MIT",
74 "dependencies": {
75 "@jridgewell/resolve-uri": "^3.1.0",
76 "@jridgewell/sourcemap-codec": "^1.4.14"
77 }
78 },
79 "node_modules/@types/istanbul-lib-coverage": {
80 "version": "2.0.6",
81 "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
82 "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
83 "dev": true,
84 "license": "MIT"
85 },
86 "node_modules/b4a": {
87 "version": "1.8.0",
88 "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
89 "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
90 "dev": true,
91 "license": "Apache-2.0",
92 "peerDependencies": {
93 "react-native-b4a": "*"
94 },
95 "peerDependenciesMeta": {
96 "react-native-b4a": {
97 "optional": true
98 }
99 }
100 },
101 "node_modules/bare-abort": {
102 "version": "2.0.13",
103 "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz",
104 "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==",
105 "dev": true,
106 "license": "Apache-2.0"
107 },
108 "node_modules/bare-addon-resolve": {
109 "version": "1.10.0",
110 "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz",
111 "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==",
112 "dev": true,
113 "license": "Apache-2.0",
114 "dependencies": {
115 "bare-module-resolve": "^1.10.0",
116 "bare-semver": "^1.0.0"
117 },
118 "peerDependencies": {
119 "bare-url": "*"
120 },
121 "peerDependenciesMeta": {
122 "bare-url": {
123 "optional": true
124 }
125 }
126 },
127 "node_modules/bare-ansi-escapes": {
128 "version": "2.2.3",
129 "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz",
130 "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==",
131 "dev": true,
132 "license": "Apache-2.0",
133 "dependencies": {
134 "bare-stream": "^2.6.5"
135 },
136 "peerDependencies": {
137 "bare-buffer": "*"
138 },
139 "peerDependenciesMeta": {
140 "bare-buffer": {
141 "optional": true
142 }
143 }
144 },
145 "node_modules/bare-assert": {
146 "version": "1.2.0",
147 "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz",
148 "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==",
149 "dev": true,
150 "license": "Apache-2.0",
151 "dependencies": {
152 "bare-inspect": "^3.1.2"
153 }
154 },
155 "node_modules/bare-bundle": {
156 "version": "1.10.0",
157 "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz",
158 "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==",
159 "dev": true,
160 "license": "Apache-2.0",
161 "peerDependencies": {
162 "bare-buffer": "*",
163 "bare-url": "*"
164 },
165 "peerDependenciesMeta": {
166 "bare-buffer": {
167 "optional": true
168 },
169 "bare-url": {
170 "optional": true
171 }
172 }
173 },
174 "node_modules/bare-cov": {
175 "version": "1.2.1",
176 "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz",
177 "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==",
178 "dev": true,
179 "license": "Apache-2.0",
180 "dependencies": {
181 "bare-fs": "^4.1.2",
182 "bare-inspector": "^4.0.1",
183 "bare-path": "^3.0.0",
184 "bare-process": "^4.2.1",
185 "bare-url": "^2.1.5",
186 "bare-v8-to-istanbul": "^1.0.2",
187 "picomatch": "^4.0.2",
188 "which-runtime": "^1.2.1"
189 }
190 },
191 "node_modules/bare-crypto": {
192 "version": "1.13.4",
193 "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.4.tgz",
194 "integrity": "sha512-JiCZ5l2YOG1y8J7yy1BCAKTCZrPnHLb7pDRIdurBTOn5oIwBQDIv8iH5Pl2V85vzjl1NZXRfNY4HZLsE942jJA==",
195 "dev": true,
196 "license": "Apache-2.0",
197 "dependencies": {
198 "bare-assert": "^1.2.0",
199 "bare-stream": "^2.6.3"
200 },
201 "peerDependencies": {
202 "bare-buffer": "*"
203 },
204 "peerDependenciesMeta": {
205 "bare-buffer": {
206 "optional": true
207 }
208 }
209 },
210 "node_modules/bare-debug-log": {
211 "version": "2.0.0",
212 "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz",
213 "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==",
214 "dev": true,
215 "license": "Apache-2.0",
216 "dependencies": {
217 "bare-os": "^3.0.1"
218 }
219 },
220 "node_modules/bare-dns": {
221 "version": "2.1.4",
222 "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz",
223 "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==",
224 "dev": true,
225 "license": "Apache-2.0",
226 "engines": {
227 "bare": ">=1.7.0"
228 }
229 },
230 "node_modules/bare-encoding": {
231 "version": "1.0.3",
232 "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz",
233 "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==",
234 "dev": true,
235 "license": "Apache-2.0",
236 "peerDependencies": {
237 "bare-buffer": "*"
238 },
239 "peerDependenciesMeta": {
240 "bare-buffer": {
241 "optional": true
242 }
243 }
244 },
245 "node_modules/bare-env": {
246 "version": "3.0.0",
247 "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz",
248 "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==",
249 "dev": true,
250 "license": "Apache-2.0",
251 "dependencies": {
252 "bare-os": "^3.0.1"
253 }
254 },
255 "node_modules/bare-events": {
256 "version": "2.8.2",
257 "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
258 "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
259 "dev": true,
260 "license": "Apache-2.0",
261 "peerDependencies": {
262 "bare-abort-controller": "*"
263 },
264 "peerDependenciesMeta": {
265 "bare-abort-controller": {
266 "optional": true
267 }
268 }
269 },
270 "node_modules/bare-format": {
271 "version": "1.0.2",
272 "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz",
273 "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==",
274 "dev": true,
275 "license": "Apache-2.0",
276 "dependencies": {
277 "bare-inspect": "^3.0.0"
278 }
279 },
280 "node_modules/bare-fs": {
281 "version": "4.7.1",
282 "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz",
283 "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==",
284 "dev": true,
285 "license": "Apache-2.0",
286 "dependencies": {
287 "bare-events": "^2.5.4",
288 "bare-path": "^3.0.0",
289 "bare-stream": "^2.6.4",
290 "bare-url": "^2.2.2",
291 "fast-fifo": "^1.3.2"
292 },
293 "engines": {
294 "bare": ">=1.16.0"
295 },
296 "peerDependencies": {
297 "bare-buffer": "*"
298 },
299 "peerDependenciesMeta": {
300 "bare-buffer": {
301 "optional": true
302 }
303 }
304 },
305 "node_modules/bare-hrtime": {
306 "version": "2.1.1",
307 "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz",
308 "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==",
309 "dev": true,
310 "license": "Apache-2.0"
311 },
312 "node_modules/bare-http-parser": {
313 "version": "1.1.3",
314 "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.1.3.tgz",
315 "integrity": "sha512-+dhVvQi6brHq14L/XHNRQ+TLuVE76VjRmMt61wVEtS+Od8xUslfMHWJN/ZjIIt3RtTG6vPuA+x9cOh7KrkBJsA==",
316 "dev": true,
317 "license": "Apache-2.0"
318 },
319 "node_modules/bare-http1": {
320 "version": "4.5.6",
321 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.6.tgz",
322 "integrity": "sha512-31OAwMkSU+z1VuUOCk65hx3aWQgzCfH/zQ6LGxbJtmiy2Czsw0+uvOBM9YkqaL6zUSTSYG2pLbL0v/TjME3Buw==",
323 "dev": true,
324 "license": "Apache-2.0",
325 "dependencies": {
326 "bare-events": "^2.6.0",
327 "bare-http-parser": "^1.1.1",
328 "bare-stream": "^2.10.0",
329 "bare-tcp": "^2.2.0"
330 },
331 "peerDependencies": {
332 "bare-buffer": "*",
333 "bare-url": "*"
334 },
335 "peerDependenciesMeta": {
336 "bare-buffer": {
337 "optional": true
338 },
339 "bare-url": {
340 "optional": true
341 }
342 }
343 },
344 "node_modules/bare-https": {
345 "version": "2.1.3",
346 "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.3.tgz",
347 "integrity": "sha512-0TI/mJXQGYXmG7UUyWEG+KCJusayIAQLywUjFAskDoKuxfqVnGF+M/mTMrEV8J64DaIdU+5x761FvgmT7M68tA==",
348 "dev": true,
349 "license": "Apache-2.0",
350 "dependencies": {
351 "bare-http1": "^4.4.0",
352 "bare-tcp": "^2.2.0",
353 "bare-tls": "^2.0.0"
354 }
355 },
356 "node_modules/bare-inspect": {
357 "version": "3.1.4",
358 "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz",
359 "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==",
360 "dev": true,
361 "license": "Apache-2.0",
362 "dependencies": {
363 "bare-ansi-escapes": "^2.1.0",
364 "bare-type": "^1.0.0"
365 },
366 "engines": {
367 "bare": ">=1.18.0"
368 }
369 },
370 "node_modules/bare-inspector": {
371 "version": "4.0.3",
372 "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz",
373 "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==",
374 "dev": true,
375 "license": "Apache-2.0",
376 "dependencies": {
377 "bare-events": "^2.1.0",
378 "bare-http1": "^4.0.0",
379 "bare-stream": "^2.0.0",
380 "bare-url": "^2.0.0",
381 "bare-ws": "^2.0.0"
382 },
383 "engines": {
384 "bare": ">=1.2.0"
385 },
386 "peerDependencies": {
387 "bare-tcp": "*"
388 },
389 "peerDependenciesMeta": {
390 "bare-tcp": {
391 "optional": true
392 }
393 }
394 },
395 "node_modules/bare-module": {
396 "version": "6.2.0",
397 "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.2.0.tgz",
398 "integrity": "sha512-CoCTG8y8HKPnNW317OW1hynl28DasVPtbX033ZEAXk0Q3SYCUXi3OOnNAr6wTrDgMgpOsW0kl7Nifcom5GGH8Q==",
399 "dev": true,
400 "license": "Apache-2.0",
401 "dependencies": {
402 "bare-bundle": "^1.3.0",
403 "bare-module-lexer": "^1.0.0",
404 "bare-module-resolve": "^1.8.0",
405 "bare-path": "^3.0.0",
406 "bare-url": "^2.0.1"
407 },
408 "engines": {
409 "bare": ">=1.23.0"
410 },
411 "peerDependencies": {
412 "bare-buffer": "*"
413 },
414 "peerDependenciesMeta": {
415 "bare-buffer": {
416 "optional": true
417 }
418 }
419 },
420 "node_modules/bare-module-lexer": {
421 "version": "1.4.7",
422 "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz",
423 "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==",
424 "dev": true,
425 "license": "Apache-2.0",
426 "dependencies": {
427 "require-addon": "^1.0.2"
428 },
429 "peerDependencies": {
430 "bare-buffer": "*"
431 },
432 "peerDependenciesMeta": {
433 "bare-buffer": {
434 "optional": true
435 }
436 }
437 },
438 "node_modules/bare-module-resolve": {
439 "version": "1.12.1",
440 "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz",
441 "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==",
442 "dev": true,
443 "license": "Apache-2.0",
444 "dependencies": {
445 "bare-semver": "^1.0.0"
446 },
447 "peerDependencies": {
448 "bare-url": "*"
449 },
450 "peerDependenciesMeta": {
451 "bare-url": {
452 "optional": true
453 }
454 }
455 },
456 "node_modules/bare-net": {
457 "version": "2.3.1",
458 "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.1.tgz",
459 "integrity": "sha512-MypSqDKpDU2Xt7FIfazn5yGvRnV09gFcIPHGWstW0gxuzA4tucTcwJSZeos97C4F89vtU5oGwXDN/HrGN6Y4Jw==",
460 "dev": true,
461 "license": "Apache-2.0",
462 "dependencies": {
463 "bare-events": "^2.2.2",
464 "bare-pipe": "^4.0.0",
465 "bare-stream": "^2.0.0",
466 "bare-tcp": "^2.0.0"
467 }
468 },
469 "node_modules/bare-os": {
470 "version": "3.8.7",
471 "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz",
472 "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==",
473 "dev": true,
474 "license": "Apache-2.0",
475 "engines": {
476 "bare": ">=1.14.0"
477 }
478 },
479 "node_modules/bare-path": {
480 "version": "3.0.0",
481 "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
482 "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
483 "dev": true,
484 "license": "Apache-2.0",
485 "dependencies": {
486 "bare-os": "^3.0.1"
487 }
488 },
489 "node_modules/bare-pipe": {
490 "version": "4.1.5",
491 "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.5.tgz",
492 "integrity": "sha512-6OfxaG8JSkRh3Gc4hzHRsxNt+yu2PpN7lrv1V+T78GdknWQkVGwiEvu4m+1nbfk8cMVQ0TGxRvQ90XA4rhnTuw==",
493 "dev": true,
494 "license": "Apache-2.0",
495 "dependencies": {
496 "bare-events": "^2.0.0",
497 "bare-stream": "^2.0.0"
498 },
499 "engines": {
500 "bare": ">=1.16.0"
501 }
502 },
503 "node_modules/bare-process": {
504 "version": "4.4.1",
505 "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.4.1.tgz",
506 "integrity": "sha512-JfcTtymq6akqM2bdyTsP4A4GYJceBzb91k/ECmFps75uFf6uO33SM1bOZsRAqyRXExabsQJLn5S41NBl8HTM4A==",
507 "dev": true,
508 "license": "Apache-2.0",
509 "dependencies": {
510 "bare-abort": "^2.0.13",
511 "bare-env": "^3.0.0",
512 "bare-events": "^2.3.1",
513 "bare-hrtime": "^2.0.0",
514 "bare-os": "^3.7.1",
515 "bare-signals": "^4.0.0",
516 "bare-stdio": "^1.0.1"
517 }
518 },
519 "node_modules/bare-semver": {
520 "version": "1.0.3",
521 "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.3.tgz",
522 "integrity": "sha512-HS/A30bi2+PiRJfU6R4+Kp+6KeLSCSByjYM2iiobOKzLAvtu1CT+S8xWfiU7wz0erknjkUoC+yXy108tzIuP5Q==",
523 "dev": true,
524 "license": "Apache-2.0"
525 },
526 "node_modules/bare-signals": {
527 "version": "4.2.0",
528 "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz",
529 "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==",
530 "dev": true,
531 "license": "Apache-2.0",
532 "dependencies": {
533 "bare-events": "^2.5.3",
534 "bare-os": "^3.3.1"
535 },
536 "engines": {
537 "bare": ">=1.7.0"
538 }
539 },
540 "node_modules/bare-stdio": {
541 "version": "1.0.2",
542 "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.2.tgz",
543 "integrity": "sha512-3WJDqtvVGP4f+j68kyEC05umOYNwKJ1xG+YAXL8yZ605WgNqiRhVaFq+mVIhBt2eKNp7pa5vCQdhOt1pNh79SA==",
544 "dev": true,
545 "license": "Apache-2.0",
546 "dependencies": {
547 "bare-fs": "^4.5.2",
548 "bare-pipe": "^4.1.5",
549 "bare-tty": "^5.0.3"
550 }
551 },
552 "node_modules/bare-stream": {
553 "version": "2.13.0",
554 "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.0.tgz",
555 "integrity": "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==",
556 "dev": true,
557 "license": "Apache-2.0",
558 "dependencies": {
559 "streamx": "^2.25.0",
560 "teex": "^1.0.1"
561 },
562 "peerDependencies": {
563 "bare-abort-controller": "*",
564 "bare-buffer": "*",
565 "bare-events": "*"
566 },
567 "peerDependenciesMeta": {
568 "bare-abort-controller": {
569 "optional": true
570 },
571 "bare-buffer": {
572 "optional": true
573 },
574 "bare-events": {
575 "optional": true
576 }
577 }
578 },
579 "node_modules/bare-stylize": {
580 "version": "0.0.1",
581 "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz",
582 "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==",
583 "dev": true,
584 "license": "Apache-2.0",
585 "dependencies": {
586 "bare-ansi-escapes": "^2.2.3",
587 "bare-process": "^4.2.1"
588 }
589 },
590 "node_modules/bare-subprocess": {
591 "version": "5.2.3",
592 "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz",
593 "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==",
594 "dev": true,
595 "license": "Apache-2.0",
596 "dependencies": {
597 "bare-env": "^3.0.0",
598 "bare-events": "^2.5.4",
599 "bare-os": "^3.0.1",
600 "bare-pipe": "^4.0.0",
601 "bare-url": "^2.2.2"
602 },
603 "engines": {
604 "bare": ">=1.7.0"
605 },
606 "peerDependencies": {
607 "bare-buffer": "*"
608 },
609 "peerDependenciesMeta": {
610 "bare-buffer": {
611 "optional": true
612 }
613 }
614 },
615 "node_modules/bare-tcp": {
616 "version": "2.2.7",
617 "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.7.tgz",
618 "integrity": "sha512-rjpqNQ2cOCkNo3NeYA/W4GTK3DRkl8sDHO3uos+AEswUjLC8XXMQF8WrJCSjlIowCbS6NUVxKE92X5RGXjyefg==",
619 "dev": true,
620 "license": "Apache-2.0",
621 "dependencies": {
622 "bare-dns": "^2.0.4",
623 "bare-events": "^2.5.4",
624 "bare-stream": "^2.6.4"
625 },
626 "engines": {
627 "bare": ">=1.16.0"
628 }
629 },
630 "node_modules/bare-tls": {
631 "version": "2.2.3",
632 "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.2.3.tgz",
633 "integrity": "sha512-dPYBGEXtgLceRFMfGaF2/rqR86/xMxMyrM9ootO/gaRKL/z2uNHJs7aP7IOBtnJF8eUCt5qwMzbKfrKgDIxPLg==",
634 "dev": true,
635 "license": "Apache-2.0",
636 "dependencies": {
637 "bare-net": "^2.0.1",
638 "bare-stream": "^2.6.4"
639 },
640 "engines": {
641 "bare": ">=1.7.0"
642 }
643 },
644 "node_modules/bare-tty": {
645 "version": "5.1.0",
646 "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.1.0.tgz",
647 "integrity": "sha512-EZLvW4A+XiJgI3TW+e1pMME9PIJsfEXe/DA/WSKzIkq/v7Yarpv/rvG6Z5pGnpo4V/Bd+qopwnCLSR71hMMBYA==",
648 "dev": true,
649 "license": "Apache-2.0",
650 "dependencies": {
651 "bare-events": "^2.2.0",
652 "bare-signals": "^4.0.0",
653 "bare-stream": "^2.0.0"
654 },
655 "engines": {
656 "bare": ">=1.16.0"
657 }
658 },
659 "node_modules/bare-type": {
660 "version": "1.1.0",
661 "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz",
662 "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==",
663 "dev": true,
664 "license": "Apache-2.0",
665 "engines": {
666 "bare": ">=1.2.0"
667 }
668 },
669 "node_modules/bare-url": {
670 "version": "2.4.1",
671 "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.1.tgz",
672 "integrity": "sha512-fZapLWNB25gS+etK27NV9KgBNXgo2yeYHuj+OyPblQd6GYAE3JVy6aKxszMV5jhGGFwraXQKA5fldvf3lMyEqw==",
673 "dev": true,
674 "license": "Apache-2.0",
675 "peer": true,
676 "dependencies": {
677 "bare-path": "^3.0.0"
678 }
679 },
680 "node_modules/bare-utils": {
681 "version": "1.6.0",
682 "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz",
683 "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==",
684 "dev": true,
685 "license": "Apache-2.0",
686 "dependencies": {
687 "bare-debug-log": "^2.0.0",
688 "bare-encoding": "^1.0.0",
689 "bare-format": "^1.0.0",
690 "bare-inspect": "^3.0.0",
691 "bare-stylize": "^0.0.1",
692 "bare-type": "^1.0.6"
693 }
694 },
695 "node_modules/bare-v8-to-istanbul": {
696 "version": "1.0.2",
697 "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz",
698 "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==",
699 "dev": true,
700 "license": "Apache-2.0",
701 "dependencies": {
702 "bare-assert": "^1.0.2",
703 "bare-fs": "^4.1.2",
704 "bare-module": "^6.1.2",
705 "bare-path": "^3.0.0",
706 "bare-process": "^4.2.0",
707 "bare-url": "^2.1.5",
708 "bare-utils": "^1.2.0",
709 "v8-to-istanbul": "^9.3.0",
710 "which-runtime": "^1.2.1"
711 }
712 },
713 "node_modules/bare-ws": {
714 "version": "2.1.0",
715 "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.1.0.tgz",
716 "integrity": "sha512-2gEWlPK9iyBchACdIY6oQXgmDz3KLrChdwrPgmU3IVXOFTnxTqSUT27WE/+izd4QojHj/SsqDkQiD2HDvuTdAA==",
717 "dev": true,
718 "license": "Apache-2.0",
719 "dependencies": {
720 "bare-crypto": "^1.2.0",
721 "bare-events": "^2.3.1",
722 "bare-http1": "^4.0.0",
723 "bare-https": "^2.0.0",
724 "bare-stream": "^2.1.2"
725 },
726 "peerDependencies": {
727 "bare-buffer": "*",
728 "bare-url": "*"
729 },
730 "peerDependenciesMeta": {
731 "bare-buffer": {
732 "optional": true
733 },
734 "bare-url": {
735 "optional": true
736 }
737 }
738 },
739 "node_modules/brittle": {
740 "version": "3.19.1",
741 "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz",
742 "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==",
743 "dev": true,
744 "license": "Apache-2.0",
745 "dependencies": {
746 "b4a": "^1.6.0",
747 "bare-assert": "^1.0.2",
748 "bare-cov": "^1.1.0",
749 "bare-fs": "^4.1.6",
750 "bare-os": "^3.6.1",
751 "bare-path": "^3.0.0",
752 "bare-process": "^4.2.1",
753 "bare-subprocess": "^5.0.0",
754 "bare-url": "^2.1.6",
755 "error-stack-parser": "^2.1.4",
756 "globbie": "^1.0.2",
757 "paparam": "^1.6.2",
758 "same-object": "^1.0.2",
759 "test-tmp": "^1.4.0",
760 "tmatch": "^5.0.0"
761 },
762 "bin": {
763 "brittle": "bin/node.js",
764 "brittle-bare": "bin/bare.js",
765 "brittle-node": "bin/node.js",
766 "brittle-pear": "bin/pear.js"
767 }
768 },
769 "node_modules/compact-encoding": {
770 "version": "2.19.2",
771 "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.19.2.tgz",
772 "integrity": "sha512-/YjhHQE/5L4F7l5Bht69dRbP9RV6zoJPeowi8bMKQxNKe3Nh6hOY8pBGoVE9fz5GaWfEd8fWJ2aU9sB4KZuMYg==",
773 "dev": true,
774 "license": "Apache-2.0",
775 "dependencies": {
776 "b4a": "^1.3.0"
777 }
778 },
779 "node_modules/convert-source-map": {
780 "version": "2.0.0",
781 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
782 "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
783 "dev": true,
784 "license": "MIT"
785 },
786 "node_modules/error-stack-parser": {
787 "version": "2.1.4",
788 "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
789 "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
790 "dev": true,
791 "license": "MIT",
792 "dependencies": {
793 "stackframe": "^1.3.4"
794 }
795 },
796 "node_modules/events-universal": {
797 "version": "1.0.1",
798 "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
799 "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
800 "dev": true,
801 "license": "Apache-2.0",
802 "dependencies": {
803 "bare-events": "^2.7.0"
804 }
805 },
806 "node_modules/fast-fifo": {
807 "version": "1.3.2",
808 "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
809 "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
810 "dev": true,
811 "license": "MIT"
812 },
813 "node_modules/globbie": {
814 "version": "1.0.3",
815 "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz",
816 "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==",
817 "dev": true,
818 "license": "Apache-2.0",
819 "dependencies": {
820 "bare-fs": "^4.1.2",
821 "bare-path": "^3.0.0",
822 "bare-process": "^4.2.1",
823 "picomatch": "^4.0.2"
824 }
825 },
826 "node_modules/hyper-overlay": {
827 "resolved": "../hyper-overlay",
828 "link": true
829 },
830 "node_modules/hypercore-crypto": {
831 "version": "3.6.1",
832 "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz",
833 "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==",
834 "dev": true,
835 "license": "MIT",
836 "dependencies": {
837 "b4a": "^1.6.6",
838 "compact-encoding": "^2.15.0",
839 "sodium-universal": "^5.0.0"
840 }
841 },
842 "node_modules/paparam": {
843 "version": "1.10.1",
844 "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.1.tgz",
845 "integrity": "sha512-viyQI64VIja0Za3njIzhoEP8ZVkgPowhZPuG0E96NwBfYJ6ZIyrrlhWGtFPkdN7eYLl2L8CTWL+wla0evm1KKQ==",
846 "dev": true,
847 "license": "Apache-2.0"
848 },
849 "node_modules/picomatch": {
850 "version": "4.0.4",
851 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
852 "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
853 "dev": true,
854 "license": "MIT",
855 "engines": {
856 "node": ">=12"
857 },
858 "funding": {
859 "url": "https://github.com/sponsors/jonschlinkert"
860 }
861 },
862 "node_modules/require-addon": {
863 "version": "1.2.0",
864 "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz",
865 "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==",
866 "dev": true,
867 "license": "Apache-2.0",
868 "dependencies": {
869 "bare-addon-resolve": "^1.3.0"
870 },
871 "engines": {
872 "bare": ">=1.10.0"
873 }
874 },
875 "node_modules/same-object": {
876 "version": "1.0.2",
877 "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz",
878 "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==",
879 "dev": true,
880 "license": "MIT"
881 },
882 "node_modules/sodium-native": {
883 "version": "5.1.0",
884 "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz",
885 "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==",
886 "dev": true,
887 "license": "MIT",
888 "dependencies": {
889 "bare-assert": "^1.2.0",
890 "require-addon": "^1.1.0",
891 "which-runtime": "^1.2.1"
892 },
893 "engines": {
894 "bare": ">=1.16.0"
895 }
896 },
897 "node_modules/sodium-universal": {
898 "version": "5.0.1",
899 "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz",
900 "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==",
901 "dev": true,
902 "license": "MIT",
903 "dependencies": {
904 "sodium-native": "^5.0.1"
905 },
906 "peerDependencies": {
907 "sodium-javascript": "~0.8.0"
908 },
909 "peerDependenciesMeta": {
910 "sodium-javascript": {
911 "optional": true
912 }
913 }
914 },
915 "node_modules/stackframe": {
916 "version": "1.3.4",
917 "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
918 "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
919 "dev": true,
920 "license": "MIT"
921 },
922 "node_modules/streamx": {
923 "version": "2.25.0",
924 "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz",
925 "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==",
926 "dev": true,
927 "license": "MIT",
928 "dependencies": {
929 "events-universal": "^1.0.0",
930 "fast-fifo": "^1.3.2",
931 "text-decoder": "^1.1.0"
932 }
933 },
934 "node_modules/teex": {
935 "version": "1.0.1",
936 "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
937 "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
938 "dev": true,
939 "license": "MIT",
940 "dependencies": {
941 "streamx": "^2.12.5"
942 }
943 },
944 "node_modules/test-tmp": {
945 "version": "1.4.0",
946 "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz",
947 "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==",
948 "dev": true,
949 "license": "MIT",
950 "dependencies": {
951 "bare-fs": "^4.0.1",
952 "bare-os": "^3.3.0",
953 "bare-path": "^3.0.0"
954 }
955 },
956 "node_modules/text-decoder": {
957 "version": "1.2.7",
958 "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
959 "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
960 "dev": true,
961 "license": "Apache-2.0",
962 "dependencies": {
963 "b4a": "^1.6.4"
964 }
965 },
966 "node_modules/tmatch": {
967 "version": "5.0.0",
968 "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz",
969 "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==",
970 "dev": true,
971 "license": "ISC",
972 "engines": {
973 "node": ">=8"
974 }
975 },
976 "node_modules/v8-to-istanbul": {
977 "version": "9.3.0",
978 "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
979 "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
980 "dev": true,
981 "license": "ISC",
982 "dependencies": {
983 "@jridgewell/trace-mapping": "^0.3.12",
984 "@types/istanbul-lib-coverage": "^2.0.1",
985 "convert-source-map": "^2.0.0"
986 },
987 "engines": {
988 "node": ">=10.12.0"
989 }
990 },
991 "node_modules/which-runtime": {
992 "version": "1.3.2",
993 "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz",
994 "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==",
995 "dev": true,
996 "license": "Apache-2.0"
997 }
998 }
999}
package.json +21 -0
……@@ -0,0 +1,21 @@
1{
2 "name": "pgh-runtime",
3 "version": "0.1.0",
4 "description": "Per-app OTA updater for pgh-installed apps — polls a release source, materializes new trees into the pgh store",
5 "type": "module",
6 "main": "index.js",
7 "exports": {
8 ".": "./index.js"
9 },
10 "scripts": {
11 "test": "brittle test/*.test.js"
12 },
13 "license": "AGPL-3.0",
14 "dependencies": {
15 "hyper-overlay": "file:../hyper-overlay"
16 },
17 "devDependencies": {
18 "brittle": "^3.0.0",
19 "hypercore-crypto": "^3.0.0"
20 }
21}
test/runtime.test.js +261 -0
……@@ -0,0 +1,261 @@
1/**
2 * PghRuntime tests — standalone
3 *
4 * Uses a minimal in-memory store mock + in-memory fetcher so this test
5 * has no dependency on the pgh CLI package. Any store implementing
6 * { init, hasTree, materialize } works with pgh-runtime.
7 */
8
9import test from 'brittle'
10import crypto from 'hypercore-crypto'
11
12import { PghRuntime, installTree } from '../index.js'
13import { KIND_FILE, computeTreeHash } from 'hyper-overlay/tree'
14
15function hashBytes (b) { return crypto.data(b).toString('hex') }
16
17/**
18 * In-memory store that implements the interface pgh-runtime expects.
19 * materialize just records (tree hash → entries); no real filesystem.
20 */
21function makeMemoryStore () {
22 const trees = new Map()
23 return {
24 init () {},
25 hasTree (hash) { return trees.has(hash) },
26 async materialize (hash, entries) {
27 trees.set(hash, { entries, pulledAt: Date.now() })
28 },
29 _trees: trees
30 }
31}
32
33function makeFetcher () {
34 const trees = {}
35 const contents = {}
36 let latest = null
37
38 function build (entries) {
39 const outEntries = entries.map(e => {
40 if (e.content) {
41 const buf = Buffer.from(e.content)
42 const h = hashBytes(buf)
43 contents[h] = buf
44 return { kind: KIND_FILE, exec: 0, name: e.name, childHash: h, size: buf.length }
45 }
46 return e
47 })
48 const h = computeTreeHash(outEntries)
49 trees[h] = { entries: outEntries, size: outEntries.reduce((a, c) => a + (c.size || 0), 0) }
50 return h
51 }
52
53 return {
54 build,
55 async getTree (h) {
56 if (!trees[h]) throw new Error('no tree: ' + h.slice(0, 12))
57 return trees[h]
58 },
59 async getContent (h) {
60 if (!contents[h]) throw new Error('no content: ' + h.slice(0, 12))
61 return contents[h]
62 },
63 async getLatestRelease () { return latest },
64 setLatest (entry) { latest = entry }
65 }
66}
67
68// ── Lifecycle ────────────────────────────────────────────────
69
70test('start + stop lifecycle', async (t) => {
71 const runtime = new PghRuntime({
72 name: 'test',
73 fetcher: makeFetcher(),
74 store: makeMemoryStore(),
75 interval: 0,
76 startImmediately: false
77 })
78 await runtime.start()
79 await runtime.stop()
80 t.pass('clean')
81})
82
83test('constructor rejects missing required opts', (t) => {
84 t.exception(() => new PghRuntime({}))
85 t.exception(() => new PghRuntime({ name: 'x' }))
86 t.exception(() => new PghRuntime({ name: 'x', fetcher: {} }))
87})
88
89// ── No-update scenarios ─────────────────────────────────────
90
91test('no release → no updated event', async (t) => {
92 let updated = false
93 const runtime = new PghRuntime({
94 name: 'test', fetcher: makeFetcher(), store: makeMemoryStore(), interval: 0
95 })
96 runtime.on('updated', () => { updated = true })
97 await runtime.start()
98 t.absent(updated)
99 await runtime.stop()
100})
101
102test('same tree hash → no update', async (t) => {
103 const fetcher = makeFetcher()
104 const h = fetcher.build([{ name: 'a', content: 'x' }])
105 fetcher.setLatest({ name: 'test', version: '1.0.0', treeHash: h })
106
107 let updated = false
108 const runtime = new PghRuntime({
109 name: 'test', fetcher, store: makeMemoryStore(), interval: 0, currentTreeHash: h
110 })
111 runtime.on('updated', () => { updated = true })
112 await runtime.start()
113 t.absent(updated)
114 await runtime.stop()
115})
116
117// ── Happy path ───────────────────────────────────────────────
118
119test('new release → updating + updated events + stored tree', async (t) => {
120 const fetcher = makeFetcher()
121 const h = fetcher.build([{ name: 'index.js', content: 'ok' }])
122 fetcher.setLatest({ name: 'test', version: '1.0.0', treeHash: h })
123
124 const store = makeMemoryStore()
125 const events = []
126 const runtime = new PghRuntime({
127 name: 'test', fetcher, store, interval: 0, currentTreeHash: 'x'.repeat(64)
128 })
129 runtime.on('updating', (r) => events.push({ type: 'updating', version: r.version }))
130 runtime.on('updated', (i) => events.push({ type: 'updated', hash: i.newTreeHash }))
131
132 await runtime.start()
133
134 t.is(events.length, 2)
135 t.is(events[0].type, 'updating')
136 t.is(events[1].type, 'updated')
137 t.is(events[1].hash, h)
138 t.ok(store.hasTree(h))
139 t.is(runtime.updated, true)
140
141 await runtime.stop()
142})
143
144test('readyToApply descriptor after update', async (t) => {
145 const fetcher = makeFetcher()
146 const h = fetcher.build([{ name: 'a', content: 'a' }])
147 fetcher.setLatest({ name: 'test', version: '2.0.0', treeHash: h })
148
149 const previous = 'p'.repeat(64)
150 const runtime = new PghRuntime({
151 name: 'test', fetcher, store: makeMemoryStore(), interval: 0, currentTreeHash: previous
152 })
153 t.is(runtime.readyToApply(), null)
154 await runtime.start()
155
156 const desc = runtime.readyToApply()
157 t.ok(desc)
158 t.is(desc.name, 'test')
159 t.is(desc.previousTreeHash, previous)
160 t.is(desc.newTreeHash, h)
161 t.is(desc.release.version, '2.0.0')
162 await runtime.stop()
163})
164
165// ── Errors ──────────────────────────────────────────────────
166
167test('fetcher throws → error event', async (t) => {
168 const fetcher = {
169 async getLatestRelease () { throw new Error('nope') },
170 async getTree () {},
171 async getContent () {}
172 }
173 let err = null
174 const runtime = new PghRuntime({
175 name: 'test', fetcher, store: makeMemoryStore(), interval: 0
176 })
177 runtime.on('error', (e) => { err = e })
178 await runtime.start()
179 t.ok(err)
180 t.ok(err.message.includes('nope'))
181 await runtime.stop()
182})
183
184test('installTree failure mid-update → error event, not "updated"', async (t) => {
185 const fetcher = makeFetcher()
186 fetcher.setLatest({ name: 'test', version: '1.0.0', treeHash: 'z'.repeat(64) })
187
188 let err = null, updated = false
189 const runtime = new PghRuntime({
190 name: 'test', fetcher, store: makeMemoryStore(), interval: 0, currentTreeHash: 'x'.repeat(64)
191 })
192 runtime.on('error', (e) => { err = e })
193 runtime.on('updated', () => { updated = true })
194 await runtime.start()
195 t.ok(err)
196 t.absent(updated)
197 t.is(runtime.updating, false)
198 await runtime.stop()
199})
200
201// ── Polling ─────────────────────────────────────────────────
202
203test('polling picks up a new release between checks', async (t) => {
204 const fetcher = makeFetcher()
205 let updatedHash = null
206 const runtime = new PghRuntime({
207 name: 'test', fetcher, store: makeMemoryStore(),
208 interval: 30, startImmediately: true
209 })
210 runtime.on('updated', (i) => { updatedHash = i.newTreeHash })
211
212 await runtime.start()
213 t.is(updatedHash, null)
214
215 const h = fetcher.build([{ name: 'q', content: 'q' }])
216 fetcher.setLatest({ name: 'test', version: '1.0.0', treeHash: h })
217 await new Promise(r => setTimeout(r, 100))
218
219 t.is(updatedHash, h)
220 await runtime.stop()
221})
222
223// ── installTree (direct) ────────────────────────────────────
224
225test('installTree — direct call walks a nested tree', async (t) => {
226 const fetcher = makeFetcher()
227 // leaf subtree
228 const leafEntries = [{ kind: KIND_FILE, exec: 0, name: 'util.js', childHash: hashBytes(Buffer.from('u')), size: 1 }]
229 const leafHash = computeTreeHash(leafEntries)
230 fetcher.build([{ name: 'util.js', content: 'u' }]) // registers content for util.js
231
232 // Replace fetcher.getTree to serve our leaf + root
233 const rootEntries = [{ kind: 1, exec: 0, name: 'lib', childHash: leafHash, size: 1 }]
234 const rootHash = computeTreeHash(rootEntries)
235
236 const trees = { [leafHash]: { entries: leafEntries, size: 1 }, [rootHash]: { entries: rootEntries, size: 1 } }
237 const customFetcher = {
238 async getTree (h) { if (!trees[h]) throw new Error('no tree'); return trees[h] },
239 async getContent (h) { return fetcher.getContent(h) }
240 }
241
242 const store = makeMemoryStore()
243 const result = await installTree(store, rootHash, customFetcher)
244 t.is(result.materialized, 2, 'root + leaf')
245 t.ok(store.hasTree(rootHash))
246 t.ok(store.hasTree(leafHash))
247})
248
249test('installTree — skips already-present trees', async (t) => {
250 const fetcher = makeFetcher()
251 const h = fetcher.build([{ name: 'a', content: 'a' }])
252 const store = makeMemoryStore()
253
254 const first = await installTree(store, h, fetcher)
255 t.is(first.materialized, 1)
256 t.is(first.skipped, 0)
257
258 const second = await installTree(store, h, fetcher)
259 t.is(second.materialized, 0)
260 t.is(second.skipped, 1)
261})