wao 0.11.0 → 0.11.2

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/esm/weavedrive.js CHANGED
@@ -1,8 +1,8 @@
1
1
  const KB = 1024
2
2
  const MB = KB * 1024
3
- const CACHE_SZ = 32 * KB
4
- const CHUNK_SZ = 128 * MB
5
- const NOTIFY_SZ = 512 * MB
3
+ const CACHE_SZ = 32 * KB // Keep small cache size
4
+ const CHUNK_SZ = 1 * MB // Reduced from 128MB to 1MB for better memory management
5
+ const STREAM_CHUNK = 256 * KB // Added smaller streaming chunk size
6
6
  const log = console.log
7
7
 
8
8
  export default class WeaveDrive {
@@ -10,50 +10,43 @@ export default class WeaveDrive {
10
10
  this.ext = (mod, FS) => {
11
11
  return {
12
12
  reset(fd) {
13
- //console.log("WeaveDrive: Resetting fd: ", fd)
14
13
  FS.streams[fd].node.position = 0
15
14
  FS.streams[fd].node.cache = new Uint8Array(0)
16
15
  },
17
16
 
18
17
  async create(id) {
19
- var properties = { isDevice: false, contents: null }
20
-
21
- //console.log("WeaveDrive: Arweave ID is not admissable! ", id)
22
18
  if (!(await this.checkAdmissible(id))) return 0
23
19
 
24
- // Create the file in the emscripten FS
25
-
26
20
  if (!FS.analyzePath("/data/").exists) FS.mkdir("/data/")
27
21
 
22
+ var properties = { isDevice: false, contents: null }
28
23
  var node = FS.createFile("/", "data/" + id, properties, true, false)
29
- // Set initial parameters
30
- /*
31
- var bytesLength = await this.customFetch(`/${id}`, {
32
- method: "HEAD",
33
- }).then(res => res.headers.get("Content-Length"))
34
- */
35
- let data = await ar.data(id)
36
- const bytesLength = data?.length ?? 0
24
+
25
+ let bytesLength = 0
26
+ try {
27
+ let data = await ar.data(id)
28
+ bytesLength = data?.length ?? 0
29
+ } catch (e) {
30
+ console.error("Failed to get data length:", e)
31
+ return 0
32
+ }
33
+
37
34
  node.total_size = Number(bytesLength)
38
35
  node.cache = new Uint8Array(0)
39
36
  node.position = 0
37
+ node.chunks = new Map() // Add chunk cache
40
38
 
41
- // Add a function that defers querying the file size until it is asked the first time.
42
39
  Object.defineProperties(node, {
43
40
  usedBytes: { get: () => bytesLength },
44
41
  })
45
42
 
46
- // Now we have created the file in the emscripten FS, we can open it as a stream
47
43
  var stream = FS.open("/data/" + id, "r")
48
-
49
- //console.log("JS: Created file: ", id, " fd: ", stream.fd);
50
44
  return stream
51
45
  },
46
+
52
47
  async createBlockHeader(id) {
53
48
  var result = ""
54
49
  try {
55
- // todo: implement indep_hash
56
- // fetch(`/block/height/${id}`)
57
50
  const block = ar.mem.blockmap[ar.mem.blocks[id]]
58
51
  if (block) {
59
52
  result = JSON.stringify({
@@ -77,9 +70,9 @@ export default class WeaveDrive {
77
70
  var stream = FS.open("/block/" + id, "r")
78
71
  return stream
79
72
  },
73
+
80
74
  async createTxHeader(id) {
81
75
  var result = ""
82
- // fetch(`/tx/${id}`)
83
76
  try {
84
77
  let tx = ar.mem.txs[id]
85
78
  if (tx) result = JSON.stringify(tx)
@@ -94,6 +87,7 @@ export default class WeaveDrive {
94
87
  var stream = FS.open("/tx/" + id, "r")
95
88
  return stream
96
89
  },
90
+
97
91
  async createDataItemTxHeader(id) {
98
92
  let result = (
99
93
  await ar.gql.txs({
@@ -124,255 +118,98 @@ export default class WeaveDrive {
124
118
  var stream = FS.open("/tx2/" + id, "r")
125
119
  return stream
126
120
  },
127
- async open(filename) {
128
- const pathCategory = filename.split("/")[1]
129
- const id = filename.split("/")[2]
130
- console.log("JS: Opening ID: ", id)
131
- if (pathCategory === "tx") {
132
- FS.createPath("/", "tx", true, false)
133
- if (FS.analyzePath(filename).exists) {
134
- var stream = FS.open(filename, "r")
135
- if (stream.fd) return stream.fd
136
- return 0
137
- } else {
138
- const stream = await this.createTxHeader(id)
139
- return stream.fd
140
- }
141
- }
142
- if (pathCategory === "tx2") {
143
- FS.createPath("/", "tx2", true, false)
144
- if (FS.analyzePath(filename).exists) {
145
- var stream = FS.open(filename, "r")
146
- if (stream.fd) return stream.fd
147
- return 0
148
- } else {
149
- const stream = await this.createDataItemTxHeader(id)
150
- if (stream.fd) return stream.fd
151
- return 0
152
- }
153
- }
154
- if (pathCategory === "block") {
155
- FS.createPath("/", "block", true, false)
156
- if (FS.analyzePath(filename).exists) {
157
- var stream = FS.open(filename, "r")
158
- if (stream.fd) return stream.fd
159
- return 0
160
- } else {
161
- const stream = await this.createBlockHeader(id)
162
- return stream.fd
163
- }
121
+
122
+ // Chunk management helpers
123
+ getChunkKey(position) {
124
+ return Math.floor(position / CHUNK_SZ)
125
+ },
126
+
127
+ async fetchChunk(stream, chunkKey) {
128
+ const start = chunkKey * CHUNK_SZ
129
+ const end = Math.min(start + CHUNK_SZ, stream.node.total_size)
130
+
131
+ try {
132
+ const data = await ar.data(stream.node.name)
133
+ return data.subarray(start, end)
134
+ } catch (e) {
135
+ console.error("Failed to fetch chunk:", e)
136
+ return new Uint8Array(0)
164
137
  }
165
- if (pathCategory === "data") {
166
- if (FS.analyzePath(filename).exists) {
167
- var stream = FS.open(filename, "r")
168
- if (stream.fd) return stream.fd
169
- console.log("JS: File not found: ", filename)
170
- return 0
171
- } else {
172
- //console.log("JS: Open => Creating file: ", id);
173
- const stream = await this.create(id)
174
- //console.log("JS: Open => Created file: ", id, " fd: ", stream.fd);
175
- return stream.fd
138
+ },
139
+
140
+ async ensureChunkLoaded(stream, position) {
141
+ const chunkKey = this.getChunkKey(position)
142
+
143
+ if (!stream.node.chunks.has(chunkKey)) {
144
+ // Remove old chunks if we have too many
145
+ if (stream.node.chunks.size >= 3) {
146
+ // Keep only 3 chunks in memory
147
+ const oldestKey = stream.node.chunks.keys().next().value
148
+ stream.node.chunks.delete(oldestKey)
176
149
  }
177
- } else if (pathCategory === "headers") {
178
- console.log("Header access not implemented yet.")
179
- return 0
180
- } else {
181
- console.log("JS: Invalid path category: ", pathCategory)
182
- return 0
150
+
151
+ const chunk = await this.fetchChunk(stream, chunkKey)
152
+ stream.node.chunks.set(chunkKey, chunk)
183
153
  }
154
+
155
+ return stream.node.chunks.get(chunkKey)
184
156
  },
185
157
 
186
158
  async read(fd, raw_dst_ptr, raw_length) {
187
- // Note: The length and dst_ptr are 53 bit integers in JS, so this _should_ be ok into a large memspace.
188
159
  var to_read = Number(raw_length)
189
160
  var dst_ptr = Number(raw_dst_ptr)
190
161
 
191
- var stream = 0
192
- for (var i = 0; i < FS.streams.length; i++) {
193
- if (FS.streams[i].fd === fd) stream = FS.streams[i]
194
- }
195
- // read block headers
196
- if (stream.path.includes("/block")) {
197
- mod.HEAP8.set(stream.node.contents.subarray(0, to_read), dst_ptr)
198
- return to_read
199
- }
200
- // read tx headers
201
- if (stream.path.includes("/tx")) {
162
+ var stream = FS.streams.find(s => s.fd === fd)
163
+ if (!stream) return 0
164
+
165
+ // Handle block and tx headers
166
+ if (stream.path.includes("/block") || stream.path.includes("/tx")) {
202
167
  mod.HEAP8.set(stream.node.contents.subarray(0, to_read), dst_ptr)
203
168
  return to_read
204
169
  }
205
- // Satisfy what we can with the cache first
206
- var bytes_read = this.readFromCache(stream, dst_ptr, to_read)
207
- stream.position += bytes_read
208
- stream.lastReadPosition = stream.position
209
- dst_ptr += bytes_read
210
- to_read -= bytes_read
211
170
 
212
- // Return if we have satisfied the request
171
+ let bytesRead = 0
172
+ while (to_read > 0) {
173
+ // Get current chunk
174
+ const chunk = await this.ensureChunkLoaded(stream, stream.position)
175
+ const chunkOffset = stream.position % CHUNK_SZ
213
176
 
214
- //console.log("WeaveDrive: Satisfied request with cache. Returning...")
215
- if (to_read === 0) return bytes_read
177
+ // Calculate how much we can read from this chunk
178
+ const available = chunk.length - chunkOffset
179
+ const readSize = Math.min(to_read, available)
216
180
 
217
- //console.log("WeaveDrive: Read from cache: ", bytes_read, " Remaining to read: ", to_read)
181
+ if (readSize <= 0) break
218
182
 
219
- const chunk_download_sz = Math.max(to_read, CACHE_SZ)
220
- const to = Math.min(
221
- stream.node.total_size,
222
- stream.position + chunk_download_sz,
223
- )
224
- //console.log("WeaveDrive: fd: ", fd, " Read length: ", to_read, " Reading ahead:", to - to_read - stream.position)
225
-
226
- // fetch(`/${stream.node.name}`)
227
- const data = await ar.data(stream.node.name, null, log)
228
- // Extract the Range header to determine the start and end of the requested chunk
229
- const start = stream.position
230
- const end = to
231
-
232
- // Create a ReadableStream for the requested chunk
233
- const chunk = data.subarray(start, end)
234
- const response = new Response(
235
- new ReadableStream({
236
- start(controller) {
237
- controller.enqueue(chunk)
238
- controller.close()
239
- },
240
- }),
241
- {
242
- headers: { "content-length": chunk.length.toString() },
243
- },
244
- )
245
- const reader = response.body.getReader()
246
- var bytes_until_cache = CHUNK_SZ
247
- var bytes_until_notify = NOTIFY_SZ
248
- var downloaded_bytes = 0
249
- var cache_chunks = []
250
-
251
- try {
252
- while (true) {
253
- const { done, value: chunk_bytes } = await reader.read()
254
- if (done) break
255
- // Update the number of downloaded bytes to be _all_, not just the write length
256
- downloaded_bytes += chunk_bytes.length
257
- bytes_until_cache -= chunk_bytes.length
258
- bytes_until_notify -= chunk_bytes.length
259
-
260
- // Write bytes from the chunk and update the pointer if necessary
261
- const write_length = Math.min(chunk_bytes.length, to_read)
262
- if (write_length > 0) {
263
- //console.log("WeaveDrive: Writing: ", write_length, " bytes to: ", dst_ptr)
264
- mod.HEAP8.set(chunk_bytes.subarray(0, write_length), dst_ptr)
265
- dst_ptr += write_length
266
- bytes_read += write_length
267
- stream.position += write_length
268
- to_read -= write_length
269
- }
270
-
271
- if (to_read == 0) {
272
- // Add excess bytes to our cache
273
- const chunk_to_cache = chunk_bytes.subarray(write_length)
274
- //console.log("WeaveDrive: Cacheing excess: ", chunk_to_cache.length)
275
- cache_chunks.push(chunk_to_cache)
276
- }
277
-
278
- if (bytes_until_cache <= 0) {
279
- console.log(
280
- "WeaveDrive: Chunk size reached. Compressing cache...",
281
- )
282
- stream.node.cache = this.addChunksToCache(
283
- stream.node.cache,
284
- cache_chunks,
285
- )
286
- cache_chunks = []
287
- bytes_until_cache = CHUNK_SZ
288
- }
289
-
290
- if (bytes_until_notify <= 0) {
291
- console.log(
292
- "WeaveDrive: Downloaded: ",
293
- (downloaded_bytes / stream.node.total_size) * 100,
294
- "%",
295
- )
296
- bytes_until_notify = NOTIFY_SZ
297
- }
298
- }
299
- } catch (error) {
300
- console.error("WeaveDrive: Error reading the stream: ", error)
301
- } finally {
302
- reader.releaseLock()
303
- }
304
- // If we have no cache, or we have not satisfied the full request, we need to download the rest
305
- // Rebuild the cache from the new cache chunks
306
- stream.node.cache = this.addChunksToCache(
307
- stream.node.cache,
308
- cache_chunks,
309
- )
183
+ // Copy data to destination
184
+ const data = chunk.subarray(chunkOffset, chunkOffset + readSize)
185
+ mod.HEAP8.set(data, dst_ptr)
310
186
 
311
- // Update the last read position
312
- stream.lastReadPosition = stream.position
313
- return bytes_read
314
- },
315
- close(fd) {
316
- var stream = 0
317
- for (var i = 0; i < FS.streams.length; i++) {
318
- if (FS.streams[i].fd === fd) stream = FS.streams[i]
187
+ // Update pointers
188
+ stream.position += readSize
189
+ dst_ptr += readSize
190
+ to_read -= readSize
191
+ bytesRead += readSize
319
192
  }
320
- FS.close(stream)
321
- },
322
193
 
323
- // Readahead cache functions
324
- readFromCache(stream, dst_ptr, length) {
325
- // Check if the cache has been invalidated by a seek
326
- if (stream.lastReadPosition !== stream.position) {
327
- //console.log("WeaveDrive: Invalidating cache for fd: ", stream.fd, " Current pos: ", stream.position, " Last read pos: ", stream.lastReadPosition)
328
- stream.node.cache = new Uint8Array(0)
329
- return 0
330
- }
331
- // Calculate the bytes of the request that can be satisfied with the cache
332
- var cache_part_length = Math.min(length, stream.node.cache.length)
333
- var cache_part = stream.node.cache.subarray(0, cache_part_length)
334
- mod.HEAP8.set(cache_part, dst_ptr)
335
- // Set the new cache to the remainder of the unused cache and update pointers
336
- stream.node.cache = stream.node.cache.subarray(cache_part_length)
337
-
338
- return cache_part_length
194
+ return bytesRead
339
195
  },
340
196
 
341
- addChunksToCache(old_cache, chunks) {
342
- // Make a new cache array of the old cache length + the sum of the chunk lengths, capped by the max cache size
343
- var new_cache_length = Math.min(
344
- old_cache.length +
345
- chunks.reduce((acc, chunk) => acc + chunk.length, 0),
346
- CACHE_SZ,
347
- )
348
- var new_cache = new Uint8Array(new_cache_length)
349
- // Copy the old cache to the new cache
350
- new_cache.set(old_cache, 0)
351
- // Load the cache chunks into the new cache
352
- var current_offset = old_cache.length
353
- for (let chunk of chunks) {
354
- if (current_offset < new_cache_length) {
355
- new_cache.set(
356
- chunk.subarray(0, new_cache_length - current_offset),
357
- current_offset,
358
- )
359
- current_offset += chunk.length
360
- }
197
+ // Keep existing helper methods
198
+ close(fd) {
199
+ var stream = FS.streams.find(s => s.fd === fd)
200
+ if (stream) {
201
+ stream.node.chunks?.clear() // Clean up chunks
202
+ FS.close(stream)
361
203
  }
362
- return new_cache
363
204
  },
364
205
 
365
- // General helpder functions
206
+ // ... rest of the existing methods (checkAdmissible, getTagValues, etc.) ...
366
207
  async checkAdmissible(ID) {
367
- // CAUTION: If the module is initiated with `mode = test` we don't check availability.
368
208
  if (mod.mode && mod.mode == "test") return true
369
209
 
370
- // Check if we are attempting to load the On-Boot id, if so allow it
371
- // this was added for AOP 6 Boot loader See: https://github.com/permaweb/aos/issues/342
372
210
  const bootTag = this.getTagValue("On-Boot", mod.spawn.tags)
373
211
  if (bootTag && bootTag === ID) return true
374
212
 
375
- // Check that this module or process set the WeaveDrive tag on spawn
376
213
  const blockHeight = mod.blockHeight
377
214
  const moduleExtensions = this.getTagValues(
378
215
  "Extension",
@@ -393,18 +230,16 @@ export default class WeaveDrive {
393
230
  )
394
231
  return false
395
232
  }
233
+
396
234
  const modes = ["Assignments", "Individual", "Library"]
397
- // Get the Availability-Type from the spawned process's Module or Process item
398
- // First check the module for its defaults
399
235
  const moduleAvailabilityType = this.getTagValue(
400
236
  "Availability-Type",
401
237
  mod.module.tags,
402
238
  )
403
239
  const moduleMode = moduleAvailabilityType
404
240
  ? moduleAvailabilityType
405
- : "Assignments" // Default to assignments
241
+ : "Assignments"
406
242
 
407
- // Now check the process's spawn item. These settings override Module item settings.
408
243
  const processAvailabilityType = this.getTagValue(
409
244
  "Availability-Type",
410
245
  mod.spawn.tags,
@@ -423,8 +258,7 @@ export default class WeaveDrive {
423
258
  ...this.getTagValues("Attestor", mod.spawn.tags),
424
259
  ].filter(t => !!t),
425
260
  )
426
- // Init a set of GraphQL queries to run in order to find a valid attestation
427
- // Every WeaveDrive process has at least the "Assignments" availability check form.
261
+
428
262
  const exists = async tags => {
429
263
  const common = {
430
264
  owners: attestors,
@@ -433,6 +267,7 @@ export default class WeaveDrive {
433
267
  }
434
268
  return (await ar.gql.txs({ ...common, tags })).length > 0
435
269
  }
270
+
436
271
  const assignmentsHaveID = await exists({
437
272
  Type: "Attestation",
438
273
  Message: ID,
@@ -472,6 +307,67 @@ export default class WeaveDrive {
472
307
  const values = this.getTagValues(key, tags)
473
308
  return values.pop()
474
309
  },
310
+
311
+ async open(filename) {
312
+ const pathCategory = filename.split("/")[1]
313
+ const id = filename.split("/")[2]
314
+ console.log("JS: Opening ID: ", id)
315
+
316
+ if (pathCategory === "tx") {
317
+ FS.createPath("/", "tx", true, false)
318
+ if (FS.analyzePath(filename).exists) {
319
+ var stream = FS.open(filename, "r")
320
+ if (stream.fd) return stream.fd
321
+ return 0
322
+ } else {
323
+ const stream = await this.createTxHeader(id)
324
+ return stream.fd
325
+ }
326
+ }
327
+
328
+ if (pathCategory === "tx2") {
329
+ FS.createPath("/", "tx2", true, false)
330
+ if (FS.analyzePath(filename).exists) {
331
+ var stream = FS.open(filename, "r")
332
+ if (stream.fd) return stream.fd
333
+ return 0
334
+ } else {
335
+ const stream = await this.createDataItemTxHeader(id)
336
+ if (stream.fd) return stream.fd
337
+ return 0
338
+ }
339
+ }
340
+
341
+ if (pathCategory === "block") {
342
+ FS.createPath("/", "block", true, false)
343
+ if (FS.analyzePath(filename).exists) {
344
+ var stream = FS.open(filename, "r")
345
+ if (stream.fd) return stream.fd
346
+ return 0
347
+ } else {
348
+ const stream = await this.createBlockHeader(id)
349
+ return stream.fd
350
+ }
351
+ }
352
+
353
+ if (pathCategory === "data") {
354
+ if (FS.analyzePath(filename).exists) {
355
+ var stream = FS.open(filename, "r")
356
+ if (stream.fd) return stream.fd
357
+ console.log("JS: File not found: ", filename)
358
+ return 0
359
+ } else {
360
+ const stream = await this.create(id)
361
+ return stream.fd
362
+ }
363
+ } else if (pathCategory === "headers") {
364
+ console.log("Header access not implemented yet.")
365
+ return 0
366
+ } else {
367
+ console.log("JS: Invalid path category: ", pathCategory)
368
+ return 0
369
+ }
370
+ },
475
371
  }
476
372
  }
477
373
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wao",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "bin": {
5
5
  "wao": "./cjs/cli.js",
6
6
  "wao-esm": "./esm/cli.js"