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.
- package/cjs/accounts-web.js +25 -0
- package/cjs/accounts.js +38 -0
- package/cjs/adaptor-base.js +504 -287
- package/cjs/adaptor-cf.js +42 -0
- package/cjs/ao-loader.js +1 -1
- package/cjs/ao.js +18 -6
- package/cjs/aoconnect-base.js +1017 -546
- package/cjs/aoconnect-cf.js +24 -0
- package/cjs/ar-remote.js +277 -0
- package/cjs/armem-base.js +822 -211
- package/cjs/armem-cf.js +128 -0
- package/cjs/armem.js +11 -5
- package/cjs/bar.js +511 -173
- package/cjs/car.js +37 -0
- package/cjs/cf-env.js +54 -0
- package/cjs/cf.js +76 -0
- package/cjs/cli.js +12 -6
- package/cjs/create.js +1 -1
- package/cjs/devs.js +53 -0
- package/cjs/dodb.js +116 -0
- package/cjs/hb.js +136 -53
- package/cjs/hyperbeam.js +85 -44
- package/cjs/keygen.js +94 -0
- package/cjs/run.js +4 -1
- package/cjs/server.js +40 -9
- package/cjs/storage-multi.js +525 -0
- package/cjs/tgql-d1.js +664 -0
- package/cjs/tgql.js +293 -172
- package/cjs/workspace/.claude/agents/tester.md +2 -2
- package/cjs/workspace/.claude/skills/build/SKILL.md +2 -2
- package/cjs/workspace/.claude/skills/test/SKILL.md +3 -2
- package/cjs/workspace/CLAUDE.md +2 -2
- package/cjs/workspace/README.md +1 -1
- package/cjs/workspace/docs/debug.md +9 -4
- package/cjs/workspace/docs/hyperbeam-devices.md +50 -1
- package/cjs/workspace/package.json +3 -3
- package/esm/accounts-web.js +14 -0
- package/esm/accounts.js +27 -0
- package/esm/adaptor-base.js +129 -31
- package/esm/adaptor-cf.js +11 -0
- package/esm/ao-loader.js +1 -1
- package/esm/ao.js +21 -2
- package/esm/aoconnect-base.js +255 -7
- package/esm/aoconnect-cf.js +9 -0
- package/esm/ar-remote.js +87 -0
- package/esm/armem-base.js +304 -53
- package/esm/armem-cf.js +67 -0
- package/esm/armem.js +7 -2
- package/esm/bar.js +126 -16
- package/esm/car.js +10 -0
- package/esm/cf-env.js +29 -0
- package/esm/cf.js +11 -0
- package/esm/cli.js +6 -2
- package/esm/create.js +1 -1
- package/esm/devs.js +15 -0
- package/esm/dodb.js +26 -0
- package/esm/hb.js +93 -16
- package/esm/hyperbeam.js +68 -30
- package/esm/keygen.js +47 -0
- package/esm/run.js +4 -1
- package/esm/server.js +29 -9
- package/esm/storage-multi.js +183 -0
- package/esm/tgql-d1.js +407 -0
- package/esm/tgql.js +29 -10
- package/esm/workspace/.claude/agents/tester.md +2 -2
- package/esm/workspace/.claude/skills/build/SKILL.md +2 -2
- package/esm/workspace/.claude/skills/test/SKILL.md +3 -2
- package/esm/workspace/CLAUDE.md +2 -2
- package/esm/workspace/README.md +1 -1
- package/esm/workspace/docs/debug.md +9 -4
- package/esm/workspace/docs/hyperbeam-devices.md +50 -1
- package/esm/workspace/package.json +3 -3
- package/package.json +10 -3
- package/postinstall.cjs +84 -0
- package/wao-0.41.1.tgz +0 -0
package/esm/bar.js
CHANGED
|
@@ -27,6 +27,10 @@ class AR extends MAR {
|
|
|
27
27
|
this.mem = opt.mem ?? new opt.ArMem()
|
|
28
28
|
this.gql = new GQL({ mem: this.mem })
|
|
29
29
|
this.arweave = this.mem.arweave
|
|
30
|
+
// Block batching (DB path only)
|
|
31
|
+
this._pendingBatch = []
|
|
32
|
+
this._flushTimer = null
|
|
33
|
+
this._batchWindow = opt.batchWindow ?? 0 // ms, 0 = disabled
|
|
30
34
|
}
|
|
31
35
|
isHttpMsg(item) {
|
|
32
36
|
if (typeof item === "object" && item !== null) {
|
|
@@ -77,6 +81,21 @@ class AR extends MAR {
|
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
async postItems(items, jwk) {
|
|
84
|
+
// Remote mode: forward bundle to main AR via HTTP
|
|
85
|
+
if (this.mem._remote) {
|
|
86
|
+
let err = null
|
|
87
|
+
;({ err, jwk } = await this.checkWallet({ jwk }))
|
|
88
|
+
if (err) return { err }
|
|
89
|
+
if (!is(Array, items)) items = [items]
|
|
90
|
+
const bundle = await bundleAndSignData(items, new ArweaveSigner(jwk))
|
|
91
|
+
const tx = await this.mem.arweave.createTransaction(
|
|
92
|
+
{ data: bundle.binary },
|
|
93
|
+
jwk
|
|
94
|
+
)
|
|
95
|
+
tx.addTag("Bundle-Format", "binary")
|
|
96
|
+
tx.addTag("Bundle-Version", "2.0.0")
|
|
97
|
+
return await this.postTx(tx, jwk, items.map(i => ({ id: i.id })))
|
|
98
|
+
}
|
|
80
99
|
let err = null
|
|
81
100
|
;({ err, jwk } = await this.checkWallet({ jwk }))
|
|
82
101
|
if (err) return { err }
|
|
@@ -90,6 +109,10 @@ class AR extends MAR {
|
|
|
90
109
|
if (t.name === "Content-Type") data_type = t.value
|
|
91
110
|
const owner = await this.owner(di)
|
|
92
111
|
await this.mem.set({ key: di.owner, address: owner }, "addrmap", owner)
|
|
112
|
+
// D1: write addrmap
|
|
113
|
+
if (this.mem.db?.d1WriteAddrmap) {
|
|
114
|
+
try { await this.mem.db.d1WriteAddrmap(owner, { key: di.owner, address: owner }) } catch (e) {}
|
|
115
|
+
}
|
|
93
116
|
let _item = {
|
|
94
117
|
_data: { size: data_size, type: data_type },
|
|
95
118
|
anchor: di.anchor,
|
|
@@ -101,6 +124,12 @@ class AR extends MAR {
|
|
|
101
124
|
tags: di.tags,
|
|
102
125
|
data: di.data,
|
|
103
126
|
}
|
|
127
|
+
// Extract raw data to R2
|
|
128
|
+
if (_item.data && this.mem.db?.r2PutTxData) {
|
|
129
|
+
await this.mem.db.r2PutTxData(_item.id, _item.data)
|
|
130
|
+
_item._r2_data = true
|
|
131
|
+
_item.data = ""
|
|
132
|
+
}
|
|
104
133
|
await this.mem.set(_item, "txs", await di.id)
|
|
105
134
|
_items.push(_item)
|
|
106
135
|
}
|
|
@@ -115,19 +144,41 @@ class AR extends MAR {
|
|
|
115
144
|
}
|
|
116
145
|
|
|
117
146
|
async postTx(tx, jwk, items = []) {
|
|
147
|
+
// Remote mode: sign locally, POST to main AR
|
|
148
|
+
if (this.mem._remote) {
|
|
149
|
+
let err = null
|
|
150
|
+
;({ err, jwk } = await this.checkWallet({ jwk }))
|
|
151
|
+
if (err) return { err }
|
|
152
|
+
if (!tx.id) await this.mem.arweave.transactions.sign(tx, jwk)
|
|
153
|
+
const res = await this.mem._remote.postTx(tx)
|
|
154
|
+
return { res: { id: tx.id, status: 200 }, err: null, id: tx.id }
|
|
155
|
+
}
|
|
118
156
|
let err = null
|
|
119
157
|
;({ err, jwk } = await this.checkWallet({ jwk }))
|
|
120
158
|
if (err) return { err }
|
|
121
159
|
|
|
122
160
|
let res = null
|
|
123
161
|
if (!tx.id) await this.mem.arweave.transactions.sign(tx, jwk)
|
|
124
|
-
let height
|
|
125
|
-
|
|
162
|
+
let height
|
|
163
|
+
if (this.mem._d1Ready) {
|
|
164
|
+
// D1 ready: increment in-memory, flush every 10 txs
|
|
165
|
+
this.mem.height = (this.mem.height ?? 0) + 1
|
|
166
|
+
height = this.mem.height
|
|
167
|
+
if (height % 10 === 0) {
|
|
168
|
+
await this.mem.set(height, "height")
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
height = (await this.mem.get("height")) + 1
|
|
172
|
+
await this.mem.set(height, "height")
|
|
173
|
+
}
|
|
174
|
+
let previous = this.mem._d1Ready
|
|
175
|
+
? (this.mem.lastBlockId || "")
|
|
176
|
+
: (last(await this.mem.get("blocks")) ?? "")
|
|
126
177
|
let block = {
|
|
127
178
|
id: tx.id,
|
|
128
179
|
timestamp: Date.now(),
|
|
129
180
|
height,
|
|
130
|
-
previous
|
|
181
|
+
previous,
|
|
131
182
|
txs: [],
|
|
132
183
|
}
|
|
133
184
|
let msg = null
|
|
@@ -144,6 +195,7 @@ class AR extends MAR {
|
|
|
144
195
|
"Process",
|
|
145
196
|
"Module",
|
|
146
197
|
"Scheduler-Location",
|
|
198
|
+
"Scheduler-Transfer",
|
|
147
199
|
"Attestation",
|
|
148
200
|
"Available",
|
|
149
201
|
])
|
|
@@ -157,7 +209,12 @@ class AR extends MAR {
|
|
|
157
209
|
}
|
|
158
210
|
block.txs.push(item.id)
|
|
159
211
|
_txs.block = block.id
|
|
160
|
-
|
|
212
|
+
// Re-store item with parent/bundledIn/block, but drop the raw
|
|
213
|
+
// DataItem binary (item.item) to save space — data is in R2 or
|
|
214
|
+
// can be reconstructed from the wrapper bundle tx.
|
|
215
|
+
const slim = { ..._txs, bundle: tx.id }
|
|
216
|
+
delete slim.item
|
|
217
|
+
await this.mem.set(slim, "txs", item.id)
|
|
161
218
|
}
|
|
162
219
|
}
|
|
163
220
|
let _tags = []
|
|
@@ -180,19 +237,64 @@ class AR extends MAR {
|
|
|
180
237
|
}
|
|
181
238
|
tx.tags = _tags
|
|
182
239
|
tx.owner = await this.arweave.wallets.jwkToAddress({ n: tx.owner })
|
|
240
|
+
tx.recipient = tx.target || ""
|
|
183
241
|
let _txs = tx
|
|
184
242
|
block.txs.push(tx.id)
|
|
185
243
|
_txs.block = block.id
|
|
244
|
+
// Extract raw data to R2
|
|
245
|
+
if (_txs.data && this.mem.db?.r2PutTxData) {
|
|
246
|
+
await this.mem.db.r2PutTxData(tx.id, _txs.data)
|
|
247
|
+
_txs._r2_data = true
|
|
248
|
+
_txs.data = ""
|
|
249
|
+
}
|
|
186
250
|
await this.mem.set(_txs, "txs", tx.id)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
251
|
+
// Update block tracking — always maintain blocks array + blockmap
|
|
252
|
+
// so the O(n) scan fallback in tgql.js works even if D1 is stale
|
|
253
|
+
if (this.mem._d1Ready) {
|
|
254
|
+
await this.mem.set(block.id, "lastBlockId")
|
|
255
|
+
this.mem.lastBlockId = block.id
|
|
256
|
+
}
|
|
257
|
+
this.mem.blocks ??= []
|
|
258
|
+
this.mem.blocks.push(block.id)
|
|
259
|
+
await this.mem.set(this.mem.blocks, "blocks")
|
|
190
260
|
await this.mem.set(block, "blockmap", block.id)
|
|
191
261
|
|
|
192
262
|
if (jwk) {
|
|
193
263
|
const owner = await this.arweave.wallets.jwkToAddress(jwk)
|
|
194
264
|
await this.mem.set({ address: owner, key: jwk.n }, "addrmap", owner)
|
|
265
|
+
// D1: write addrmap
|
|
266
|
+
if (this.mem.db?.d1WriteAddrmap) {
|
|
267
|
+
try { await this.mem.db.d1WriteAddrmap(owner, { address: owner, key: jwk.n }) } catch (e) {}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// D1 write: block + txs + tags (only when D1 schema is verified ready)
|
|
272
|
+
if (this.mem._d1Ready && this.mem.db?.d1WriteBlock) {
|
|
273
|
+
const mainTx = {
|
|
274
|
+
id: tx.id,
|
|
275
|
+
owner: tx.owner,
|
|
276
|
+
recipient: tx.recipient || "",
|
|
277
|
+
anchor: tx.anchor || "",
|
|
278
|
+
signature: tx.signature || "",
|
|
279
|
+
tags: _tags,
|
|
280
|
+
_data: tx._data || { size: tx.data ? String(tx.data.length) : "0", type: "" },
|
|
281
|
+
bundledIn: tx.bundledIn,
|
|
282
|
+
parent: tx.parent,
|
|
283
|
+
}
|
|
284
|
+
if (this._batchWindow > 0) {
|
|
285
|
+
this._pendingBatch.push({ block, mainTx, items: [...items] })
|
|
286
|
+
if (!this._flushTimer) {
|
|
287
|
+
this._flushTimer = setTimeout(() => this._flushBatch(), this._batchWindow)
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
await this.mem.db.d1WriteBlock(block)
|
|
291
|
+
await this.mem.db.d1WriteTx(mainTx, block.id, block.height)
|
|
292
|
+
for (const item of items) {
|
|
293
|
+
await this.mem.db.d1WriteTx(item, block.id, block.height)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
195
296
|
}
|
|
297
|
+
|
|
196
298
|
res = { id: tx.id, status: 200, statusText: "200" }
|
|
197
299
|
if (this.log) {
|
|
198
300
|
if (msg) {
|
|
@@ -206,6 +308,21 @@ class AR extends MAR {
|
|
|
206
308
|
return { res, err, id: tx.id }
|
|
207
309
|
}
|
|
208
310
|
|
|
311
|
+
async _flushBatch() {
|
|
312
|
+
this._flushTimer = null
|
|
313
|
+
if (!this._pendingBatch.length) return
|
|
314
|
+
const batch = this._pendingBatch.splice(0)
|
|
315
|
+
for (const { block, mainTx, items } of batch) {
|
|
316
|
+
await this.mem.db.d1WriteBlock(block)
|
|
317
|
+
await this.mem.db.d1WriteTx(mainTx, block.id, block.height)
|
|
318
|
+
for (const item of items) {
|
|
319
|
+
await this.mem.db.d1WriteTx(item, block.id, block.height)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Flush height to DO after batch
|
|
323
|
+
await this.mem.set(this.mem.height, "height")
|
|
324
|
+
}
|
|
325
|
+
|
|
209
326
|
async tx(id) {
|
|
210
327
|
return await this.mem.getTx(id)
|
|
211
328
|
}
|
|
@@ -219,15 +336,8 @@ class AR extends MAR {
|
|
|
219
336
|
}
|
|
220
337
|
let tx = await this.mem.getTx(id)
|
|
221
338
|
let _data = tx?.data ?? null
|
|
222
|
-
if (
|
|
223
|
-
_data =
|
|
224
|
-
} else if (_data) {
|
|
225
|
-
// need to check production
|
|
226
|
-
if (tx._data.type === "") {
|
|
227
|
-
_data = tobuff(_data)
|
|
228
|
-
} else {
|
|
229
|
-
_data = Buffer.from(base64url.decode(_data))
|
|
230
|
-
}
|
|
339
|
+
if (_data && is(String, _data)) {
|
|
340
|
+
_data = tobuff(_data)
|
|
231
341
|
}
|
|
232
342
|
let isBuf = is(Uint8Array, _data) || is(ArrayBuffer, _data)
|
|
233
343
|
let isStr = is(String, _data)
|
package/esm/car.js
ADDED
package/esm/cf-env.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// CF Workers polyfill: make emscripten (ao-loader) detect "web" environment.
|
|
2
|
+
// Must be imported BEFORE ao-loader.js so it runs first.
|
|
3
|
+
if (typeof globalThis.window === "undefined") {
|
|
4
|
+
globalThis.window = globalThis
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// CF Workers blocks WebAssembly.instantiate(buffer, imports) — only pre-compiled
|
|
8
|
+
// WebAssembly.Module objects are allowed. Patch the API so that when a pre-compiled
|
|
9
|
+
// module is queued (via cfWasmReady), emscripten's default async loading path works
|
|
10
|
+
// unchanged, preserving Asyncify and all other internals.
|
|
11
|
+
const _wasmInstantiate = WebAssembly.instantiate.bind(WebAssembly)
|
|
12
|
+
let _cfWasmModule = null
|
|
13
|
+
|
|
14
|
+
export function cfWasmReady(mod) {
|
|
15
|
+
_cfWasmModule = mod
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
WebAssembly.instantiate = async function (source, imports) {
|
|
19
|
+
if (
|
|
20
|
+
_cfWasmModule &&
|
|
21
|
+
(source instanceof ArrayBuffer || ArrayBuffer.isView(source))
|
|
22
|
+
) {
|
|
23
|
+
const mod = _cfWasmModule
|
|
24
|
+
_cfWasmModule = null
|
|
25
|
+
const instance = await _wasmInstantiate(mod, imports)
|
|
26
|
+
return { instance, module: mod }
|
|
27
|
+
}
|
|
28
|
+
return _wasmInstantiate(source, imports)
|
|
29
|
+
}
|
package/esm/cf.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { acc, mu, su, cu } from "./accounts-web.js"
|
|
2
|
+
import { connect } from "./aoconnect-cf.js"
|
|
3
|
+
import Adaptor from "./adaptor-cf.js"
|
|
4
|
+
import AO from "./wao.js"
|
|
5
|
+
import AR from "./car.js"
|
|
6
|
+
import GQL from "./tgql.js"
|
|
7
|
+
import ArMem from "./armem-cf.js"
|
|
8
|
+
import { dirname } from "./utils.js"
|
|
9
|
+
const scheduler = "_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA"
|
|
10
|
+
|
|
11
|
+
export { GQL, ArMem, AO, AR, connect, acc, mu, su, cu, scheduler, Adaptor }
|
package/esm/cli.js
CHANGED
|
@@ -192,7 +192,7 @@ const ask = question =>
|
|
|
192
192
|
|
|
193
193
|
|
|
194
194
|
const HB_REPO = "https://github.com/permaweb/HyperBEAM.git"
|
|
195
|
-
const HB_TAG = "v0.9-
|
|
195
|
+
const HB_TAG = "v0.9-FINAL"
|
|
196
196
|
|
|
197
197
|
const which = async cmd => {
|
|
198
198
|
try {
|
|
@@ -527,10 +527,14 @@ else {
|
|
|
527
527
|
process.exit(2)
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
+
// Node 24+ enables wasm-memory64 by default and rejects the
|
|
531
|
+
// experimental flag at startup; older Node still needs it.
|
|
532
|
+
const _nodeMajor = parseInt((process.versions.node || "0").split(".")[0], 10)
|
|
533
|
+
const _wasm64Flag = _nodeMajor >= 24 ? "" : "--experimental-wasm-memory64"
|
|
530
534
|
pm2.start(
|
|
531
535
|
{
|
|
532
536
|
script: resolve(await dirname(), cmd.script),
|
|
533
|
-
nodeArgs:
|
|
537
|
+
nodeArgs: _wasm64Flag,
|
|
534
538
|
instances: 1,
|
|
535
539
|
force: true,
|
|
536
540
|
args: args,
|
package/esm/create.js
CHANGED
|
@@ -141,7 +141,7 @@ const ask = (question) => new Promise(res => {
|
|
|
141
141
|
})
|
|
142
142
|
|
|
143
143
|
const HB_REPO = "https://github.com/permaweb/HyperBEAM.git"
|
|
144
|
-
const HB_TAG = "v0.9-
|
|
144
|
+
const HB_TAG = "v0.9-FINAL"
|
|
145
145
|
|
|
146
146
|
const yellow = t => `\x1b[33m${t}\x1b[39m`
|
|
147
147
|
|
package/esm/devs.js
CHANGED
|
@@ -40,6 +40,7 @@ export default {
|
|
|
40
40
|
p4: { name: "p4@1.0", module: "dev_p4" },
|
|
41
41
|
"node-process": { name: "node-process@1.0", module: "dev_node_process" },
|
|
42
42
|
"simple-pay": { name: "simple-pay@1.0", module: "dev_simple_pay" },
|
|
43
|
+
metering: { name: "metering@1.0", module: "dev_metering" },
|
|
43
44
|
|
|
44
45
|
// tested
|
|
45
46
|
cron: { name: "cron@1.0", module: "dev_cron" },
|
|
@@ -51,12 +52,16 @@ export default {
|
|
|
51
52
|
"local-name": { name: "local-name@1.0", module: "dev_local_name" },
|
|
52
53
|
lookup: { name: "lookup@1.0", module: "dev_lookup" },
|
|
53
54
|
name: { name: "name@1.0", module: "dev_name" },
|
|
55
|
+
location: { name: "location@1.0", module: "dev_location" },
|
|
56
|
+
trie: { name: "trie@1.0", module: "dev_trie" },
|
|
54
57
|
|
|
55
58
|
// others
|
|
56
59
|
compute: { name: "compute@1.0", module: "dev_cu" },
|
|
57
60
|
dedup: { name: "dedup@1.0", module: "dev_dedup" },
|
|
58
61
|
manifest: { name: "manifest@1.0", module: "dev_manifest" },
|
|
59
62
|
monitor: { name: "monitor@1.0", module: "dev_monitor" },
|
|
63
|
+
blacklist: { name: "blacklist@1.0", module: "dev_blacklist" },
|
|
64
|
+
"rate-limit": { name: "rate-limit@1.0", module: "dev_rate_limit" },
|
|
60
65
|
|
|
61
66
|
// advanced
|
|
62
67
|
snp: { name: "snp@1.0", module: "dev_snp" },
|
|
@@ -68,4 +73,14 @@ export default {
|
|
|
68
73
|
hyperbuddy: { name: "hyperbuddy@1.0", module: "dev_hyperbuddy" },
|
|
69
74
|
ans104: { name: "ans104@1.0", module: "dev_codec_ans104" },
|
|
70
75
|
cacheviz: { name: "cacheviz@1.0", module: "dev_cacheviz" },
|
|
76
|
+
|
|
77
|
+
// v0.9-FINAL additions
|
|
78
|
+
arweave: { name: "arweave@2.9", module: "dev_arweave" },
|
|
79
|
+
"b32-name": { name: "b32-name@1.0", module: "dev_b32_name" },
|
|
80
|
+
bundler: { name: "bundler@1.0", module: "dev_bundler" },
|
|
81
|
+
copycat: { name: "copycat@1.0", module: "dev_copycat" },
|
|
82
|
+
gzip: { name: "gzip@1.0", module: "dev_gzip" },
|
|
83
|
+
secret: { name: "secret@1.0", module: "dev_secret" },
|
|
84
|
+
tx: { name: "tx@1.0", module: "dev_codec_tx" },
|
|
85
|
+
whois: { name: "whois@1.0", module: "dev_whois" },
|
|
71
86
|
}
|
package/esm/dodb.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Strip functions and other non-serializable values before storing.
|
|
2
|
+
// LMDB silently drops these; DO storage would throw on them.
|
|
3
|
+
// The codebase handles missing `handle` by recreating it on read.
|
|
4
|
+
function sanitize(val) {
|
|
5
|
+
if (val == null) return val
|
|
6
|
+
if (typeof val === "function") return null
|
|
7
|
+
if (typeof val !== "object") return val
|
|
8
|
+
if (val instanceof Uint8Array || val instanceof ArrayBuffer) return val
|
|
9
|
+
if (ArrayBuffer.isView(val)) return val
|
|
10
|
+
if (Array.isArray(val)) return val.map(sanitize)
|
|
11
|
+
const out = {}
|
|
12
|
+
for (const k of Object.keys(val)) {
|
|
13
|
+
const v = val[k]
|
|
14
|
+
if (typeof v !== "function") out[k] = sanitize(v)
|
|
15
|
+
}
|
|
16
|
+
return out
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default (storage) => ({
|
|
20
|
+
put: async (key, val) => await storage.put(key, sanitize(val)),
|
|
21
|
+
get: async (key) => await storage.get(key) ?? null,
|
|
22
|
+
getKeys: async ({ start, end }) => {
|
|
23
|
+
const map = await storage.list({ start, end })
|
|
24
|
+
return [...map.keys()]
|
|
25
|
+
},
|
|
26
|
+
})
|
package/esm/hb.js
CHANGED
|
@@ -17,6 +17,83 @@ import aos_wamr from "./aos_wamr.js"
|
|
|
17
17
|
import { ArweaveSigner } from "@ar.io/sdk"
|
|
18
18
|
import { createData } from "@dha-team/arbundles"
|
|
19
19
|
|
|
20
|
+
// Convert objects whose keys are sequential numeric strings (e.g.
|
|
21
|
+
// `{1: "a", 2: "b"}`) into arrays. v0.9-FINAL's multipart re-encoder treats
|
|
22
|
+
// numbered TABMs as lists; JS hbsig only emits `.="list"` in ao-types when
|
|
23
|
+
// the value is an Array, so without this conversion the content-digest of
|
|
24
|
+
// the request body diverges from HB's recomputation and rsa-pss verification
|
|
25
|
+
// fails with `process_not_verified`.
|
|
26
|
+
const normalizeNumberedObjects = v => {
|
|
27
|
+
if (v === null || v === undefined) return v
|
|
28
|
+
if (Array.isArray(v)) return v.map(normalizeNumberedObjects)
|
|
29
|
+
if (Buffer.isBuffer(v) || v instanceof Blob) return v
|
|
30
|
+
if (typeof v !== "object") return v
|
|
31
|
+
const keys = Object.keys(v)
|
|
32
|
+
if (
|
|
33
|
+
keys.length > 0 &&
|
|
34
|
+
keys.every(k => /^[1-9][0-9]*$/.test(k)) &&
|
|
35
|
+
keys
|
|
36
|
+
.map(k => parseInt(k, 10))
|
|
37
|
+
.sort((a, b) => a - b)
|
|
38
|
+
.every((n, i) => n === i + 1)
|
|
39
|
+
) {
|
|
40
|
+
return keys
|
|
41
|
+
.map(k => parseInt(k, 10))
|
|
42
|
+
.sort((a, b) => a - b)
|
|
43
|
+
.map(i => normalizeNumberedObjects(v[String(i)]))
|
|
44
|
+
}
|
|
45
|
+
const out = {}
|
|
46
|
+
for (const [k, val] of Object.entries(v)) out[k] = normalizeNumberedObjects(val)
|
|
47
|
+
return out
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// v0.9-FINAL returns Messages with lowercase fields (data, target, anchor, from)
|
|
51
|
+
// where legacy AOS-format consumers expect capitalised (Data, Target, Anchor,
|
|
52
|
+
// From). Also lower-cases tag {name, value} pairs and may emit them as a
|
|
53
|
+
// numbered-key map under "tags" rather than an array under "Tags". Normalise
|
|
54
|
+
// so existing callers that read `Messages[0].Data` keep working.
|
|
55
|
+
const _normalizeLegacyResult = res => {
|
|
56
|
+
if (!res || typeof res !== "object") return res
|
|
57
|
+
const _msgs = res.Messages ?? res.messages
|
|
58
|
+
if (!Array.isArray(_msgs)) return res
|
|
59
|
+
const _tagsArray = t => {
|
|
60
|
+
if (!t) return []
|
|
61
|
+
if (Array.isArray(t)) {
|
|
62
|
+
return t.map(x => ({
|
|
63
|
+
name: x?.name ?? x?.Name,
|
|
64
|
+
value: x?.value ?? x?.Value,
|
|
65
|
+
})).filter(x => x.name != null)
|
|
66
|
+
}
|
|
67
|
+
if (typeof t === "object") {
|
|
68
|
+
return Object.keys(t)
|
|
69
|
+
.filter(k => /^\d+$/.test(k))
|
|
70
|
+
.sort((a, b) => Number(a) - Number(b))
|
|
71
|
+
.map(k => {
|
|
72
|
+
const x = t[k]
|
|
73
|
+
return {
|
|
74
|
+
name: x?.name ?? x?.Name,
|
|
75
|
+
value: x?.value ?? x?.Value,
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
.filter(x => x.name != null)
|
|
79
|
+
}
|
|
80
|
+
return []
|
|
81
|
+
}
|
|
82
|
+
const Messages = _msgs.map(m => {
|
|
83
|
+
if (!m || typeof m !== "object") return m
|
|
84
|
+
const tags = _tagsArray(m.Tags ?? m.tags)
|
|
85
|
+
return {
|
|
86
|
+
...m,
|
|
87
|
+
Data: m.Data ?? m.data,
|
|
88
|
+
Target: m.Target ?? m.target,
|
|
89
|
+
Anchor: m.Anchor ?? m.anchor,
|
|
90
|
+
From: m.From ?? m.from,
|
|
91
|
+
Tags: tags,
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
return { ...res, Messages }
|
|
95
|
+
}
|
|
96
|
+
|
|
20
97
|
const toMsg = async req => {
|
|
21
98
|
let msg = {}
|
|
22
99
|
req?.headers?.forEach((v, k) => {
|
|
@@ -251,24 +328,17 @@ class HB {
|
|
|
251
328
|
async computeLegacy({ pid, slot }) {
|
|
252
329
|
// Match master: compute and parse results.json.body
|
|
253
330
|
const json = await this.compute({ pid, slot })
|
|
331
|
+
let parsed = json
|
|
254
332
|
if (json?.results?.json?.body) {
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (json?.
|
|
259
|
-
return JSON.parse(json["compute/results/json"].body)
|
|
260
|
-
}
|
|
261
|
-
// Remote nodes return results.raw with CU format directly
|
|
262
|
-
if (json?.results?.raw) {
|
|
333
|
+
parsed = JSON.parse(json.results.json.body)
|
|
334
|
+
} else if (json?.["compute/results/json"]?.body) {
|
|
335
|
+
parsed = JSON.parse(json["compute/results/json"].body)
|
|
336
|
+
} else if (json?.results?.raw) {
|
|
263
337
|
const raw = typeof json.results.raw === "string"
|
|
264
338
|
? JSON.parse(json.results.raw) : json.results.raw
|
|
265
|
-
if (raw?.Messages || raw?.Output)
|
|
266
|
-
}
|
|
267
|
-
// Another fallback: check if it's the raw CU format
|
|
268
|
-
if (json?.Messages || json?.Output) {
|
|
269
|
-
return json
|
|
339
|
+
if (raw?.Messages || raw?.Output) parsed = raw
|
|
270
340
|
}
|
|
271
|
-
return
|
|
341
|
+
return _normalizeLegacyResult(parsed)
|
|
272
342
|
}
|
|
273
343
|
|
|
274
344
|
async cacheScript(data, type = "application/lua") {
|
|
@@ -448,6 +518,13 @@ class HB {
|
|
|
448
518
|
}
|
|
449
519
|
async spawn(tags = {}) {
|
|
450
520
|
await this.setInfo()
|
|
521
|
+
// v0.9-FINAL multipart re-encoding sees a numbered-key object as a list
|
|
522
|
+
// (TABM with .="list" in ao-types). JS hbsig's structured codec encodes
|
|
523
|
+
// a plain numeric-keyed object without that marker, so HB's recomputed
|
|
524
|
+
// content-digest doesn't match what JS signed and verify fails. Pre-
|
|
525
|
+
// normalize any numbered-key object value into an array so the array
|
|
526
|
+
// path (which already emits .="list") is used.
|
|
527
|
+
tags = normalizeNumberedObjects(tags)
|
|
451
528
|
let res = null
|
|
452
529
|
if (this.format === "ans104") {
|
|
453
530
|
res = await this.post104({
|
|
@@ -462,7 +539,7 @@ class HB {
|
|
|
462
539
|
})
|
|
463
540
|
return { res, pid: res.out.process }
|
|
464
541
|
} else {
|
|
465
|
-
// Use httpsig-signed multipart POST
|
|
542
|
+
// Use httpsig-signed multipart POST
|
|
466
543
|
const spawnTags = mergeLeft(tags, {
|
|
467
544
|
"random-seed": seed(16),
|
|
468
545
|
type: "Process",
|
|
@@ -742,7 +819,7 @@ class HB {
|
|
|
742
819
|
i++
|
|
743
820
|
}
|
|
744
821
|
}
|
|
745
|
-
// Add accept-bundle header to get inline data instead of links
|
|
822
|
+
// Add accept-bundle header to get inline data instead of links
|
|
746
823
|
const url = `${this.url}${path}${_json}${_params}`
|
|
747
824
|
let response
|
|
748
825
|
for (let attempt = 0; attempt < 3; attempt++) {
|