wao 0.40.2 → 0.41.1

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 (75) hide show
  1. package/cjs/accounts-web.js +25 -0
  2. package/cjs/accounts.js +38 -0
  3. package/cjs/adaptor-base.js +504 -287
  4. package/cjs/adaptor-cf.js +42 -0
  5. package/cjs/ao-loader.js +1 -1
  6. package/cjs/ao.js +18 -6
  7. package/cjs/aoconnect-base.js +1017 -546
  8. package/cjs/aoconnect-cf.js +24 -0
  9. package/cjs/ar-remote.js +277 -0
  10. package/cjs/armem-base.js +822 -211
  11. package/cjs/armem-cf.js +128 -0
  12. package/cjs/armem.js +11 -5
  13. package/cjs/bar.js +511 -173
  14. package/cjs/car.js +37 -0
  15. package/cjs/cf-env.js +54 -0
  16. package/cjs/cf.js +76 -0
  17. package/cjs/cli.js +12 -6
  18. package/cjs/create.js +1 -1
  19. package/cjs/devs.js +53 -0
  20. package/cjs/dodb.js +116 -0
  21. package/cjs/hb.js +136 -53
  22. package/cjs/hyperbeam.js +85 -44
  23. package/cjs/keygen.js +94 -0
  24. package/cjs/run.js +4 -1
  25. package/cjs/server.js +40 -9
  26. package/cjs/storage-multi.js +525 -0
  27. package/cjs/tgql-d1.js +664 -0
  28. package/cjs/tgql.js +293 -172
  29. package/cjs/workspace/.claude/agents/tester.md +2 -2
  30. package/cjs/workspace/.claude/skills/build/SKILL.md +2 -2
  31. package/cjs/workspace/.claude/skills/test/SKILL.md +3 -2
  32. package/cjs/workspace/CLAUDE.md +2 -2
  33. package/cjs/workspace/README.md +1 -1
  34. package/cjs/workspace/docs/debug.md +9 -4
  35. package/cjs/workspace/docs/hyperbeam-devices.md +50 -1
  36. package/cjs/workspace/package.json +3 -3
  37. package/esm/accounts-web.js +14 -0
  38. package/esm/accounts.js +27 -0
  39. package/esm/adaptor-base.js +129 -31
  40. package/esm/adaptor-cf.js +11 -0
  41. package/esm/ao-loader.js +1 -1
  42. package/esm/ao.js +21 -2
  43. package/esm/aoconnect-base.js +255 -7
  44. package/esm/aoconnect-cf.js +9 -0
  45. package/esm/ar-remote.js +87 -0
  46. package/esm/armem-base.js +304 -53
  47. package/esm/armem-cf.js +67 -0
  48. package/esm/armem.js +7 -2
  49. package/esm/bar.js +126 -16
  50. package/esm/car.js +10 -0
  51. package/esm/cf-env.js +29 -0
  52. package/esm/cf.js +11 -0
  53. package/esm/cli.js +6 -2
  54. package/esm/create.js +1 -1
  55. package/esm/devs.js +15 -0
  56. package/esm/dodb.js +26 -0
  57. package/esm/hb.js +93 -16
  58. package/esm/hyperbeam.js +68 -30
  59. package/esm/keygen.js +47 -0
  60. package/esm/run.js +4 -1
  61. package/esm/server.js +29 -9
  62. package/esm/storage-multi.js +183 -0
  63. package/esm/tgql-d1.js +407 -0
  64. package/esm/tgql.js +29 -10
  65. package/esm/workspace/.claude/agents/tester.md +2 -2
  66. package/esm/workspace/.claude/skills/build/SKILL.md +2 -2
  67. package/esm/workspace/.claude/skills/test/SKILL.md +3 -2
  68. package/esm/workspace/CLAUDE.md +2 -2
  69. package/esm/workspace/README.md +1 -1
  70. package/esm/workspace/docs/debug.md +9 -4
  71. package/esm/workspace/docs/hyperbeam-devices.md +50 -1
  72. package/esm/workspace/package.json +3 -3
  73. package/package.json +10 -3
  74. package/postinstall.cjs +84 -0
  75. package/wao-0.41.1.tgz +0 -0
