wao 0.40.1 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +22 -10
- package/cjs/aoconnect-base.js +1010 -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/docs/wao-sdk.md +1 -1
- package/cjs/workspace/package.json +4 -4
- 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 +24 -5
- package/esm/aoconnect-base.js +248 -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/docs/wao-sdk.md +1 -1
- package/esm/workspace/package.json +4 -4
- package/package.json +10 -3
- package/postinstall.cjs +84 -0
package/esm/tgql-d1.js
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
// D1-backed GQL query engine
|
|
2
|
+
// Replaces O(n) scan in tgql.js with indexed SQL queries
|
|
3
|
+
|
|
4
|
+
import { reverse, is, isNil, last, clone, pick, map, includes } from "ramda"
|
|
5
|
+
|
|
6
|
+
const subs = {
|
|
7
|
+
owner: ["address", "key"],
|
|
8
|
+
fee: ["winston", "ar"],
|
|
9
|
+
quantity: ["winston", "ar"],
|
|
10
|
+
data: ["size", "type"],
|
|
11
|
+
tags: ["name", "value"],
|
|
12
|
+
block: ["id", "timestamp", "height", "previous"],
|
|
13
|
+
parent: ["id"],
|
|
14
|
+
bundledIn: ["id"],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const field = (key, val = true) => {
|
|
18
|
+
if (includes(key, ["id", "anchor", "signature", "recipient"])) {
|
|
19
|
+
return { key }
|
|
20
|
+
} else if (subs[key]) {
|
|
21
|
+
let _subs = []
|
|
22
|
+
if (val === true) val = subs[key]
|
|
23
|
+
else if (is(Object, val) && !is(Array, val)) {
|
|
24
|
+
let _val = []
|
|
25
|
+
let isTrue = false
|
|
26
|
+
let isFalse = false
|
|
27
|
+
for (const k in val)
|
|
28
|
+
if (val[k] === true) isTrue = true
|
|
29
|
+
else if (val[k] === false) isFalse = true
|
|
30
|
+
if (!isTrue && isFalse) {
|
|
31
|
+
for (const k of subs[key]) if (val[k] !== false) _val.push(k)
|
|
32
|
+
} else {
|
|
33
|
+
for (const k in val) if (val[k] === true) _val.push(k)
|
|
34
|
+
}
|
|
35
|
+
val = _val
|
|
36
|
+
} else if (!is(Array, val)) val = subs[key]
|
|
37
|
+
for (const v2 of val) {
|
|
38
|
+
if (is(String, v2) && includes(v2, subs[key])) _subs.push(v2)
|
|
39
|
+
}
|
|
40
|
+
if (_subs.length === 0) return null
|
|
41
|
+
return { key, subs: _subs }
|
|
42
|
+
}
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const field_blocks = (key, val = true) => {
|
|
47
|
+
if (includes(key, ["id", "timestamp", "height", "previous"])) {
|
|
48
|
+
return { key }
|
|
49
|
+
}
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function applyFieldProjection(tx, opt) {
|
|
54
|
+
if (isNil(opt.fields)) return tx
|
|
55
|
+
|
|
56
|
+
let _tx2 = { cursor: tx.cursor }
|
|
57
|
+
let fields = []
|
|
58
|
+
if (is(Array, opt.fields) && opt.fields.length > 0) {
|
|
59
|
+
for (const v of opt.fields) {
|
|
60
|
+
if (is(String, v)) {
|
|
61
|
+
const fld = field(v)
|
|
62
|
+
if (fld) fields.push(fld)
|
|
63
|
+
} else if (is(Object, v) && !is(Array, v)) {
|
|
64
|
+
for (const k in v) {
|
|
65
|
+
const fld = field(k, v[k])
|
|
66
|
+
if (fld) fields.push(fld)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} else if (is(Object, opt.fields) && !is(Array, opt.fields)) {
|
|
71
|
+
for (const k in opt.fields) {
|
|
72
|
+
const fld = field(k, opt.fields[k])
|
|
73
|
+
if (fld) fields.push(fld)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const f of fields) {
|
|
77
|
+
if (isNil(f.subs)) {
|
|
78
|
+
_tx2[f.key] = tx[f.key] ?? ""
|
|
79
|
+
} else {
|
|
80
|
+
_tx2[f.key] = {}
|
|
81
|
+
if (is(Array, tx[f.key])) {
|
|
82
|
+
_tx2[f.key] = map(pick(f.subs))(tx[f.key])
|
|
83
|
+
} else {
|
|
84
|
+
for (const f2 of f.subs) {
|
|
85
|
+
_tx2[f.key][f2] = tx[f.key]?.[f2] ?? ""
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return _tx2
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default class D1GQL {
|
|
94
|
+
constructor({ d1, mem }) {
|
|
95
|
+
this.d1 = d1
|
|
96
|
+
this.mem = mem
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async txs(opt = {}) {
|
|
100
|
+
let _block = {}
|
|
101
|
+
if (is(Number, opt.block)) {
|
|
102
|
+
_block = { min: opt.block, max: opt.block }
|
|
103
|
+
} else if (is(Array, opt.block) && opt.block.length > 0) {
|
|
104
|
+
_block = {}
|
|
105
|
+
if (!isNil(opt.block[0])) _block.min = opt.block[0]
|
|
106
|
+
if (!isNil(opt.block[1])) _block.max = opt.block[1]
|
|
107
|
+
} else if (is(Object, opt.block)) {
|
|
108
|
+
if (!isNil(opt.block.min)) _block.min = opt.block.min
|
|
109
|
+
if (!isNil(opt.block.max)) _block.max = opt.block.max
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const first = opt.first ?? 10
|
|
113
|
+
let tags = []
|
|
114
|
+
for (const k in opt.tags ?? {}) {
|
|
115
|
+
if (is(String, opt.tags[k])) {
|
|
116
|
+
tags.push({ name: k, values: [opt.tags[k]] })
|
|
117
|
+
} else if (is(Array, opt.tags[k])) {
|
|
118
|
+
tags.push({ name: k, values: opt.tags[k] })
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let recipients = null
|
|
123
|
+
if (is(Array, opt.recipients)) recipients = opt.recipients
|
|
124
|
+
else if (is(String, opt.recipient)) recipients = [opt.recipient]
|
|
125
|
+
|
|
126
|
+
let owners = null
|
|
127
|
+
if (is(Array, opt.owners)) owners = opt.owners
|
|
128
|
+
else if (is(String, opt.owner)) owners = [opt.owner]
|
|
129
|
+
|
|
130
|
+
let ids = null
|
|
131
|
+
if (is(Array, opt.ids)) ids = opt.ids
|
|
132
|
+
else if (is(String, opt.id)) ids = [opt.id]
|
|
133
|
+
|
|
134
|
+
// Build SQL query
|
|
135
|
+
const conditions = []
|
|
136
|
+
const params = []
|
|
137
|
+
|
|
138
|
+
if (!isNil(_block.min)) {
|
|
139
|
+
conditions.push("t.block_height >= ?")
|
|
140
|
+
params.push(_block.min)
|
|
141
|
+
}
|
|
142
|
+
if (!isNil(_block.max)) {
|
|
143
|
+
conditions.push("t.block_height <= ?")
|
|
144
|
+
params.push(_block.max)
|
|
145
|
+
}
|
|
146
|
+
if (ids && ids.length > 0) {
|
|
147
|
+
conditions.push(`t.id IN (${ids.map(() => "?").join(",")})`)
|
|
148
|
+
params.push(...ids)
|
|
149
|
+
}
|
|
150
|
+
if (owners && owners.length > 0) {
|
|
151
|
+
conditions.push(`t.owner IN (${owners.map(() => "?").join(",")})`)
|
|
152
|
+
params.push(...owners)
|
|
153
|
+
}
|
|
154
|
+
if (recipients && recipients.length > 0) {
|
|
155
|
+
conditions.push(`t.recipient IN (${recipients.map(() => "?").join(",")})`)
|
|
156
|
+
params.push(...recipients)
|
|
157
|
+
}
|
|
158
|
+
if (!isNil(opt.bundledIn) && opt.bundledIn.length > 0) {
|
|
159
|
+
conditions.push(`t.bundle_id IN (${opt.bundledIn.map(() => "?").join(",")})`)
|
|
160
|
+
params.push(...opt.bundledIn)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Tag filters — each tag filter requires ALL to match (AND)
|
|
164
|
+
for (const tag of tags) {
|
|
165
|
+
const valuePlaceholders = tag.values.map(() => "?").join(",")
|
|
166
|
+
conditions.push(
|
|
167
|
+
`t.id IN (SELECT tx_id FROM tx_tags WHERE name = ? AND value IN (${valuePlaceholders}))`
|
|
168
|
+
)
|
|
169
|
+
params.push(tag.name, ...tag.values)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// After cursor (pagination)
|
|
173
|
+
if (!isNil(opt.after)) {
|
|
174
|
+
// Get the block_height of the cursor tx to paginate correctly
|
|
175
|
+
const cursorRow = await this.d1
|
|
176
|
+
.prepare("SELECT block_height FROM txs WHERE id = ?")
|
|
177
|
+
.bind(opt.after)
|
|
178
|
+
.first()
|
|
179
|
+
if (cursorRow) {
|
|
180
|
+
if (opt.asc === true) {
|
|
181
|
+
conditions.push("(t.block_height > ? OR (t.block_height = ? AND t.id > ?))")
|
|
182
|
+
params.push(cursorRow.block_height, cursorRow.block_height, opt.after)
|
|
183
|
+
} else {
|
|
184
|
+
conditions.push("(t.block_height < ? OR (t.block_height = ? AND t.id < ?))")
|
|
185
|
+
params.push(cursorRow.block_height, cursorRow.block_height, opt.after)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""
|
|
191
|
+
const order = opt.asc === true ? "ASC" : "DESC"
|
|
192
|
+
|
|
193
|
+
const sql = `
|
|
194
|
+
SELECT t.id, t.block_id, t.block_height, t.owner, t.recipient,
|
|
195
|
+
t.anchor, t.signature, t.data_size, t.data_type, t.bundle_id, t.parent_id,
|
|
196
|
+
b.height, b.timestamp, b.previous
|
|
197
|
+
FROM txs t
|
|
198
|
+
JOIN blocks b ON t.block_id = b.id
|
|
199
|
+
${where}
|
|
200
|
+
ORDER BY t.block_height ${order}, t.id ${order}
|
|
201
|
+
LIMIT ?
|
|
202
|
+
`
|
|
203
|
+
params.push(first)
|
|
204
|
+
|
|
205
|
+
const { results: rows } = await this.d1
|
|
206
|
+
.prepare(sql)
|
|
207
|
+
.bind(...params)
|
|
208
|
+
.all()
|
|
209
|
+
|
|
210
|
+
// Fetch tags for all result txs in one query
|
|
211
|
+
const txIds = rows.map(r => r.id)
|
|
212
|
+
let tagMap = {}
|
|
213
|
+
if (txIds.length > 0) {
|
|
214
|
+
const tagSql = `SELECT tx_id, name, value FROM tx_tags WHERE tx_id IN (${txIds.map(() => "?").join(",")})`
|
|
215
|
+
const { results: tagRows } = await this.d1
|
|
216
|
+
.prepare(tagSql)
|
|
217
|
+
.bind(...txIds)
|
|
218
|
+
.all()
|
|
219
|
+
for (const t of tagRows) {
|
|
220
|
+
tagMap[t.tx_id] ??= []
|
|
221
|
+
tagMap[t.tx_id].push({ name: t.name, value: t.value })
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Fetch addrmap for owners
|
|
226
|
+
const ownerAddrs = [...new Set(rows.map(r => r.owner).filter(Boolean))]
|
|
227
|
+
let addrMap = {}
|
|
228
|
+
if (ownerAddrs.length > 0) {
|
|
229
|
+
const addrSql = `SELECT address, key FROM addrmap WHERE address IN (${ownerAddrs.map(() => "?").join(",")})`
|
|
230
|
+
const { results: addrRows } = await this.d1
|
|
231
|
+
.prepare(addrSql)
|
|
232
|
+
.bind(...ownerAddrs)
|
|
233
|
+
.all()
|
|
234
|
+
for (const a of addrRows) {
|
|
235
|
+
addrMap[a.address] = { address: a.address, key: a.key }
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const data = rows.map(row => {
|
|
240
|
+
let _tx = {
|
|
241
|
+
cursor: row.id,
|
|
242
|
+
id: row.id,
|
|
243
|
+
recipient: row.recipient || "",
|
|
244
|
+
tags: tagMap[row.id] || [],
|
|
245
|
+
parent: row.parent_id ? { id: row.parent_id } : { id: "" },
|
|
246
|
+
bundledIn: row.bundle_id ? { id: row.bundle_id } : { id: "" },
|
|
247
|
+
block: {
|
|
248
|
+
id: row.block_id,
|
|
249
|
+
timestamp: row.timestamp,
|
|
250
|
+
height: row.height,
|
|
251
|
+
previous: row.previous || "",
|
|
252
|
+
},
|
|
253
|
+
anchor: row.anchor || "",
|
|
254
|
+
signature: row.signature || "",
|
|
255
|
+
owner: addrMap[row.owner] || { address: row.owner, key: "" },
|
|
256
|
+
fee: { ar: "0.000000000000", winston: "0" },
|
|
257
|
+
quantity: { ar: "0.000000000000", winston: "0" },
|
|
258
|
+
data: { size: row.data_size || "0", type: row.data_type || "" },
|
|
259
|
+
}
|
|
260
|
+
return applyFieldProjection(_tx, opt)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
if (opt.next === true) {
|
|
264
|
+
let cursor = null
|
|
265
|
+
if (data.length > 0) cursor = last(data).cursor
|
|
266
|
+
const next = !cursor
|
|
267
|
+
? null
|
|
268
|
+
: async () => {
|
|
269
|
+
let _opt = clone(opt)
|
|
270
|
+
_opt.after = cursor
|
|
271
|
+
return await this.txs(_opt)
|
|
272
|
+
}
|
|
273
|
+
return { data, next }
|
|
274
|
+
}
|
|
275
|
+
return data
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async blocks(opt = {}) {
|
|
279
|
+
let _block = {}
|
|
280
|
+
if (is(Number, opt.block)) {
|
|
281
|
+
_block = { min: opt.block, max: opt.block }
|
|
282
|
+
} else if (is(Array, opt.block) && opt.block.length > 0) {
|
|
283
|
+
_block = {}
|
|
284
|
+
if (!isNil(opt.block[0])) _block.min = opt.block[0]
|
|
285
|
+
if (!isNil(opt.block[1])) _block.max = opt.block[1]
|
|
286
|
+
}
|
|
287
|
+
const first = opt.first ?? 20
|
|
288
|
+
|
|
289
|
+
let ids = null
|
|
290
|
+
if (is(Array, opt.ids)) ids = opt.ids
|
|
291
|
+
else if (is(String, opt.id)) ids = [opt.id]
|
|
292
|
+
|
|
293
|
+
const conditions = []
|
|
294
|
+
const params = []
|
|
295
|
+
|
|
296
|
+
if (!isNil(_block.min)) {
|
|
297
|
+
conditions.push("height >= ?")
|
|
298
|
+
params.push(_block.min)
|
|
299
|
+
}
|
|
300
|
+
if (!isNil(_block.max)) {
|
|
301
|
+
conditions.push("height <= ?")
|
|
302
|
+
params.push(_block.max)
|
|
303
|
+
}
|
|
304
|
+
if (ids && ids.length > 0) {
|
|
305
|
+
conditions.push(`id IN (${ids.map(() => "?").join(",")})`)
|
|
306
|
+
params.push(...ids)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!isNil(opt.after)) {
|
|
310
|
+
const cursorRow = await this.d1
|
|
311
|
+
.prepare("SELECT height FROM blocks WHERE id = ?")
|
|
312
|
+
.bind(opt.after)
|
|
313
|
+
.first()
|
|
314
|
+
if (cursorRow) {
|
|
315
|
+
if (opt.asc === true) {
|
|
316
|
+
conditions.push("height > ?")
|
|
317
|
+
params.push(cursorRow.height)
|
|
318
|
+
} else {
|
|
319
|
+
conditions.push("height < ?")
|
|
320
|
+
params.push(cursorRow.height)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""
|
|
326
|
+
const order = opt.asc === true ? "ASC" : "DESC"
|
|
327
|
+
|
|
328
|
+
const sql = `
|
|
329
|
+
SELECT id, height, timestamp, previous
|
|
330
|
+
FROM blocks
|
|
331
|
+
${where}
|
|
332
|
+
ORDER BY height ${order}
|
|
333
|
+
LIMIT ?
|
|
334
|
+
`
|
|
335
|
+
params.push(first)
|
|
336
|
+
|
|
337
|
+
const { results: rows } = await this.d1
|
|
338
|
+
.prepare(sql)
|
|
339
|
+
.bind(...params)
|
|
340
|
+
.all()
|
|
341
|
+
|
|
342
|
+
let data = rows.map(row => {
|
|
343
|
+
let block = {
|
|
344
|
+
cursor: row.id,
|
|
345
|
+
id: row.id,
|
|
346
|
+
height: row.height,
|
|
347
|
+
timestamp: row.timestamp,
|
|
348
|
+
previous: row.previous || "",
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Apply field projection
|
|
352
|
+
if (!isNil(opt.fields)) {
|
|
353
|
+
let block2 = { cursor: block.id }
|
|
354
|
+
let fields = []
|
|
355
|
+
if (is(Array, opt.fields) && opt.fields.length > 0) {
|
|
356
|
+
for (const v of opt.fields) {
|
|
357
|
+
if (is(String, v)) {
|
|
358
|
+
const fld = field_blocks(v)
|
|
359
|
+
if (fld) fields.push(fld)
|
|
360
|
+
} else if (is(Object, v) && !is(Array, v)) {
|
|
361
|
+
for (const k in v) {
|
|
362
|
+
const fld = field_blocks(k, v[k])
|
|
363
|
+
if (fld) fields.push(fld)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
} else if (is(Object, opt.fields) && !is(Array, opt.fields)) {
|
|
368
|
+
for (const k in opt.fields) {
|
|
369
|
+
const fld = field_blocks(k, opt.fields[k])
|
|
370
|
+
if (fld) fields.push(fld)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
for (const f of fields) {
|
|
374
|
+
if (isNil(f.subs)) {
|
|
375
|
+
block2[f.key] = block[f.key] ?? ""
|
|
376
|
+
} else {
|
|
377
|
+
block2[f.key] = {}
|
|
378
|
+
if (is(Array, block[f.key])) {
|
|
379
|
+
block2[f.key] = map(pick(f.subs))(block[f.key])
|
|
380
|
+
} else {
|
|
381
|
+
for (const f2 of f.subs) {
|
|
382
|
+
block2[f.key][f2] = block[f.key]?.[f2] ?? ""
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
block = block2
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return block
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
if (opt.next === true) {
|
|
394
|
+
let cursor = null
|
|
395
|
+
if (data.length > 0) cursor = last(data).cursor
|
|
396
|
+
const next = !cursor
|
|
397
|
+
? null
|
|
398
|
+
: async () => {
|
|
399
|
+
let _opt = clone(opt)
|
|
400
|
+
_opt.after = cursor
|
|
401
|
+
return await this.txs(_opt)
|
|
402
|
+
}
|
|
403
|
+
return { data, next }
|
|
404
|
+
}
|
|
405
|
+
return data
|
|
406
|
+
}
|
|
407
|
+
}
|
package/esm/tgql.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import D1GQL from "./tgql-d1.js"
|
|
1
2
|
import { reverse, includes, map, is, isNil, last, clone, pick } from "ramda"
|
|
2
3
|
|
|
3
4
|
const subs = {
|
|
@@ -48,10 +49,20 @@ const field_blocks = (key, val = true) => {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
export default class GQL {
|
|
51
|
-
constructor({ mem }) {
|
|
52
|
+
constructor({ mem, d1 }) {
|
|
52
53
|
this.mem = mem
|
|
54
|
+
this.d1 = d1 || null
|
|
55
|
+
if (this.d1) {
|
|
56
|
+
this._d1gql = new D1GQL({ d1: this.d1, mem: this.mem })
|
|
57
|
+
}
|
|
53
58
|
}
|
|
54
59
|
async txs(opt = {}) {
|
|
60
|
+
if (this._d1gql) {
|
|
61
|
+
try { return await this._d1gql.txs(opt) } catch (e) {
|
|
62
|
+
// Fall through to O(n) scan only if D1 schema is missing
|
|
63
|
+
if (!e?.message?.includes("no such table")) throw e
|
|
64
|
+
}
|
|
65
|
+
}
|
|
55
66
|
let block_max = null
|
|
56
67
|
let block_min = null
|
|
57
68
|
let _block = {}
|
|
@@ -91,7 +102,7 @@ export default class GQL {
|
|
|
91
102
|
let count = 0
|
|
92
103
|
let after = false
|
|
93
104
|
for (const v of blocks) {
|
|
94
|
-
const block = clone(this.mem.blockmap
|
|
105
|
+
const block = clone(await this.mem.get("blockmap", v))
|
|
95
106
|
if (!isNil(_block.min) && block.height < _block.min) continue
|
|
96
107
|
if (!isNil(_block.max) && block.height > _block.max) continue
|
|
97
108
|
let txs = block.txs
|
|
@@ -103,6 +114,7 @@ export default class GQL {
|
|
|
103
114
|
}
|
|
104
115
|
if (!isNil(opt.after) && count === 0 && !after) continue
|
|
105
116
|
let tx = await this.mem.getTx(v2)
|
|
117
|
+
if (!tx) continue
|
|
106
118
|
if (!isNil(ids) && ids.length > 0) {
|
|
107
119
|
if (!includes(tx.id, ids)) continue
|
|
108
120
|
}
|
|
@@ -113,10 +125,13 @@ export default class GQL {
|
|
|
113
125
|
if (!isNil(recipients) && recipients.length > 0) {
|
|
114
126
|
if (!includes(tx.recipient, recipients)) continue
|
|
115
127
|
}
|
|
128
|
+
if (!isNil(opt.bundledIn) && opt.bundledIn.length > 0) {
|
|
129
|
+
if (!tx.bundledIn?.id || !includes(tx.bundledIn.id, opt.bundledIn)) continue
|
|
130
|
+
}
|
|
116
131
|
let tag_unmatch = false
|
|
117
132
|
for (const v of tags) {
|
|
118
133
|
let ex = false
|
|
119
|
-
for (const v2 of tx.tags) {
|
|
134
|
+
for (const v2 of (tx.tags || [])) {
|
|
120
135
|
if (v2.name === v.name && includes(v2.value, v.values)) {
|
|
121
136
|
ex = true
|
|
122
137
|
break
|
|
@@ -132,17 +147,16 @@ export default class GQL {
|
|
|
132
147
|
cursor: tx.id,
|
|
133
148
|
id: tx.id,
|
|
134
149
|
recipient: tx.recipient ?? "",
|
|
135
|
-
|
|
136
|
-
tags: tx.tags,
|
|
150
|
+
tags: tx.tags || [],
|
|
137
151
|
parent: tx.parent ?? { id: "" },
|
|
138
152
|
bundledIn: tx.bundledIn ?? { id: "" },
|
|
139
153
|
block: pick(["id", "timestamp", "height", "previous"], block),
|
|
140
154
|
anchor: tx.anchor ?? "",
|
|
141
155
|
signature: tx.signature ?? "",
|
|
142
|
-
owner: { address: tx.owner, key: this.mem.addrmap
|
|
156
|
+
owner: { address: tx.owner, key: await this.mem.get("addrmap", tx.owner) },
|
|
143
157
|
fee: { ar: "0.000000000000", winston: "0" },
|
|
144
158
|
quantity: { ar: "0.000000000000", winston: "0" },
|
|
145
|
-
data: { size: "0", type: "" },
|
|
159
|
+
data: tx._data?.type ? tx._data : { size: tx._data?.size || tx.data_size || (tx.data ? String(tx.data.length) : "0"), type: (tx.tags || []).find(t => t.name === "Content-Type")?.value || "" },
|
|
146
160
|
}
|
|
147
161
|
if (!isNil(opt.fields)) {
|
|
148
162
|
let _tx2 = { cursor: tx.id }
|
|
@@ -173,7 +187,7 @@ export default class GQL {
|
|
|
173
187
|
_tx2[f.key] = {}
|
|
174
188
|
if (is(Array, _tx[f.key])) {
|
|
175
189
|
_tx2[f.key] = map(pick(f.subs))(_tx[f.key])
|
|
176
|
-
} else {
|
|
190
|
+
} else if (_tx[f.key] != null) {
|
|
177
191
|
for (const f2 of f.subs) {
|
|
178
192
|
_tx2[f.key][f2] = _tx[f.key][f2] ?? ""
|
|
179
193
|
}
|
|
@@ -204,6 +218,11 @@ export default class GQL {
|
|
|
204
218
|
}
|
|
205
219
|
}
|
|
206
220
|
async blocks(opt = {}) {
|
|
221
|
+
if (this._d1gql) {
|
|
222
|
+
try { return await this._d1gql.blocks(opt) } catch (e) {
|
|
223
|
+
if (!e?.message?.includes("no such table")) throw e
|
|
224
|
+
}
|
|
225
|
+
}
|
|
207
226
|
let block_max = null
|
|
208
227
|
let block_min = null
|
|
209
228
|
let _block = {}
|
|
@@ -226,7 +245,7 @@ export default class GQL {
|
|
|
226
245
|
let count = 0
|
|
227
246
|
let after = false
|
|
228
247
|
for (const v of blocks) {
|
|
229
|
-
let block = clone({ ...this.mem.blockmap
|
|
248
|
+
let block = clone({ ...(await this.mem.get("blockmap", v)), cursor: v })
|
|
230
249
|
delete block.txs
|
|
231
250
|
if (!isNil(_block.min) && block.height < _block.min) continue
|
|
232
251
|
if (!isNil(_block.max) && block.height > _block.max) continue
|
|
@@ -260,7 +279,7 @@ export default class GQL {
|
|
|
260
279
|
block2[f.key] = {}
|
|
261
280
|
if (is(Array, block[f.key])) {
|
|
262
281
|
block2[f.key] = map(pick(f.subs))(block[f.key])
|
|
263
|
-
} else {
|
|
282
|
+
} else if (block[f.key] != null) {
|
|
264
283
|
for (const f2 of f.subs) {
|
|
265
284
|
block2[f.key][f2] = block[f.key][f2] ?? ""
|
|
266
285
|
}
|
|
@@ -29,7 +29,7 @@ yarn test # all tests
|
|
|
29
29
|
yarn test test/aos.test.js # specific file
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
The test command runs: `node --
|
|
32
|
+
The test command runs: `node --test --test-concurrency=1` (Node 24+ enables wasm-memory64 by default and rejects the experimental flag; Node 22 users add `--experimental-wasm-memory64` via NODE_OPTIONS).
|
|
33
33
|
|
|
34
34
|
## Systematic Debugging
|
|
35
35
|
|
|
@@ -59,7 +59,7 @@ Report test results with specifics: which tests pass, which fail, and the error
|
|
|
59
59
|
## Common Issues
|
|
60
60
|
|
|
61
61
|
- **Port already in use**: Kill stale `beam.smp` processes
|
|
62
|
-
- **WASM memory error**:
|
|
62
|
+
- **WASM memory error**: On Node 22 set `NODE_OPTIONS=--experimental-wasm-memory64`. On Node 24+ the flag is default-on (and rejected if passed explicitly — "bad option: --experimental-wasm-memory64")
|
|
63
63
|
- **Process not found**: Check that `src_data` path is correct
|
|
64
64
|
- **HyperBEAM timeout**: Ensure `hbeam.kill()` is called in `after()`
|
|
65
65
|
- **Send().receive() hangs**: Does NOT work on genesis-wasm — use fire-and-forget `Send()` + separate Handlers.add calls
|
|
@@ -210,5 +210,5 @@ Never force a task to `done` status with failing tests. The TaskCompleted hook w
|
|
|
210
210
|
- Verify task statuses — a task stuck as `in_progress` blocks progression
|
|
211
211
|
|
|
212
212
|
### Permission denied on yarn test
|
|
213
|
-
- Check `node --version` is
|
|
214
|
-
-
|
|
213
|
+
- Check `node --version` is 22+ (24+ recommended)
|
|
214
|
+
- On Node 22, set `NODE_OPTIONS=--experimental-wasm-memory64`; on Node 24+ the flag is default-on and is rejected if passed explicitly
|
|
@@ -51,5 +51,6 @@ yarn test
|
|
|
51
51
|
- Check `package.json` has `"wao"` in dependencies
|
|
52
52
|
|
|
53
53
|
### WASM memory error
|
|
54
|
-
-
|
|
55
|
-
- Node
|
|
54
|
+
- Node 24+ enables wasm-memory64 by default — DO NOT pass `--experimental-wasm-memory64` (it's rejected with "bad option")
|
|
55
|
+
- Node 22: set `NODE_OPTIONS=--experimental-wasm-memory64` before running
|
|
56
|
+
- Node.js 22+ required
|
package/esm/workspace/CLAUDE.md
CHANGED
|
@@ -95,7 +95,7 @@ yarn deploy --mainnet # remote HyperBEAM (push-1)
|
|
|
95
95
|
yarn deploy --mainnet --lua # remote HyperBEAM (Lua mode)
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
Runs: `node --
|
|
98
|
+
Runs: `node --test --test-concurrency=1` (Node 24+; on Node 22 prefix with `--experimental-wasm-memory64`)
|
|
99
99
|
|
|
100
100
|
## Deployment Targets
|
|
101
101
|
|
|
@@ -109,7 +109,7 @@ Runs: `node --experimental-wasm-memory64 --test --test-concurrency=1`
|
|
|
109
109
|
- **Remote nodes**: Use `push-1` through `push-10` for full compute. `push.forward.computer` is push-only (no compute).
|
|
110
110
|
- **Lua mode**: Faster but no `receive()` — use `msg.reply()` pattern instead.
|
|
111
111
|
- **Wallet**: Run `yarn keygen` to generate `.wallet.json`.
|
|
112
|
-
- **HyperBEAM fork**: `git clone -b wao-
|
|
112
|
+
- **HyperBEAM fork**: `git clone -b wao-final https://github.com/weavedb/HyperBEAM.git`
|
|
113
113
|
|
|
114
114
|
### Frontend Commands
|
|
115
115
|
|
package/esm/workspace/README.md
CHANGED
|
@@ -52,7 +52,7 @@ yarn deploy --local-hb --lua src/counter.lua
|
|
|
52
52
|
|
|
53
53
|
**Remote nodes**: Use `push-1` through `push-10` for full compute. `push.forward.computer` is push-only (no compute).
|
|
54
54
|
|
|
55
|
-
**HyperBEAM fork**: `git clone -b wao-
|
|
55
|
+
**HyperBEAM fork**: `git clone -b wao-final https://github.com/weavedb/HyperBEAM.git && cd HyperBEAM && rebar3 compile`
|
|
56
56
|
|
|
57
57
|
---
|
|
58
58
|
|
|
@@ -144,13 +144,17 @@ await hb.schedule({
|
|
|
144
144
|
|
|
145
145
|
**Problem:** `WebAssembly.Memory` errors or WASM module fails to load.
|
|
146
146
|
|
|
147
|
-
**Fix:**
|
|
147
|
+
**Fix:** Node 24+ enables wasm-memory64 by default (and rejects the experimental flag at startup with "bad option"). On Node 22 or older, prefix with `--experimental-wasm-memory64`:
|
|
148
148
|
|
|
149
149
|
```bash
|
|
150
|
+
# Node 24+: no flag needed
|
|
151
|
+
node --test test/aos.test.js
|
|
152
|
+
|
|
153
|
+
# Node 22:
|
|
150
154
|
node --experimental-wasm-memory64 --test test/aos.test.js
|
|
151
155
|
```
|
|
152
156
|
|
|
153
|
-
The `yarn test` script
|
|
157
|
+
The `yarn test` script ships without the flag (Node 24+ default); set `NODE_OPTIONS=--experimental-wasm-memory64` if you're on older Node.
|
|
154
158
|
|
|
155
159
|
### Genesis-WASM Server
|
|
156
160
|
|
|
@@ -202,7 +206,8 @@ The `yarn test` script should already include this flag.
|
|
|
202
206
|
| `badarg in list_to_atom` | Atom not pre-registered | Add to preRegisterAtoms |
|
|
203
207
|
| `multiple_matches` | Duplicate message content | Add nonce to messages |
|
|
204
208
|
| `timeout` | Process or server hung | Kill stale processes, increase timeout |
|
|
205
|
-
| `WASM memory error` | Missing memory64
|
|
209
|
+
| `WASM memory error` | Missing memory64 (Node 22) | Set `NODE_OPTIONS=--experimental-wasm-memory64` (Node 22) — Node 24+ has it default-on |
|
|
210
|
+
| `bad option: --experimental-wasm-memory64` | Passing the flag on Node 24+ | Remove the flag — Node 24+ rejects it |
|
|
206
211
|
| `Process not found` | Bad pid or deploy failed | Check deploy result, verify pid |
|
|
207
212
|
| `Insufficient balance` | Payment balance too low | Top up via simple-pay or p4 |
|
|
208
213
|
| `Not signed` | Missing wallet/JWK | Call `.init(jwk)` before operations |
|
|
@@ -215,7 +220,7 @@ When tests fail, check in order:
|
|
|
215
220
|
|
|
216
221
|
1. **Ports clear?** `lsof -ti :10000-10010 | xargs -r kill -9`
|
|
217
222
|
2. **beam.smp killed?** `pkill -f beam.smp`
|
|
218
|
-
3. **WASM flag?** `--experimental-wasm-memory64`
|
|
223
|
+
3. **WASM flag?** Node 24+ default-on; on Node 22 add `--experimental-wasm-memory64` (and on Node 24+ remove it — it's rejected)
|
|
219
224
|
4. **HyperBEAM dir?** `ls ./HyperBEAM` exists
|
|
220
225
|
5. **Wallet exists?** `.wallet.json` present (auto-generated)
|
|
221
226
|
6. **hbeam.kill() in after()?** Always clean up
|
|
@@ -348,7 +348,7 @@ const result = await hb.computeLegacy({ pid, slot })
|
|
|
348
348
|
**Limitations:**
|
|
349
349
|
- External CU is single-pass — **`Send().receive()` does NOT work**
|
|
350
350
|
- Auto-starts CU server at port 6363
|
|
351
|
-
- Requires `--experimental-wasm-memory64`
|
|
351
|
+
- Requires wasm-memory64 (Node 24+ default; Node 22 needs `--experimental-wasm-memory64`)
|
|
352
352
|
|
|
353
353
|
Functions: `init/3`, `compute/3`, `snapshot/3`, `import/3`
|
|
354
354
|
|
|
@@ -540,6 +540,55 @@ Query Arweave via GraphQL from within HyperBEAM.
|
|
|
540
540
|
|
|
541
541
|
---
|
|
542
542
|
|
|
543
|
+
## v0.9-FINAL Devices (new since beta3)
|
|
544
|
+
|
|
545
|
+
### location@1.0 — Scheduler-Location Registry
|
|
546
|
+
|
|
547
|
+
Replaces the old `/~scheduler@1.0/location` endpoint. Stores per-address scheduler-location records (with DNS/IP resolution and a TTL) in a node-local cache, with fallback to the Arweave gateway.
|
|
548
|
+
|
|
549
|
+
- `POST /~location@1.0/node` — operator generates and registers a record (signed)
|
|
550
|
+
- `POST /~location@1.0/known` — cache a peer's record if newer than the local one
|
|
551
|
+
- Read via `dev_whois` / `dev_location_cache`
|
|
552
|
+
|
|
553
|
+
### metering@1.0 — Dynamic P4 Pricing
|
|
554
|
+
|
|
555
|
+
A P4 pricing device that records resource usage per request/response lifecycle.
|
|
556
|
+
|
|
557
|
+
- `estimate/3` opens a process-local session
|
|
558
|
+
- `consume/3` increments usage during the session
|
|
559
|
+
- `price/3` closes the session and returns the integer charge
|
|
560
|
+
- Operator sets `metering-rates` in node message (resource → AO units per resource unit)
|
|
561
|
+
|
|
562
|
+
### blacklist@1.0 — Content Moderation by Blacklist
|
|
563
|
+
|
|
564
|
+
A request hook for filtering. Operator configures `blacklist-providers` (list) in node message; each provider returns a moderation set.
|
|
565
|
+
|
|
566
|
+
### bundler@1.0 — ANS-104 Bundler Integration
|
|
567
|
+
|
|
568
|
+
Outbound bundler for posting ANS-104 transactions to upstream Arweave bundlers (e.g. `up.arweave.net`).
|
|
569
|
+
|
|
570
|
+
### gzip@1.0 — gzip Codec
|
|
571
|
+
|
|
572
|
+
Compress/decompress message bodies via gzip during processing.
|
|
573
|
+
|
|
574
|
+
### rate-limit@1.0 — Per-Address Rate Limiting
|
|
575
|
+
|
|
576
|
+
Operator-configurable per-address rate limits applied as a request hook.
|
|
577
|
+
|
|
578
|
+
### tx@1.0 — Arweave TX Helpers
|
|
579
|
+
|
|
580
|
+
Helpers for fetching and validating Arweave transactions inline.
|
|
581
|
+
|
|
582
|
+
### b32-name@1.0 — Base-32 Name Encoding
|
|
583
|
+
|
|
584
|
+
Helpers for base-32 name encoding/decoding used by other devices.
|
|
585
|
+
|
|
586
|
+
### arweave@2.9 — Arweave Block + GraphQL Integration
|
|
587
|
+
|
|
588
|
+
Renamed in v0.9-FINAL (was `arweave@2.9-pre`). Provides Arweave block cache, offset helpers, and common Arweave protocol primitives.
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
543
592
|
## Multi-Instance Setup
|
|
544
593
|
|
|
545
594
|
Running multiple HyperBEAM nodes requires unique ports and Erlang node names:
|