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/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
- return this.blocks.length === 0
66
- ? "Do_Uc2Sju_ffp6Ev0AnLVdPtot15rvMjP-a9VVaA5fM"
67
- : last(this.blockmap[last(this.blocks)].txs)
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) return this[key]
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
- let memory = val.memory
85
- try {
86
- memory = this.compress(val.memory)
87
- } catch (e) {
88
- console.log(e)
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
- this[key][field].original_size = val.memory.length
91
- await this.db.put(
92
- `${key}.${field}`,
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) this[v] = val
294
+ if (val !== null) {
295
+ this[v] = val
296
+ fresh = false
297
+ }
189
298
  }
190
- for (const v of [
191
- "items",
192
- "txs",
193
- "env",
194
- "modules",
195
- "wasms",
196
- "addrmap",
197
- "blockmap",
198
- "modmap",
199
- "msgs",
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.match(/^env/)) {
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
- let tx = this.txs[id]
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 bundle = new Bundle(this.txs[tx.bundle].data)
232
- for (let di of bundle.items) {
233
- if (id === di.id) {
234
- const data = di.data
235
- const data_size = Buffer.byteLength(di.rawData).toString()
236
- let data_type = ""
237
- for (const t of di.tags)
238
- if (t.name === "Content-Type") data_type = t.value
239
- const owner = await this.owner(di)
240
- tx = {
241
- _data: { size: data_size, type: data_type },
242
- anchor: di.anchor,
243
- signature: di.signature,
244
- recipient: di.target,
245
- id: await di.id,
246
- item: di,
247
- owner,
248
- tags: di.tags,
249
- data,
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
- const tx = await this.getTx(mod)
269
- if (tx) {
270
- wasm = Buffer.from(tx.data, "base64")
271
- format = tags(tx.tags)["Module-Format"]
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
@@ -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 (cache) this.db = open({ path: cache, compression: true })
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) {