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/armem-base.js
CHANGED
|
@@ -5,6 +5,7 @@ import { compress, decompress } from "./compress.js"
|
|
|
5
5
|
import { last, assoc, is, isNil } from "ramda"
|
|
6
6
|
import { buildTags, tags } from "./utils.js"
|
|
7
7
|
import base64url from "base64url"
|
|
8
|
+
import RemoteAR from "./ar-remote.js"
|
|
8
9
|
|
|
9
10
|
function eq(buf1, buf2, chunkSize = 1024 * 1024) {
|
|
10
11
|
if (buf1.byteLength !== buf2.byteLength) return false
|
|
@@ -33,6 +34,7 @@ export default class ArMemBase {
|
|
|
33
34
|
init,
|
|
34
35
|
Waosm,
|
|
35
36
|
variant,
|
|
37
|
+
ar_url,
|
|
36
38
|
} = {}) {
|
|
37
39
|
this.variant = variant
|
|
38
40
|
this.__type__ = "mem"
|
|
@@ -46,6 +48,10 @@ export default class ArMemBase {
|
|
|
46
48
|
this.arweave.transactions.getPrice = () => 0
|
|
47
49
|
this.scheduler = scheduler
|
|
48
50
|
this.SU_URL = SU_URL
|
|
51
|
+
this._txCache = new Map()
|
|
52
|
+
this._TX_CACHE_MAX = 200
|
|
53
|
+
this.ar_url = ar_url || null
|
|
54
|
+
this._remote = ar_url ? new RemoteAR(ar_url) : null
|
|
49
55
|
}
|
|
50
56
|
compress(memory) {
|
|
51
57
|
const waosm = new this.Waosm()
|
|
@@ -62,13 +68,65 @@ export default class ArMemBase {
|
|
|
62
68
|
)
|
|
63
69
|
}
|
|
64
70
|
getAnchor() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
if (this._remote) {
|
|
72
|
+
return this._remoteAnchor ?? "Do_Uc2Sju_ffp6Ev0AnLVdPtot15rvMjP-a9VVaA5fM"
|
|
73
|
+
}
|
|
74
|
+
// D1 ready: use lastBlockId scalar (block.id === last tx.id)
|
|
75
|
+
if (this._d1Ready) {
|
|
76
|
+
return this.lastBlockId || "Do_Uc2Sju_ffp6Ev0AnLVdPtot15rvMjP-a9VVaA5fM"
|
|
77
|
+
}
|
|
78
|
+
// Fallback: use blocks array
|
|
79
|
+
if (this.blocks.length === 0) {
|
|
80
|
+
return "Do_Uc2Sju_ffp6Ev0AnLVdPtot15rvMjP-a9VVaA5fM"
|
|
81
|
+
}
|
|
82
|
+
const lastBlock = this.blockmap[last(this.blocks)]
|
|
83
|
+
if (!lastBlock) return "Do_Uc2Sju_ffp6Ev0AnLVdPtot15rvMjP-a9VVaA5fM"
|
|
84
|
+
return last(lastBlock.txs)
|
|
68
85
|
}
|
|
69
86
|
async get(key, field) {
|
|
70
87
|
await this.init()
|
|
71
|
-
if (!field)
|
|
88
|
+
if (!field) {
|
|
89
|
+
// Remote AR fallback for height
|
|
90
|
+
if (key === "height" && this._remote && !this[key]) {
|
|
91
|
+
this[key] = await this._remote.getHeight()
|
|
92
|
+
}
|
|
93
|
+
return this[key]
|
|
94
|
+
}
|
|
95
|
+
// Lazy load from storage if not in memory
|
|
96
|
+
if (this[key]?.[field] === undefined && this.db) {
|
|
97
|
+
const val = await this.db.get(`${key}.${field}`)
|
|
98
|
+
if (val !== null) {
|
|
99
|
+
this[key] ??= {}
|
|
100
|
+
if (key === "env" && val) {
|
|
101
|
+
// Try loading memory from R2 first
|
|
102
|
+
if (this.db.r2GetMemory && !val.memory) {
|
|
103
|
+
try {
|
|
104
|
+
const r2mem = await this.db.r2GetMemory(field)
|
|
105
|
+
if (r2mem) {
|
|
106
|
+
val.memory = r2mem
|
|
107
|
+
val.compressed = true
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {}
|
|
110
|
+
}
|
|
111
|
+
// Fallback: memory was stored inline in DO (pre-migration)
|
|
112
|
+
if (val.memory && is(Uint8Array, val.memory)) {
|
|
113
|
+
val.compressed = true
|
|
114
|
+
// Lazy migrate: move inline memory to R2
|
|
115
|
+
if (this.db.r2PutMemory) {
|
|
116
|
+
try {
|
|
117
|
+
await this.db.r2PutMemory(field, val.memory)
|
|
118
|
+
// Remove memory from DO entry to save space
|
|
119
|
+
const meta = { ...val }
|
|
120
|
+
delete meta.memory
|
|
121
|
+
meta._r2_memory = true
|
|
122
|
+
await this.db.put(`${key}.${field}`, meta)
|
|
123
|
+
} catch (e) {}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
this[key][field] = val
|
|
128
|
+
}
|
|
129
|
+
}
|
|
72
130
|
return this[key]?.[field]
|
|
73
131
|
}
|
|
74
132
|
async set(val, key, field) {
|
|
@@ -81,23 +139,59 @@ export default class ArMemBase {
|
|
|
81
139
|
this[key][field] = val
|
|
82
140
|
if (this.db) {
|
|
83
141
|
if (key === "env") {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
142
|
+
if (val.memory) {
|
|
143
|
+
let memory = val.memory
|
|
144
|
+
try {
|
|
145
|
+
memory = this.compress(val.memory)
|
|
146
|
+
} catch (e) {
|
|
147
|
+
console.log(e)
|
|
148
|
+
}
|
|
149
|
+
this[key][field].original_size = val.memory.length
|
|
150
|
+
|
|
151
|
+
// R2 path: store compressed memory in R2, metadata in DO
|
|
152
|
+
if (this.db.r2PutMemory) {
|
|
153
|
+
try {
|
|
154
|
+
await this.db.r2PutMemory(field, memory)
|
|
155
|
+
// Store metadata without memory in DO
|
|
156
|
+
const meta = { ...this[key][field] }
|
|
157
|
+
delete meta.memory
|
|
158
|
+
meta._r2_memory = true
|
|
159
|
+
await this.db.put(`${key}.${field}`, meta)
|
|
160
|
+
} catch (e) {
|
|
161
|
+
// Fallback to inline storage
|
|
162
|
+
await this.db.put(
|
|
163
|
+
`${key}.${field}`,
|
|
164
|
+
assoc("memory", memory, this[key][field])
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
await this.db.put(
|
|
169
|
+
`${key}.${field}`,
|
|
170
|
+
assoc("memory", memory, this[key][field])
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
// Remote CU process — memory lives on satellite, store metadata only
|
|
175
|
+
await this.db.put(`${key}.${field}`, this[key][field])
|
|
89
176
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
assoc("memory", memory, this[key][field])
|
|
94
|
-
)
|
|
177
|
+
} else if (key === "txs" && this._d1Ready) {
|
|
178
|
+
// D1 + R2 are authoritative for tx metadata, skip DO storage
|
|
179
|
+
this._txCache.set(field, val)
|
|
95
180
|
} else {
|
|
96
181
|
await this.db.put(`${key}.${field}`, this[key][field])
|
|
97
182
|
}
|
|
98
183
|
}
|
|
99
184
|
}
|
|
100
185
|
}
|
|
186
|
+
// Get all field keys for a given prefix (e.g. all process IDs under "env")
|
|
187
|
+
async getFieldKeys(key) {
|
|
188
|
+
await this.init()
|
|
189
|
+
if (!this.db) return Object.keys(this[key] ?? {})
|
|
190
|
+
const stored = await this.db.getKeys({ start: key + ".", end: key + "0" })
|
|
191
|
+
const dbKeys = (stored || []).map(k => k.split(".")[1]).filter(Boolean)
|
|
192
|
+
const memKeys = Object.keys(this[key] ?? {})
|
|
193
|
+
return [...new Set([...memKeys, ...dbKeys])]
|
|
194
|
+
}
|
|
101
195
|
initSync() {
|
|
102
196
|
this.items = {}
|
|
103
197
|
this.addrmap = {}
|
|
@@ -134,6 +228,7 @@ export default class ArMemBase {
|
|
|
134
228
|
this.txs[key] = {
|
|
135
229
|
id: key,
|
|
136
230
|
block: 0,
|
|
231
|
+
owner: this.scheduler || "",
|
|
137
232
|
tags: buildTags(null, {
|
|
138
233
|
"Data-Protocol": "ao",
|
|
139
234
|
Variant: w.variant ?? this.variant ?? "ao.TN.1",
|
|
@@ -173,6 +268,7 @@ export default class ArMemBase {
|
|
|
173
268
|
id: "0",
|
|
174
269
|
}
|
|
175
270
|
this.blocks.push("0")
|
|
271
|
+
this.lastBlockId = "0"
|
|
176
272
|
this.height = 1
|
|
177
273
|
}
|
|
178
274
|
async putAll(key) {
|
|
@@ -183,32 +279,62 @@ export default class ArMemBase {
|
|
|
183
279
|
this.isInit = true
|
|
184
280
|
if (typeof this._init === "function") await this._init()
|
|
185
281
|
if (this.db) {
|
|
282
|
+
let fresh = true
|
|
283
|
+
// Probe D1 readiness: binding exists AND schema is applied
|
|
284
|
+
this._d1Ready = false
|
|
285
|
+
if (this.db.d1) {
|
|
286
|
+
try {
|
|
287
|
+
await this.db.d1.prepare("SELECT 1 FROM blocks LIMIT 0").all()
|
|
288
|
+
this._d1Ready = true
|
|
289
|
+
} catch {}
|
|
290
|
+
}
|
|
291
|
+
// Load scalars: always load height + blocks; also lastBlockId when D1 ready
|
|
186
292
|
for (const v of ["height", "blocks"]) {
|
|
187
293
|
const val = await this.db.get(v)
|
|
188
|
-
if (val
|
|
294
|
+
if (val !== null) {
|
|
295
|
+
this[v] = val
|
|
296
|
+
fresh = false
|
|
297
|
+
}
|
|
189
298
|
}
|
|
190
|
-
|
|
191
|
-
"
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
"
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
299
|
+
if (this._d1Ready) {
|
|
300
|
+
const lbid = await this.db.get("lastBlockId")
|
|
301
|
+
if (lbid !== null) {
|
|
302
|
+
this.lastBlockId = lbid
|
|
303
|
+
fresh = false
|
|
304
|
+
}
|
|
305
|
+
// Migration: derive lastBlockId from blocks if not yet stored
|
|
306
|
+
if (this.lastBlockId === "0" && this.blocks.length > 0) {
|
|
307
|
+
this.lastBlockId = this.blocks[this.blocks.length - 1]
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Always load blocks + blockmap so the O(n) scan fallback in tgql.js works.
|
|
311
|
+
// Skip eagerly loading txs when D1 is ready (D1 is authoritative for tx queries).
|
|
312
|
+
const collections = this._d1Ready
|
|
313
|
+
? ["items", "env", "modules", "wasms", "addrmap", "blockmap", "modmap", "msgs"]
|
|
314
|
+
: ["items", "txs", "env", "modules", "wasms", "addrmap", "blockmap", "modmap", "msgs"]
|
|
315
|
+
for (const v of collections) {
|
|
201
316
|
this[v] ??= {}
|
|
202
317
|
const items = await this.db.getKeys({ start: v, end: v + "a" })
|
|
318
|
+
if (items?.length) fresh = false
|
|
203
319
|
for (const v2 of items || []) {
|
|
204
320
|
const key = v2.split(".")[0]
|
|
205
321
|
const field = v2.split(".")[1]
|
|
206
|
-
if (key === v) {
|
|
207
|
-
if (key
|
|
322
|
+
if (key === v && field) {
|
|
323
|
+
if (key === "env") {
|
|
208
324
|
let v3 = await this.db.get(v2)
|
|
209
|
-
if (is(Uint8Array, v3.memory)) {
|
|
325
|
+
if (v3 && is(Uint8Array, v3.memory)) {
|
|
210
326
|
v3.compressed = true
|
|
211
327
|
}
|
|
328
|
+
// If memory was offloaded to R2, fetch it
|
|
329
|
+
if (v3 && !v3.memory && v3._r2_memory && this.db.r2GetMemory) {
|
|
330
|
+
try {
|
|
331
|
+
const r2mem = await this.db.r2GetMemory(field)
|
|
332
|
+
if (r2mem) {
|
|
333
|
+
v3.memory = r2mem
|
|
334
|
+
v3.compressed = true
|
|
335
|
+
}
|
|
336
|
+
} catch (e) {}
|
|
337
|
+
}
|
|
212
338
|
this[v][field] = v3
|
|
213
339
|
} else {
|
|
214
340
|
this[v][field] = await this.db.get(v2)
|
|
@@ -216,6 +342,57 @@ export default class ArMemBase {
|
|
|
216
342
|
}
|
|
217
343
|
}
|
|
218
344
|
}
|
|
345
|
+
if (fresh) {
|
|
346
|
+
// First boot: persist initial state from initSync()
|
|
347
|
+
const scalarKeys = this._d1Ready ? ["height", "blocks", "lastBlockId"] : ["height", "blocks"]
|
|
348
|
+
for (const v of scalarKeys) await this.set(this[v], v)
|
|
349
|
+
for (const v of ["modules", "wasms", "addrmap", "blockmap", "txs"]) {
|
|
350
|
+
await this.putAll(v)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Seed D1 with initial data when D1 is empty, and ensure seed txs
|
|
354
|
+
// from initSync() are present even when D1 already has data
|
|
355
|
+
if (this._d1Ready && this.db?.d1WriteBlock) {
|
|
356
|
+
try {
|
|
357
|
+
const probe = await this.db.d1.prepare("SELECT COUNT(*) as cnt FROM blocks").first()
|
|
358
|
+
if (!probe || probe.cnt === 0) {
|
|
359
|
+
for (const bId of this.blocks) {
|
|
360
|
+
const block = this.blockmap[bId]
|
|
361
|
+
if (block) await this.db.d1WriteBlock(block)
|
|
362
|
+
}
|
|
363
|
+
for (const txId in this.txs) {
|
|
364
|
+
const tx = this.txs[txId]
|
|
365
|
+
if (tx) await this.db.d1WriteTx(tx, tx.block_id ?? "0", tx.block ?? 1)
|
|
366
|
+
}
|
|
367
|
+
for (const addr in this.addrmap) {
|
|
368
|
+
await this.db.d1WriteAddrmap(addr, this.addrmap[addr])
|
|
369
|
+
}
|
|
370
|
+
for (const name in this.modules) {
|
|
371
|
+
await this.db.d1WriteModule(name, this.modules[name])
|
|
372
|
+
}
|
|
373
|
+
for (const id in this.wasms) {
|
|
374
|
+
await this.db.d1WriteWasm(id, this.wasms[id])
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
// D1 has data but seed txs from initSync() might be missing
|
|
378
|
+
// (e.g. Scheduler-Location added after initial boot)
|
|
379
|
+
const txKeys = Object.keys(this.txs)
|
|
380
|
+
for (const txId of txKeys) {
|
|
381
|
+
const existing = await this.db.d1.prepare("SELECT 1 FROM txs WHERE id = ?").bind(txId).first()
|
|
382
|
+
if (!existing) {
|
|
383
|
+
const tx = this.txs[txId]
|
|
384
|
+
if (tx) await this.db.d1WriteTx(tx, tx.block_id ?? "0", tx.block ?? 1)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
for (const addr in this.addrmap) {
|
|
388
|
+
const existing = await this.db.d1.prepare("SELECT 1 FROM addrmap WHERE address = ?").bind(addr).first()
|
|
389
|
+
if (!existing) await this.db.d1WriteAddrmap(addr, this.addrmap[addr])
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} catch (e) {
|
|
393
|
+
console.error("[ArMem] D1 seed/backfill error:", e?.message || e)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
219
396
|
} else {
|
|
220
397
|
for (const v of ["height", "blocks"]) await this.set(this[v], v)
|
|
221
398
|
for (const v of ["modules", "wasms", "addrmap", "blockmap"]) {
|
|
@@ -224,40 +401,94 @@ export default class ArMemBase {
|
|
|
224
401
|
}
|
|
225
402
|
}
|
|
226
403
|
async getTx(id) {
|
|
227
|
-
|
|
404
|
+
// Check LRU cache first
|
|
405
|
+
if (this._txCache.has(id)) return this._txCache.get(id)
|
|
406
|
+
// Check in-memory txs (always available for non-DB path, seed data for DB path)
|
|
407
|
+
let tx = this.txs[id] ?? null
|
|
408
|
+
// Try D1 first (indexed lookup), then DO storage fallback
|
|
409
|
+
if (!tx && this.db) {
|
|
410
|
+
await this.init()
|
|
411
|
+
if (this.db.d1GetTxById) {
|
|
412
|
+
try { tx = await this.db.d1GetTxById(id) } catch (e) {
|
|
413
|
+
if (!e?.message?.includes("no such table")) throw e
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (!tx) {
|
|
417
|
+
const val = await this.db.get(`txs.${id}`)
|
|
418
|
+
if (val !== null) tx = val
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// Remote AR fallback — standalone units fetch from main AR
|
|
422
|
+
if (!tx && this._remote) {
|
|
423
|
+
tx = await this._remote.getTx(id)
|
|
424
|
+
}
|
|
228
425
|
if (isNil(tx)) return null
|
|
426
|
+
// Handle bundle references (DO stores {bundle: parentId} for bundled items)
|
|
229
427
|
if (tx.bundle) {
|
|
230
428
|
try {
|
|
231
|
-
let
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
429
|
+
let bundleTx = this.txs[tx.bundle] ?? null
|
|
430
|
+
if (!bundleTx && this.db) {
|
|
431
|
+
bundleTx = await this.db.get(`txs.${tx.bundle}`)
|
|
432
|
+
}
|
|
433
|
+
if (bundleTx) {
|
|
434
|
+
// Lazy-load bundle tx data from R2 if needed
|
|
435
|
+
if (!bundleTx.data && this.db?.r2GetTxData) {
|
|
436
|
+
bundleTx.data = await this.db.r2GetTxData(tx.bundle)
|
|
437
|
+
}
|
|
438
|
+
if (bundleTx.data) {
|
|
439
|
+
let bdata = bundleTx.data
|
|
440
|
+
if (typeof bdata === "string") {
|
|
441
|
+
const b64 = bdata.replace(/-/g, "+").replace(/_/g, "/")
|
|
442
|
+
bdata = Buffer.from(b64, "base64")
|
|
443
|
+
}
|
|
444
|
+
let bundle = new Bundle(bdata)
|
|
445
|
+
for (let di of bundle.items) {
|
|
446
|
+
if (id === di.id) {
|
|
447
|
+
const data = di.data
|
|
448
|
+
const data_size = Buffer.byteLength(di.rawData).toString()
|
|
449
|
+
let data_type = ""
|
|
450
|
+
for (const t of di.tags)
|
|
451
|
+
if (t.name === "Content-Type") data_type = t.value
|
|
452
|
+
const owner = await this.owner(di)
|
|
453
|
+
tx = {
|
|
454
|
+
_data: { size: data_size, type: data_type },
|
|
455
|
+
anchor: di.anchor,
|
|
456
|
+
signature: di.signature,
|
|
457
|
+
recipient: di.target,
|
|
458
|
+
id: await di.id,
|
|
459
|
+
item: di,
|
|
460
|
+
owner,
|
|
461
|
+
tags: di.tags,
|
|
462
|
+
data,
|
|
463
|
+
}
|
|
464
|
+
}
|
|
250
465
|
}
|
|
251
466
|
}
|
|
252
467
|
}
|
|
253
468
|
} catch (e) {}
|
|
254
469
|
}
|
|
470
|
+
// Lazy-load data from R2 if not present
|
|
471
|
+
if (tx && !tx.data && this.db?.r2GetTxData) {
|
|
472
|
+
tx.data = await this.db.r2GetTxData(tx.id ?? id)
|
|
473
|
+
}
|
|
474
|
+
// Add to LRU cache
|
|
475
|
+
if (tx) {
|
|
476
|
+
if (this._txCache.size >= this._TX_CACHE_MAX) {
|
|
477
|
+
this._txCache.delete(this._txCache.keys().next().value)
|
|
478
|
+
}
|
|
479
|
+
this._txCache.set(id, tx)
|
|
480
|
+
}
|
|
255
481
|
return tx
|
|
256
482
|
}
|
|
257
483
|
async getWasm(module) {
|
|
258
484
|
let mod = module ?? this.modules.aos2_0_1
|
|
259
485
|
if (!mod) throw Error("module not found")
|
|
260
486
|
let format = null
|
|
487
|
+
// Lazy load wasm entry if needed
|
|
488
|
+
if (!this.wasms[mod] && this.db) {
|
|
489
|
+
const val = await this.db.get(`wasms.${mod}`)
|
|
490
|
+
if (val !== null) this.wasms[mod] = val
|
|
491
|
+
}
|
|
261
492
|
let _wasm = await this.wasms[mod]
|
|
262
493
|
let wasm = _wasm?.data
|
|
263
494
|
if (!wasm) {
|
|
@@ -265,10 +496,30 @@ export default class ArMemBase {
|
|
|
265
496
|
wasm = await this._getWasm(this.wasms[mod].file)
|
|
266
497
|
format = _wasm.format
|
|
267
498
|
} else {
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
wasm =
|
|
271
|
-
|
|
499
|
+
// Try R2/KV cache for wasm binary
|
|
500
|
+
if (this.db?.r2GetWasm) {
|
|
501
|
+
try { wasm = await this.db.r2GetWasm(mod) } catch (e) {}
|
|
502
|
+
}
|
|
503
|
+
// Remote AR fallback for wasm binary
|
|
504
|
+
if (!wasm && this._remote) {
|
|
505
|
+
const remoteData = await this._remote.data(mod)
|
|
506
|
+
if (remoteData) {
|
|
507
|
+
wasm = remoteData instanceof Uint8Array ? remoteData : Buffer.from(remoteData, "base64")
|
|
508
|
+
format = _wasm?.format
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (!wasm) {
|
|
512
|
+
const tx = await this.getTx(mod)
|
|
513
|
+
if (tx) {
|
|
514
|
+
wasm = Buffer.from(tx.data, "base64")
|
|
515
|
+
format = tags(tx.tags)["Module-Format"]
|
|
516
|
+
// Cache to R2 for future reads
|
|
517
|
+
if (this.db?.r2PutWasm) {
|
|
518
|
+
try { await this.db.r2PutWasm(mod, wasm) } catch (e) {}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
} else {
|
|
522
|
+
format = _wasm?.format
|
|
272
523
|
}
|
|
273
524
|
}
|
|
274
525
|
} else format = _wasm.format
|
package/esm/armem-cf.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { cfWasmReady } from "./cf-env.js"
|
|
2
|
+
import sqlite from "./lua/sqlite.js"
|
|
3
|
+
import aos2_0_3 from "./lua/aos2_0_3.js"
|
|
4
|
+
import aos2_0_1 from "./lua/aos2_0_1.js"
|
|
5
|
+
import aos2_0_4_32 from "./lua/aos2_0_4_32.js"
|
|
6
|
+
import Base from "./armem-base.js"
|
|
7
|
+
import StorageMulti from "./storage-multi.js"
|
|
8
|
+
import dodb from "./dodb.js"
|
|
9
|
+
import { initSync, Waosm } from "./waosm/waosm.js"
|
|
10
|
+
import wasmModule from "./waosm/waosm_bg.wasm"
|
|
11
|
+
|
|
12
|
+
// Import .wasm files — wrangler pre-compiles them to WebAssembly.Module
|
|
13
|
+
import aos2_0_1_mod from "./lua/aos2_0_1.wasm"
|
|
14
|
+
import aos2_0_3_mod from "./lua/aos2_0_3.wasm"
|
|
15
|
+
import aos2_0_4_32_mod from "./lua/aos2_0_4_32.wasm"
|
|
16
|
+
import aos2_0_6_mod from "./lua/aos2_0_6.wasm"
|
|
17
|
+
import sqlite_mod from "./lua/sqlite.wasm"
|
|
18
|
+
|
|
19
|
+
const wasm = { sqlite, aos2_0_3, aos2_0_1, aos2_0_4_32 }
|
|
20
|
+
|
|
21
|
+
const precompiled = {
|
|
22
|
+
aos2_0_1: aos2_0_1_mod,
|
|
23
|
+
aos2_0_3: aos2_0_3_mod,
|
|
24
|
+
aos2_0_4_32: aos2_0_4_32_mod,
|
|
25
|
+
aos2_0_6: aos2_0_6_mod,
|
|
26
|
+
sqlite: sqlite_mod,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let wasmReady = false
|
|
30
|
+
function cfInit() {
|
|
31
|
+
if (wasmReady) return
|
|
32
|
+
wasmReady = true
|
|
33
|
+
initSync({ module: wasmModule })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default class ArMem extends Base {
|
|
37
|
+
constructor(args = {}) {
|
|
38
|
+
super({ ...args, init: cfInit, Waosm })
|
|
39
|
+
if (args.storage) {
|
|
40
|
+
// Use StorageMulti when D1/R2/KV bindings are available
|
|
41
|
+
if (args.d1 || args.r2 || args.kv) {
|
|
42
|
+
this.db = new StorageMulti({
|
|
43
|
+
storage: args.storage,
|
|
44
|
+
d1: args.d1 || null,
|
|
45
|
+
r2: args.r2 || null,
|
|
46
|
+
kv: args.kv || null,
|
|
47
|
+
})
|
|
48
|
+
} else {
|
|
49
|
+
this.db = dodb(args.storage)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this.initSync()
|
|
53
|
+
}
|
|
54
|
+
async _getWasm(file) {
|
|
55
|
+
// Queue the pre-compiled WebAssembly.Module so the patched
|
|
56
|
+
// WebAssembly.instantiate in cf-env.js can intercept the call.
|
|
57
|
+
const mod = precompiled[file]
|
|
58
|
+
if (mod) cfWasmReady(mod)
|
|
59
|
+
// Return the buffer — emscripten's normal loading path proceeds,
|
|
60
|
+
// but our patched WebAssembly.instantiate swaps the pre-compiled module.
|
|
61
|
+
if (wasm[file]) return Buffer.from(wasm[file], "base64")
|
|
62
|
+
// For modules without a base64 JS file, return a dummy buffer.
|
|
63
|
+
// The patched instantiate will use the pre-compiled module anyway.
|
|
64
|
+
if (mod) return new Uint8Array(1)
|
|
65
|
+
throw new Error(`WASM module not found: ${file}`)
|
|
66
|
+
}
|
|
67
|
+
}
|
package/esm/armem.js
CHANGED
|
@@ -6,12 +6,17 @@ import { readFileSync } from "fs"
|
|
|
6
6
|
import { resolve } from "path"
|
|
7
7
|
import Base from "./armem-base.js"
|
|
8
8
|
import { Waosm } from "./waosm-node.js"
|
|
9
|
+
import dodb from "./dodb.js"
|
|
9
10
|
|
|
10
11
|
export default class ArMem extends Base {
|
|
11
12
|
constructor(args = {}) {
|
|
12
|
-
const { cache } = args
|
|
13
|
+
const { cache, storage } = args
|
|
13
14
|
super({ ...args, Waosm })
|
|
14
|
-
if (
|
|
15
|
+
if (storage) {
|
|
16
|
+
this.db = dodb(storage)
|
|
17
|
+
} else if (cache) {
|
|
18
|
+
this.db = open({ path: cache, compression: true })
|
|
19
|
+
}
|
|
15
20
|
this.initSync()
|
|
16
21
|
}
|
|
17
22
|
async _getWasm(file) {
|