package/esm/hyperbeam.js CHANGED
@@ -36,6 +36,7 @@ export default class HyperBEAM {
36
36
  genesis_wasm = false,
37
37
  arweave_gateway,
38
38
  force_signed = false,
39
+ linkify_mode, // v0.9-FINAL: HB linkify mode. undefined => HB default; pass "false" for hbsig-style inline-only responses
39
40
  rebar3, // Use rebar3 shell (true) or direct erl (false). Defaults to HB_REBAR3 env or true
40
41
  } = {}) {
41
42
  // Determine rebar3 mode: option > env var > default (true)
@@ -49,6 +50,7 @@ export default class HyperBEAM {
49
50
  }
50
51
  this.genesis_wasm = genesis_wasm
51
52
  this.force_signed = force_signed
53
+ this.linkify_mode = linkify_mode
52
54
  this.cu_port = cu_port
53
55
  this.arweave_gateway = arweave_gateway || process.env.ARWEAVE_GATEWAY
54
56
  this.devices = devices
@@ -84,7 +86,7 @@ export default class HyperBEAM {
84
86
  this.c = c
85
87
  this.cmake = cmake
86
88
  this.port = port
87
- this.url = `http://localhost:${this.port}`
89
+ this.url = `http://127.0.0.1:${this.port}`
88
90
  if (bundler) this.bundler = `http://localhost::${bundler}`
89
91
  this.bundler_ans104 = bundler_ans104
90
92
  if (bundler_httpsig) this.bundler = bundler_httpsig
@@ -102,6 +104,16 @@ export default class HyperBEAM {
102
104
  if (shell) this.shell()
103
105
  }
104
106
  shell() {
107
+ // Kill any stale beam.smp / process listening on our HB port before
108
+ // spawning a new shell. Without this, sequential test runs can hit a
109
+ // lingering Erlang VM from the previous test (kill() returned but the
110
+ // OS hadn't released the port yet) and end up either failing to bind
111
+ // 10001 or talking to the stale node with stale process registry —
112
+ // surface symptom is 400 "No scheduler information provided." on the
113
+ // first schedule of an otherwise-known process.
114
+ try {
115
+ spawnSync("bash", ["-c", `lsof -ti:${this.port} | xargs -r kill -9 2>/dev/null`], { stdio: "ignore" })
116
+ } catch (_e) {}
105
117
  const evalCmd = this.genEval({ gateway: this.gateway, wallet: this.wallet })
106
118
  const cwd = resolve(process.cwd(), this.cwd)
107
119
  const env = this.genEnv() // genEnv() returns filtered process.env without proxy vars
@@ -257,6 +269,18 @@ export default class HyperBEAM {
257
269
  // Ensure DB directory exists
258
270
  spawnSync("mkdir", ["-p", dbDir])
259
271
 
272
+ // Kill any stale CU process listening on cu_port before spawning a new
273
+ // one. Sequential test runs in the same OS share port 6363; if a prior
274
+ // run's CU lingered (e.g. detached but its parent died before SIGKILL
275
+ // could propagate), the new CU's bind silently fails and HB ends up
276
+ // talking to the stale CU, which has a different process registry and
277
+ // throws confusing 500/400s on the next spawn/schedule.
278
+ try {
279
+ spawnSync("bash", ["-c", `lsof -ti:${this.cu_port} | xargs -r kill -9 2>/dev/null`], { stdio: "ignore" })
280
+ } catch (_e) {}
281
+ // Brief settle so the OS can release the port.
282
+ await new Promise(r => setTimeout(r, 200))
283
+
260
284
  // Use arweave_gateway option or ARWEAVE_GATEWAY env var for proxy environments
261
285
  const gatewayUrl = this.arweave_gateway || process.env.GATEWAY_URL || "https://arweave.net"
262
286
  const graphqlUrl = process.env.GRAPHQL_URL || `${gatewayUrl}/graphql`
@@ -285,7 +309,11 @@ export default class HyperBEAM {
285
309
  CHECKPOINT_GRAPHQL_URL: graphqlUrl,
286
310
  }
287
311
 
288
- this.cuProc = spawn("node", ["--experimental-wasm-memory64", "-r", "dotenv/config", "src/app.js"], {
312
+ // Node 26 enables wasm-memory64 by default and rejects the experimental
313
+ // flag; older Node versions still need it. Detect from process.versions.
314
+ const nodeMajor = parseInt((process.versions.node || "0").split(".")[0], 10)
315
+ const memory64Flag = nodeMajor >= 24 ? [] : ["--experimental-wasm-memory64"]
316
+ this.cuProc = spawn("node", [...memory64Flag, "-r", "dotenv/config", "src/app.js"], {
289
317
  cwd: cuDir,
290
318
  env,
291
319
  detached: true,
@@ -341,6 +369,12 @@ export default class HyperBEAM {
341
369
  const asyncThreads = "+A 4"
342
370
  if (!_env.ERL_ZFLAGS) _env.ERL_ZFLAGS = asyncThreads
343
371
  else if (!_env.ERL_ZFLAGS.includes("+A")) _env.ERL_ZFLAGS += ` ${asyncThreads}`
372
+ // Skip the hb application's default-port HTTP server start (8734). We call
373
+ // hb_http_server:start_node/1 explicitly with the actual port via genEval,
374
+ // so the default binding is redundant; skipping it avoids eaddrinuse and
375
+ // the downstream case_clause crashes when multiple HyperBEAM instances
376
+ // run side-by-side (p4.test.js, p4-lua.test.js).
377
+ _env.WAO_NO_DEFAULT_HTTP_SERVER = "1"
344
378
  return _env
345
379
  }
346
380
 
@@ -360,14 +394,14 @@ export default class HyperBEAM {
360
394
  }
361
395
  }
362
396
  if (_devs.length > 0) {
363
- _devices = `, preloaded_devices => [${_devs.join(", ")}]`
397
+ _devices = `, <<"preloaded-devices">> => [${_devs.join(", ")}]`
364
398
  }
365
- const _wallet = `, priv_key_location => <<"${wallet}">>`
399
+ const _wallet = `, <<"priv-key-location">> => <<"${wallet}">>`
366
400
  // Use arweave_gateway (Cloudflare proxy) if set, otherwise local gateway port, otherwise default
367
401
  const _gateway = this.arweave_gateway
368
- ? `, gateway => <<"${this.arweave_gateway}">>`
402
+ ? `, <<"gateway">> => <<"${this.arweave_gateway}">>`
369
403
  : gateway
370
- ? `, gateway => <<"http://localhost:${gateway}">>`
404
+ ? `, <<"gateway">> => <<"http://localhost:${gateway}">>`
371
405
  : ""
372
406
 
373
407
  // Store config: use single hb_store_fs matching HyperBEAM eunit test pattern.
@@ -375,35 +409,35 @@ export default class HyperBEAM {
375
409
  // because list_numbered/resolve interactions across stores break symlink following.
376
410
  // The wao@1.0 device handles Arweave TX resolution independently via HTTP.
377
411
  const _store = this.store_prefix
378
- ? `, store => #{ <<"store-module">> => hb_store_fs, <<"name">> => <<"${this.store_prefix}">> }`
412
+ ? `, <<"store">> => #{ <<"store-module">> => hb_store_fs, <<"name">> => <<"${this.store_prefix}">> }`
379
413
  : ""
380
414
  let _bundler = this.bundler
381
- ? `, bundler_httpsig => <<"${this.bundler}">>`
415
+ ? `, <<"bundler-httpsig">> => <<"${this.bundler}">>`
382
416
  : ""
383
417
  // Only include bundler_ans104 if it's a truthy value (port number or URL)
384
418
  // When false or omitted, don't include it - Erlang code expects either no option or a valid URL
385
419
  let _bundler_ans104 = this.bundler_ans104 && this.bundler_ans104 !== false
386
- ? `, bundler_ans104 => <<"http://localhost:${this.bundler_ans104}">>`
420
+ ? `, <<"bundler-ans104">> => <<"http://localhost:${this.bundler_ans104}">>`
387
421
  : ""
388
422
  /*
389
423
  const _routes = `, routes => [#{ <<"template">> => <<"/result/.*">>, <<"node">> => #{ <<"prefix">> => <<"http://localhost:${this.cu}">> } }, #{ <<\"template\">> => <<\"/dry-run\">>, <<\"node\">> => #{ <<\"prefix\">> => <<\"http://localhost:${this.cu}\">> } }, #{ <<"template">> => <<"/graphql">>, <<"nodes">> => [#{ <<"prefix">> => <<"http://localhost:${gateway}">>, <<"opts">> => #{ http_client => httpc, protocol => http2 } }, #{ <<"prefix">> => <<"http://localhost:${gateway}">>, <<"opts">> => #{ http_client => gun, protocol => http2 } }] }, #{ <<"template">> => <<"/raw">>, <<"node">> => #{ <<"prefix">> => <<"http://localhost:${gateway}">>, <<"opts">> => #{ http_client => gun, protocol => http2 } } }]`
390
424
  */
391
425
  const _p4_non_chargable = this.p4_non_chargable
392
- ? `, p4_non_chargable_routes => [${this.p4_non_chargable_routes
426
+ ? `, <<"p4-non-chargable-routes">> => [${this.p4_non_chargable_routes
393
427
  .map(() => `#{ <<"template">> => <<"/*~node-process@1.0/*">> }`)
394
428
  .join(", ")}]`
395
429
  : this.p4_lua
396
- ? `, p4_non_chargable_routes => [#{ <<"template">> => <<"/*~node-process@1.0/*">> }, #{ <<"template">> => <<"/~wao@1.0/*">> }, #{ <<"template">> => <<"/~p4@1.0/balance">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }]`
430
+ ? `, <<"p4-non-chargable-routes">> => [#{ <<"template">> => <<"/*~node-process@1.0/*">> }, #{ <<"template">> => <<"/~wao@1.0/*">> }, #{ <<"template">> => <<"/~p4@1.0/balance">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }]`
397
431
  : !this.simple_pay
398
432
  ? ""
399
- : `, p4_non_chargable_routes => [#{ <<"template">> => <<"/~simple-pay@1.0/topup">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }, #{ <<"template">> => <<"/~simple-pay@1.0/balance">> }]`
433
+ : `, <<"p4-non-chargable-routes">> => [#{ <<"template">> => <<"/~simple-pay@1.0/topup">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }, #{ <<"template">> => <<"/~simple-pay@1.0/balance">> }]`
400
434
 
401
435
  const _operator = this.operator
402
- ? `, operator => <<"${this.operator}">>`
436
+ ? `, <<"operator">> => <<"${this.operator}">>`
403
437
  : ""
404
- const _spp = this.spp ? `, simple_pay_price => ${this.spp}` : ""
405
- const _genesis_wasm_port = this.genesis_wasm ? `, genesis_wasm_port => ${this.cu_port}` : ""
406
- const _force_signed = this.force_signed ? `, force_signed_requests => true, force_signed => true` : ""
438
+ const _spp = this.spp ? `, <<"simple-pay-price">> => ${this.spp}` : ""
439
+ const _genesis_wasm_port = this.genesis_wasm ? `, <<"genesis-wasm-port">> => ${this.cu_port}` : ""
440
+ const _force_signed = this.force_signed ? `, <<"force-signed-requests">> => true, <<"force-signed">> => true` : ""
407
441
 
408
442
  // Helper to format module(s) for Erlang - supports ID string, inline object, or array
409
443
  const formatModule = (mod) => {
@@ -427,30 +461,30 @@ export default class HyperBEAM {
427
461
  }
428
462
 
429
463
  const _node_processes = this.p4_lua
430
- ? `, node_processes => #{ <<"ledger">> => #{ <<"device">> => <<"process@1.0">>, <<"execution-device">> => <<"lua@5.3a">>, <<"scheduler-device">> => <<"scheduler@1.0">>, <<"module">> => ${formatModule(this.p4_lua.processor)}, <<"operator">> => <<"${this.operator}">>${this.p4_lua.admin ? `, <<"admin">> => <<"${this.p4_lua.admin}">>` : ""}${this.p4_lua.balance ? `, <<"balance">> => #{ ${Object.entries(this.p4_lua.balance).map(([k, v]) => `<<"${k}">> => ${v}`).join(", ")} }` : ""} } }`
464
+ ? `, <<"node-processes">> => #{ <<"ledger">> => #{ <<"device">> => <<"process@1.0">>, <<"execution-device">> => <<"lua@5.3a">>, <<"scheduler-device">> => <<"scheduler@1.0">>, <<"module">> => ${formatModule(this.p4_lua.processor)}, <<"operator">> => <<"${this.operator}">>${this.p4_lua.admin ? `, <<"admin">> => <<"${this.p4_lua.admin}">>` : ""}${this.p4_lua.balance ? `, <<"balance">> => #{ ${Object.entries(this.p4_lua.balance).map(([k, v]) => `<<"${k}">> => ${v}`).join(", ")} }` : ""} } }`
431
465
  : ""
432
466
  const processor = this.p4_lua
433
467
  ? `#{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"lua@5.3a">>, <<"module">> => ${formatModule(this.p4_lua.client)}, <<"ledger-path">> => <<"/ledger~node-process@1.0">> }`
434
468
  : ""
435
- const _port = `port => ${this.port}`
469
+ const _port = `<<"port">> => ${this.port}`
436
470
  const _faff = isNil(this.faff)
437
471
  ? ""
438
- : `, faff_allow_list => [ ${map(addr => `<<"${addr}">>`)(this.faff).join(", ")} ]`
472
+ : `, <<"faff-allow-list">> => [ ${map(addr => `<<"${addr}">>`)(this.faff).join(", ")} ]`
439
473
 
440
474
  const _on = this.p4_lua
441
- ? `, on => #{ <<"request">> => ${processor}, <<"response">> => ${processor} }`
475
+ ? `, <<"on">> => #{ <<"request">> => ${processor}, <<"response">> => ${processor} }`
442
476
  : this.simple_pay
443
- ? `, on => #{ <<"request">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"simple-pay@1.0">> }, <<"response">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"simple-pay@1.0">> } }`
477
+ ? `, <<"on">> => #{ <<"request">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"simple-pay@1.0">> }, <<"response">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"simple-pay@1.0">> } }`
444
478
  : !isNil(this.faff)
445
- ? `, on => #{ <<"request">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"faff@1.0">>, <<"ledger-device">> => <<"faff@1.0">> }, <<"response">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"faff@1.0">>, <<"ledger-device">> => <<"faff@1.0">> } }`
479
+ ? `, <<"on">> => #{ <<"request">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"faff@1.0">>, <<"ledger-device">> => <<"faff@1.0">> }, <<"response">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"faff@1.0">>, <<"ledger-device">> => <<"faff@1.0">> } }`
446
480
  : ""
447
481
  // Add cache_writers to allow the wallet to write to cache (needed for WASM module uploads)
448
482
  // Use the wallet address (this.addr) which is always available from the wallet file
449
- const _cache_writers = `, cache_writers => [<<"${this.addr}">>]`
483
+ const _cache_writers = `, <<"cache-writers">> => [<<"${this.addr}">>]`
450
484
 
451
485
  // Use gun HTTP client for relay calls instead of httpc
452
486
  // gun doesn't use system proxy settings, avoiding the proxy issue with localhost CU
453
- const _relay_http_client = `, relay_http_client => gun, http_client => gun`
487
+ const _relay_http_client = `, <<"relay-http-client">> => gun, <<"http-client">> => gun`
454
488
 
455
489
  // Custom routes using Cloudflare proxy instead of arweave.net
456
490
  // Also add CU routes for genesis_wasm when enabled
@@ -460,7 +494,7 @@ export default class HyperBEAM {
460
494
  #{ <<"template">> => <<"/dry-run">>, <<"node">> => #{ <<"prefix">> => <<"http://localhost:${this.cu_port}">> } },`
461
495
  : ""
462
496
  const _routes = this.arweave_gateway || this.genesis_wasm
463
- ? `, routes => [
497
+ ? `, <<"routes">> => [
464
498
  ${cuRoutes}
465
499
  #{ <<"template">> => <<"/graphql">>, <<"nodes">> => [
466
500
  #{ <<"prefix">> => <<"${this.arweave_gateway || 'https://arweave.net'}">>, <<"opts">> => #{ http_client => gun, protocol => http2 } }
@@ -487,7 +521,7 @@ export default class HyperBEAM {
487
521
 
488
522
  // Pre-register device name atoms so hb_util:atom/1 (which uses list_to_existing_atom)
489
523
  // doesn't crash with badarg when resolving device names from HTTP headers/binaries
490
- const preRegisterAtoms = `lists:foreach(fun list_to_atom/1, ["wao@1.0", "hbsig@1.0", "stack@1.0", "patch@1.0", "inc@1.0", "double@1.0", "add@1.0", "mul@1.0", "inc2@1.0", "square@1.0", "mydev@1.0", "lua@5.3a", "process@1.0", "scheduler@1.0", "message@1.0", "meta@1.0", "cache@1.0", "json@1.0", "structured@1.0", "httpsig@1.0", "flat@1.0", "genesis-wasm@1.0", "compute@1.0", "delegated-compute@1.0", "relay@1.0", "router@1.0", "cron@1.0", "node-process@1.0", "p4@1.0", "simple-pay@1.0", "faff@1.0", "ans104@1.0", "test-device@1.0", "lookup@1.0", "local-name@1.0", "upload@1.0", "hook@1.0", "auth-hook@1.0", "http-auth@1.0", "greenzone@1.0", "apply@1.0", "dedup@1.0", "cookie@1.0", "push@1.0", "query@1.0", "manifest@1.0", "name@1.0", "profile@1.0", "monitor@1.0", "multipass@1.0", "poda@1.0", "snp@1.0", "trie@1.0", "volume@1.0", "secret@1.0", "wasi@1.0", "wasm-64@1.0", "whois@1.0", "cacheviz@1.0", "hyperbuddy@1.0", "copycat@1.0", "json-iface@1.0", "arweave@2.9-pre"]), `
524
+ const preRegisterAtoms = `lists:foreach(fun list_to_atom/1, ["wao@1.0", "hbsig@1.0", "stack@1.0", "patch@1.0", "inc@1.0", "double@1.0", "add@1.0", "mul@1.0", "inc2@1.0", "square@1.0", "mydev@1.0", "lua@5.3a", "process@1.0", "scheduler@1.0", "message@1.0", "meta@1.0", "cache@1.0", "json@1.0", "structured@1.0", "httpsig@1.0", "flat@1.0", "genesis-wasm@1.0", "compute@1.0", "delegated-compute@1.0", "relay@1.0", "router@1.0", "cron@1.0", "node-process@1.0", "p4@1.0", "simple-pay@1.0", "faff@1.0", "ans104@1.0", "test-device@1.0", "lookup@1.0", "local-name@1.0", "upload@1.0", "hook@1.0", "auth-hook@1.0", "http-auth@1.0", "greenzone@1.0", "apply@1.0", "dedup@1.0", "cookie@1.0", "push@1.0", "query@1.0", "manifest@1.0", "name@1.0", "profile@1.0", "monitor@1.0", "multipass@1.0", "poda@1.0", "snp@1.0", "trie@1.0", "volume@1.0", "secret@1.0", "wasi@1.0", "wasm-64@1.0", "whois@1.0", "cacheviz@1.0", "hyperbuddy@1.0", "copycat@1.0", "json-iface@1.0", "arweave@2.9", "b32-name@1.0", "blacklist@1.0", "bundler@1.0", "gzip@1.0", "location@1.0", "metering@1.0", "rate-limit@1.0", "tx@1.0"]), `
491
525
 
492
526
  // Pre-create prometheus ETS tables owned by the shell process.
493
527
  // dev_hbsig on_load also does this, but the module loads lazily so this
@@ -506,13 +540,13 @@ export default class HyperBEAM {
506
540
  // This affects both node_processes definitions (which include 'authority' via
507
541
  // augment_definition) and push device re-scheduling (which signs outbox messages
508
542
  // with httpsig). Disable verification until upstream fixes the codec.
509
- const _verify_assignments = (this.p4_lua || this.genesis_wasm) ? `, verify_assignments => false` : ""
543
+ const _verify_assignments = (this.p4_lua || this.genesis_wasm) ? `, <<"verify-assignments">> => false` : ""
510
544
 
511
545
  // Use hb_http_server:start_node directly instead of hb:start_mainnet.
512
546
  // start_mainnet always overwrites the store config with a single hb_store_fs,
513
547
  // which prevents hb_store_gateway from resolving Arweave TX IDs.
514
548
  // start_node preserves user-provided store via set_default_opts.
515
- const _priv_wallet = `, priv_wallet => hb:wallet(<<"${wallet}">>)`
549
+ const _priv_wallet = `, <<"priv-wallet">> => hb:wallet(<<"${wallet}">>)`
516
550
  // cache_control => <<"always">> ensures compute results/snapshots are cached.
517
551
  // process_snapshot_slots => 1 takes a snapshot every slot (not just every 60s).
518
552
  // process_async_cache => false writes snapshots synchronously before returning
@@ -521,9 +555,13 @@ export default class HyperBEAM {
521
555
  // Without these, hb_cache:write strips uncommitted keys (like device-stack)
522
556
  // from the cached process state, and subsequent computes fail with
523
557
  // {error, no_valid_device_stack} when loading from the corrupted cache.
524
- const _cache_control = `, cache_control => <<"always">>, process_snapshot_slots => 1, process_async_cache => false`
558
+ const _cache_control = `, <<"cache-control">> => <<"always">>, <<"process-snapshot-slots">> => 1, <<"process-async-cache">> => false`
525
559
 
526
- const start = `${clearProxy}${initPrometheus}${preRegisterAtoms}${loadHbsig}${ensureInit}hb_http_server:start_node(#{ ${_port}${_gateway}${_priv_wallet}${_faff}${_bundler}${_bundler_ans104}${_on}${_p4_non_chargable}${_operator}${_spp}${_genesis_wasm_port}${_force_signed}${_devices}${_node_processes}${_cache_writers}${_relay_http_client}${_routes}${_store}${_verify_assignments}${_cache_control}, prometheus => false, linkify_mode => false}).`
560
+ const _linkify =
561
+ this.linkify_mode === undefined
562
+ ? ""
563
+ : `, <<"linkify-mode">> => ${this.linkify_mode === false ? "false" : (this.linkify_mode === true ? "true" : this.linkify_mode)}`
564
+ const start = `${clearProxy}${initPrometheus}${preRegisterAtoms}${loadHbsig}${ensureInit}hb_http_server:start_node(#{ ${_port}${_gateway}${_priv_wallet}${_faff}${_bundler}${_bundler_ans104}${_on}${_p4_non_chargable}${_operator}${_spp}${_genesis_wasm_port}${_force_signed}${_devices}${_node_processes}${_cache_writers}${_relay_http_client}${_routes}${_store}${_verify_assignments}${_cache_control}, <<"prometheus">> => false${_linkify}}).`
527
565
 
528
566
  return start
529
567
  }
package/esm/keygen.js ADDED
@@ -0,0 +1,47 @@
1
+ import _Arweave from "arweave"
2
+ const Arweave = _Arweave.default ?? _Arweave
3
+ import { toAddr } from "./utils.js"
4
+ import { mkdirSync, writeFileSync, existsSync, readdirSync } from "fs"
5
+ import { fileURLToPath } from "url"
6
+ import { dirname, resolve } from "path"
7
+
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = dirname(__filename)
10
+ const walletsDir = resolve(__dirname, "../devnet/.wallets")
11
+ const names = ["su", "cu", "mu", "acc0", "acc1", "acc2"]
12
+
13
+ async function main() {
14
+ const force = process.argv.includes("--force")
15
+
16
+ if (!force && existsSync(walletsDir)) {
17
+ const files = readdirSync(walletsDir).filter(f => f.endsWith(".json"))
18
+ if (files.length > 0) {
19
+ console.log("Wallets already exist in", walletsDir)
20
+ console.log("Use --force to regenerate")
21
+ return
22
+ }
23
+ }
24
+
25
+ mkdirSync(walletsDir, { recursive: true })
26
+
27
+ const arweave = Arweave.init({})
28
+
29
+ console.log("Generating wallets...\n")
30
+
31
+ for (const name of names) {
32
+ const jwk = await arweave.wallets.generate()
33
+ const addr = toAddr(jwk.n)
34
+ writeFileSync(
35
+ resolve(walletsDir, `${name}.json`),
36
+ JSON.stringify(jwk, null, 2),
37
+ )
38
+ console.log(` ${name.padEnd(5)} ${addr}`)
39
+ }
40
+
41
+ console.log(`\nWallets written to ${walletsDir}`)
42
+ }
43
+
44
+ main().catch(err => {
45
+ console.error(err)
46
+ process.exit(1)
47
+ })
package/esm/run.js CHANGED
@@ -1,4 +1,7 @@
1
- #!/usr/bin/env -S node --experimental-wasm-memory64
1
+ #!/usr/bin/env node
2
+ // Note: this script is normally launched by src/cli.js via pm2, which
3
+ // gates --experimental-wasm-memory64 to Node <24. If you run this file
4
+ // directly on Node <24, prefix with the flag: NODE_OPTIONS=--experimental-wasm-memory64
2
5
  import yargs from "yargs"
3
6
  import { resolve } from "path"
4
7
  import { unlinkSync } from "fs"
package/esm/server.js CHANGED
@@ -16,8 +16,10 @@ class Server {
16
16
  aoconnect,
17
17
  log = false,
18
18
  db,
19
+ storage,
19
20
  port,
20
21
  adaptor,
22
+ units,
21
23
  } = {}) {
22
24
  if (port) {
23
25
  ar = port
@@ -27,18 +29,25 @@ class Server {
27
29
  cu = port + 4
28
30
  aoconnect = optAO(5000)
29
31
  }
32
+ // Parse enabled units (default: all)
33
+ const enabled = units
34
+ ? new Set(units.map(u => u.toLowerCase()))
35
+ : new Set(["ar", "bd", "mu", "su", "cu"])
36
+ if (enabled.has("ar")) enabled.add("bd")
37
+ this._enabledUnits = enabled
38
+
30
39
  if (!aoconnect) {
31
- const { mem } = connect(aoconnect, { log, cache: db })
40
+ const { mem } = connect(aoconnect, { log, cache: db, storage })
32
41
  aoconnect = mem
33
42
  }
34
- this.adaptor = adaptor ?? new Adaptor({ hb_url, aoconnect, log, db })
43
+ this.adaptor = adaptor ?? new Adaptor({ hb_url, aoconnect, log, db, storage })
35
44
  this.ports = { ar, mu, su, cu, bd }
36
45
  this.servers = []
37
- this.ar()
38
- this.bd()
39
- this.mu()
40
- this.su()
41
- this.cu()
46
+ if (enabled.has("ar")) this.ar()
47
+ if (enabled.has("bd")) this.bd()
48
+ if (enabled.has("mu")) this.mu()
49
+ if (enabled.has("su")) this.su()
50
+ if (enabled.has("cu")) this.cu()
42
51
  }
43
52
 
44
53
  ar() {
@@ -93,6 +102,9 @@ class Server {
93
102
  res(res) {
94
103
  return data => {
95
104
  if (data.status) res.status(data.status)
105
+ if (data.headers) {
106
+ for (const [k, v] of Object.entries(data.headers)) res.set(k, v)
107
+ }
96
108
  if (data.error) {
97
109
  res.json({ error: data.error })
98
110
  } else if (data.json) {
@@ -137,15 +149,23 @@ class Server {
137
149
 
138
150
  end() {
139
151
  return new Promise(res => {
152
+ if (this.servers.length === 0) return res()
140
153
  let count = 0
141
- for (const v of this.servers)
154
+ for (const v of this.servers) {
155
+ // close() waits for active connections to drain; force-close any
156
+ // hanging keepalive sockets so the test process can exit when a
157
+ // dependent HyperBEAM instance dies mid-request.
158
+ if (typeof v.closeAllConnections === "function") {
159
+ try { v.closeAllConnections() } catch (_e) {}
160
+ }
142
161
  v.close(() => {
143
162
  count += 1
144
- if (count >= 4) {
163
+ if (count >= this.servers.length) {
145
164
  console.log("servers closed!")
146
165
  res()
147
166
  }
148
167
  })
168
+ }
149
169
  })
150
170
  }
151
171
  }
@@ -0,0 +1,183 @@
1
+ // Multi-backend storage adapter
2
+ // Routes data to D1 (indexed queries), R2 (large blobs), or DO (hot compute state)
3
+ // based on key prefix.
4
+
5
+ function sanitize(val) {
6
+ if (val == null) return val
7
+ if (typeof val === "function") return null
8
+ if (typeof val !== "object") return val
9
+ if (val instanceof Uint8Array || val instanceof ArrayBuffer) return val
10
+ if (ArrayBuffer.isView(val)) return val
11
+ if (Array.isArray(val)) return val.map(sanitize)
12
+ const out = {}
13
+ for (const k of Object.keys(val)) {
14
+ const v = val[k]
15
+ if (typeof v !== "function") out[k] = sanitize(v)
16
+ }
17
+ return out
18
+ }
19
+
20
+ export default class StorageMulti {
21
+ constructor({ storage, d1, r2, kv }) {
22
+ this.storage = storage // DO storage (always available)
23
+ this.d1 = d1 || null // D1 database (optional)
24
+ this.r2 = r2 || null // R2 bucket (optional)
25
+ this.kv = kv || null // KV namespace (optional)
26
+ }
27
+
28
+ // --- D1 helpers ---
29
+
30
+ async d1WriteTx(tx, blockId, blockHeight) {
31
+ if (!this.d1) return
32
+ const tags = tx.tags || []
33
+ let dataSize = "0"
34
+ let dataType = ""
35
+ if (tx._data) {
36
+ dataSize = tx._data.size || "0"
37
+ dataType = tx._data.type || ""
38
+ } else if (tx.data_size) {
39
+ dataSize = tx.data_size
40
+ }
41
+
42
+ await this.d1.batch([
43
+ this.d1.prepare(
44
+ `INSERT OR REPLACE INTO txs (id, block_id, block_height, owner, recipient, anchor, signature, data_size, data_type, bundle_id, parent_id)
45
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
46
+ ).bind(
47
+ tx.id,
48
+ blockId,
49
+ blockHeight,
50
+ tx.owner || "",
51
+ tx.recipient || "",
52
+ tx.anchor || "",
53
+ tx.signature || null,
54
+ dataSize,
55
+ dataType,
56
+ tx.bundledIn?.id || tx.parent?.id || null,
57
+ tx.parent?.id || null
58
+ ),
59
+ ...tags.map(t =>
60
+ this.d1.prepare(
61
+ `INSERT INTO tx_tags (tx_id, name, value) VALUES (?, ?, ?)`
62
+ ).bind(tx.id, t.name, t.value)
63
+ ),
64
+ ])
65
+ }
66
+
67
+ async d1WriteBlock(block) {
68
+ if (!this.d1) return
69
+ await this.d1.prepare(
70
+ `INSERT OR REPLACE INTO blocks (id, height, timestamp, previous)
71
+ VALUES (?, ?, ?, ?)`
72
+ ).bind(block.id, block.height, block.timestamp, block.previous || "")
73
+ .run()
74
+ }
75
+
76
+ async d1WriteAddrmap(address, entry) {
77
+ if (!this.d1) return
78
+ await this.d1.prepare(
79
+ `INSERT OR REPLACE INTO addrmap (address, key) VALUES (?, ?)`
80
+ ).bind(address, entry.key || entry.address || "")
81
+ .run()
82
+ }
83
+
84
+ async d1WriteModule(name, wasmId) {
85
+ if (!this.d1) return
86
+ await this.d1.prepare(
87
+ `INSERT OR REPLACE INTO modules (name, wasm_id) VALUES (?, ?)`
88
+ ).bind(name, wasmId)
89
+ .run()
90
+ }
91
+
92
+ async d1WriteWasm(id, entry) {
93
+ if (!this.d1) return
94
+ await this.d1.prepare(
95
+ `INSERT OR REPLACE INTO wasms (id, format, file, variant) VALUES (?, ?, ?, ?)`
96
+ ).bind(
97
+ id,
98
+ entry.format || "wasm64-unknown-emscripten-draft_2024_02_15",
99
+ entry.file || null,
100
+ entry.variant || null
101
+ ).run()
102
+ }
103
+
104
+ // --- R2 helpers ---
105
+
106
+ async r2PutMemory(pid, memory) {
107
+ if (!this.r2) return false
108
+ await this.r2.put(`env/${pid}/memory`, memory)
109
+ return true
110
+ }
111
+
112
+ async r2GetMemory(pid) {
113
+ if (!this.r2) return null
114
+ const obj = await this.r2.get(`env/${pid}/memory`)
115
+ if (!obj) return null
116
+ return new Uint8Array(await obj.arrayBuffer())
117
+ }
118
+
119
+ async r2PutTxData(txId, data) {
120
+ if (!this.r2) return false
121
+ await this.r2.put(`txdata/${txId}`, data)
122
+ return true
123
+ }
124
+
125
+ async r2GetTxData(txId) {
126
+ if (!this.r2) return null
127
+ const obj = await this.r2.get(`txdata/${txId}`)
128
+ if (!obj) return null
129
+ return await obj.text()
130
+ }
131
+
132
+ async r2PutWasm(id, data) {
133
+ if (!this.r2) return false
134
+ await this.r2.put(`wasms/${id}`, data)
135
+ return true
136
+ }
137
+
138
+ async r2GetWasm(id) {
139
+ if (!this.r2) return null
140
+ const obj = await this.r2.get(`wasms/${id}`)
141
+ if (!obj) return null
142
+ return new Uint8Array(await obj.arrayBuffer())
143
+ }
144
+
145
+ // --- D1 read helpers ---
146
+
147
+ async d1GetTxById(id) {
148
+ if (!this.d1) return null
149
+ const row = await this.d1.prepare("SELECT * FROM txs WHERE id = ?").bind(id).first()
150
+ if (!row) return null
151
+ const { results: tags } = await this.d1.prepare(
152
+ "SELECT name, value FROM tx_tags WHERE tx_id = ?"
153
+ ).bind(id).all()
154
+ const tx = {
155
+ id: row.id,
156
+ owner: row.owner || "",
157
+ recipient: row.recipient || "",
158
+ anchor: row.anchor || "",
159
+ signature: row.signature || "",
160
+ tags,
161
+ _data: { size: row.data_size || "0", type: row.data_type || "" },
162
+ block: row.block_id || "",
163
+ }
164
+ if (row.bundle_id) tx.bundledIn = { id: row.bundle_id }
165
+ if (row.parent_id) tx.parent = { id: row.parent_id }
166
+ return tx
167
+ }
168
+
169
+ // --- DO storage pass-through (dodb compatible interface) ---
170
+
171
+ async put(key, val) {
172
+ await this.storage.put(key, sanitize(val))
173
+ }
174
+
175
+ async get(key) {
176
+ return (await this.storage.get(key)) ?? null
177
+ }
178
+
179
+ async getKeys({ start, end }) {
180
+ const map = await this.storage.list({ start, end })
181
+ return [...map.keys()]
182
+ }
183
+ }