@autonomys/auto-drive 0.7.1 → 0.7.3

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.
@@ -1,5 +1,5 @@
1
- import { PBNode } from '@ipld/dag-pb'
2
1
  import { CID } from 'multiformats/cid'
2
+ import { PBNode } from '../ipld/index.js'
3
3
  import {
4
4
  createChunkedFileIpldNode,
5
5
  createChunkedMetadataIpldNode,
@@ -1,71 +1,110 @@
1
- import { PBNode } from '@ipld/dag-pb'
1
+ import type { BaseBlockstore } from 'blockstore-core'
2
+ import type { AwaitIterable } from 'interface-store'
2
3
  import { CID } from 'multiformats'
3
4
  import { cidOfNode } from '../cid/index.js'
4
- import { OffchainMetadata } from '../metadata/index.js'
5
+ import { decodeIPLDNodeData, OffchainMetadata } from '../metadata/index.js'
5
6
  import { Builders, fileBuilders, metadataBuilders } from './builders.js'
6
7
  import { createFolderInlinkIpldNode, createFolderIpldNode } from './nodes.js'
7
- import { chunkBuffer, encodeNode } from './utils.js'
8
+ import { chunkBuffer, encodeNode, PBNode } from './utils.js'
8
9
 
9
- export const DEFAULT_MAX_CHUNK_SIZE = 1024 * 64
10
- export const DEFAULT_MAX_LINK_PER_NODE = DEFAULT_MAX_CHUNK_SIZE / 64
10
+ export const DEFAULT_MAX_CHUNK_SIZE = 64 * 1024
11
11
 
12
- export interface IPLDDag {
13
- headCID: CID
14
- nodes: Map<CID, PBNode>
15
- }
12
+ const ESTIMATED_LINK_SIZE_IN_BYTES = 64
13
+ export const DEFAULT_MAX_LINK_PER_NODE = DEFAULT_MAX_CHUNK_SIZE / ESTIMATED_LINK_SIZE_IN_BYTES
16
14
 
17
- export const createFileIPLDDag = (
18
- file: Buffer,
15
+ export const processFileToIPLDFormat = (
16
+ blockstore: BaseBlockstore,
17
+ file: AwaitIterable<Buffer>,
18
+ totalSize: number,
19
19
  filename?: string,
20
- { chunkSize, maxLinkPerNode }: { chunkSize: number; maxLinkPerNode: number } = {
21
- chunkSize: DEFAULT_MAX_CHUNK_SIZE,
20
+ { maxChunkSize, maxLinkPerNode }: { maxChunkSize: number; maxLinkPerNode: number } = {
21
+ maxChunkSize: DEFAULT_MAX_CHUNK_SIZE,
22
22
  maxLinkPerNode: DEFAULT_MAX_LINK_PER_NODE,
23
23
  },
24
- ): IPLDDag => {
25
- return createBufferIPLDDag(file, filename, fileBuilders, { chunkSize, maxLinkPerNode })
24
+ ): Promise<CID> => {
25
+ return processBufferToIPLDFormat(blockstore, file, filename, totalSize, fileBuilders, {
26
+ maxChunkSize,
27
+ maxLinkPerNode,
28
+ })
26
29
  }
27
30
 
28
- export const createMetadataIPLDDag = (
31
+ export const processMetadataToIPLDFormat = async (
32
+ blockstore: BaseBlockstore,
29
33
  metadata: OffchainMetadata,
30
- limits: { chunkSize: number; maxLinkPerNode: number } = {
31
- chunkSize: DEFAULT_MAX_CHUNK_SIZE,
34
+ limits: { maxChunkSize: number; maxLinkPerNode: number } = {
35
+ maxChunkSize: DEFAULT_MAX_CHUNK_SIZE,
32
36
  maxLinkPerNode: DEFAULT_MAX_LINK_PER_NODE,
33
37
  },
34
- ): IPLDDag => {
38
+ ): Promise<CID> => {
35
39
  const buffer = Buffer.from(JSON.stringify(metadata))
36
40
  const name = `${metadata.name}.metadata.json`
37
- return createBufferIPLDDag(buffer, name, metadataBuilders, limits)
41
+ return processBufferToIPLDFormat(
42
+ blockstore,
43
+ (async function* () {
44
+ yield buffer
45
+ })(),
46
+ name,
47
+ buffer.byteLength,
48
+ metadataBuilders,
49
+ limits,
50
+ )
38
51
  }
39
52
 
40
- const createBufferIPLDDag = (
41
- buffer: Buffer,
53
+ const processBufferToIPLDFormat = async (
54
+ blockstore: BaseBlockstore,
55
+ buffer: AwaitIterable<Buffer>,
42
56
  filename: string | undefined,
57
+ totalSize: number,
43
58
  builders: Builders,
44
- { chunkSize, maxLinkPerNode }: { chunkSize: number; maxLinkPerNode: number } = {
45
- chunkSize: DEFAULT_MAX_CHUNK_SIZE,
59
+ { maxChunkSize, maxLinkPerNode }: { maxChunkSize: number; maxLinkPerNode: number } = {
60
+ maxChunkSize: DEFAULT_MAX_CHUNK_SIZE,
46
61
  maxLinkPerNode: DEFAULT_MAX_LINK_PER_NODE,
47
62
  },
48
- ): IPLDDag => {
49
- if (buffer.length <= chunkSize) {
50
- const head = builders.single(buffer, filename)
51
- const headCID = cidOfNode(head)
52
- return {
53
- headCID,
54
- nodes: new Map([[headCID, head]]),
55
- }
56
- }
63
+ ): Promise<CID> => {
64
+ const bufferChunks = chunkBuffer(buffer, { maxChunkSize })
57
65
 
58
- const bufferChunks = chunkBuffer(buffer, chunkSize)
59
-
60
- const nodes = new Map<CID, PBNode>()
61
-
62
- let CIDs: CID[] = bufferChunks.map((chunk) => {
66
+ let CIDs: CID[] = []
67
+ for await (const chunk of bufferChunks) {
63
68
  const node = builders.chunk(chunk)
64
69
  const cid = cidOfNode(node)
65
- nodes.set(cid, node)
70
+ await blockstore.put(cid, encodeNode(node))
71
+ CIDs.push(cid)
72
+ }
66
73
 
67
- return cid
74
+ return processBufferToIPLDFormatFromChunks(blockstore, CIDs, filename, totalSize, builders, {
75
+ maxLinkPerNode,
76
+ maxChunkSize,
68
77
  })
78
+ }
79
+
80
+ export const processBufferToIPLDFormatFromChunks = async (
81
+ blockstore: BaseBlockstore,
82
+ chunks: AwaitIterable<CID>,
83
+ filename: string | undefined,
84
+ totalSize: number,
85
+ builders: Builders,
86
+ { maxLinkPerNode, maxChunkSize }: { maxLinkPerNode: number; maxChunkSize: number } = {
87
+ maxLinkPerNode: DEFAULT_MAX_LINK_PER_NODE,
88
+ maxChunkSize: DEFAULT_MAX_CHUNK_SIZE,
89
+ },
90
+ ): Promise<CID> => {
91
+ let chunkCount = 0
92
+ let CIDs: CID[] = []
93
+ for await (const chunk of chunks) {
94
+ CIDs.push(chunk)
95
+ chunkCount++
96
+ }
97
+
98
+ if (CIDs.length === 1) {
99
+ const nodeBytes = await blockstore.get(CIDs[0])
100
+ await blockstore.delete(CIDs[0])
101
+ const data = decodeIPLDNodeData(nodeBytes)
102
+ const singleNode = builders.single(Buffer.from(data.data!), filename)
103
+ await blockstore.put(cidOfNode(singleNode), encodeNode(singleNode))
104
+ const headCID = cidOfNode(singleNode)
105
+
106
+ return headCID
107
+ }
69
108
 
70
109
  let depth = 1
71
110
  while (CIDs.length > maxLinkPerNode) {
@@ -73,31 +112,28 @@ const createBufferIPLDDag = (
73
112
  for (let i = 0; i < CIDs.length; i += maxLinkPerNode) {
74
113
  const chunk = CIDs.slice(i, i + maxLinkPerNode)
75
114
 
76
- const node = builders.inlink(chunk, chunk.length, depth, chunkSize)
115
+ const node = builders.inlink(chunk, chunk.length, depth, maxChunkSize)
77
116
  const cid = cidOfNode(node)
78
- nodes.set(cid, node)
117
+ await blockstore.put(cid, encodeNode(node))
79
118
  newCIDs.push(cid)
80
119
  }
81
120
  depth++
82
121
  CIDs = newCIDs
83
122
  }
84
- const head = builders.root(CIDs, buffer.length, depth, filename, chunkSize)
123
+ const head = builders.root(CIDs, totalSize, depth, filename, maxChunkSize)
85
124
  const headCID = cidOfNode(head)
86
- nodes.set(headCID, head)
125
+ await blockstore.put(headCID, encodeNode(head))
87
126
 
88
- return {
89
- headCID,
90
- nodes,
91
- }
127
+ return headCID
92
128
  }
93
129
 
94
- export const createFolderIPLDDag = (
130
+ export const processFolderToIPLDFormat = async (
131
+ blockstore: BaseBlockstore,
95
132
  children: CID[],
96
133
  name: string,
97
134
  size: number,
98
135
  { maxLinkPerNode }: { maxLinkPerNode: number } = { maxLinkPerNode: DEFAULT_MAX_LINK_PER_NODE },
99
- ): IPLDDag => {
100
- const nodes = new Map<CID, PBNode>()
136
+ ): Promise<CID> => {
101
137
  let cids = children
102
138
  let depth = 0
103
139
  while (cids.length > maxLinkPerNode) {
@@ -106,7 +142,7 @@ export const createFolderIPLDDag = (
106
142
  const chunk = cids.slice(i, i + maxLinkPerNode)
107
143
  const node = createFolderInlinkIpldNode(chunk, depth)
108
144
  const cid = cidOfNode(node)
109
- nodes.set(cid, node)
145
+ await blockstore.put(cid, encodeNode(node))
110
146
  newCIDs.push(cid)
111
147
  }
112
148
  cids = newCIDs
@@ -115,12 +151,34 @@ export const createFolderIPLDDag = (
115
151
 
116
152
  const node = createFolderIpldNode(cids, name, depth, size)
117
153
  const cid = cidOfNode(node)
118
- nodes.set(cid, node)
154
+ await blockstore.put(cid, encodeNode(node))
119
155
 
120
- return {
121
- headCID: cid,
122
- nodes,
156
+ return cid
157
+ }
158
+
159
+ /**
160
+ * Process chunks to IPLD format, return the last chunk if it's not full
161
+ * @returns the last chunk if it's not full, otherwise an empty buffer
162
+ */
163
+ export const processChunksToIPLDFormat = async (
164
+ blockstore: BaseBlockstore,
165
+ chunks: AwaitIterable<Buffer>,
166
+ builders: Builders,
167
+ { maxChunkSize = DEFAULT_MAX_CHUNK_SIZE }: { maxChunkSize?: number },
168
+ ): Promise<Buffer> => {
169
+ const bufferChunks = chunkBuffer(chunks, { maxChunkSize, ignoreLastChunk: false })
170
+
171
+ for await (const chunk of bufferChunks) {
172
+ if (chunk.byteLength < maxChunkSize) {
173
+ return chunk
174
+ }
175
+
176
+ const node = builders.chunk(chunk)
177
+ const cid = cidOfNode(node)
178
+ await blockstore.put(cid, encodeNode(node))
123
179
  }
180
+
181
+ return Buffer.alloc(0)
124
182
  }
125
183
 
126
184
  export const ensureNodeMaxSize = (
package/src/ipld/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './builders.js'
1
2
  export * from './chunker.js'
2
3
  export * from './nodes.js'
3
- export { encodeNode } from './utils.js'
4
+ export * from './utils.js'
package/src/ipld/nodes.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { createNode, PBNode } from '@ipld/dag-pb'
2
1
  import { CID } from 'multiformats/cid'
2
+ import { createNode, PBNode } from '../ipld/index.js'
3
3
  import { OffchainMetadata } from '../metadata/index.js'
4
4
  import { encodeIPLDNodeData, MetadataType } from '../metadata/onchain/index.js'
5
5
  import { DEFAULT_MAX_CHUNK_SIZE, ensureNodeMaxSize } from './chunker.js'
package/src/ipld/utils.ts CHANGED
@@ -1,15 +1,21 @@
1
- import { decode, encode, PBNode } from '@ipld/dag-pb'
1
+ import { createNode, decode, encode, PBNode } from '@ipld/dag-pb'
2
+ import { AwaitIterable } from 'interface-store'
2
3
 
3
- export const chunkBuffer = (buffer: Buffer, chunkSize: number) => {
4
- const chunks: Buffer[] = []
5
-
6
- for (let i = 0; i < buffer.length; i += chunkSize) {
7
- chunks.push(Buffer.from(buffer.buffer.slice(i, i + chunkSize)))
4
+ export const chunkBuffer = async function* (
5
+ buffer: AwaitIterable<Buffer>,
6
+ { maxChunkSize, ignoreLastChunk = false }: { maxChunkSize: number; ignoreLastChunk?: boolean },
7
+ ): AsyncIterable<Buffer> {
8
+ let target = Buffer.alloc(0)
9
+ for await (let chunk of buffer) {
10
+ target = Buffer.concat([target, chunk])
11
+ while (target.length >= maxChunkSize) {
12
+ yield target.subarray(0, maxChunkSize)
13
+ target = target.subarray(maxChunkSize)
14
+ }
15
+ }
16
+ if (target.length > 0 && !ignoreLastChunk) {
17
+ yield target
8
18
  }
9
-
10
- return chunks
11
19
  }
12
20
 
13
- export const encodeNode = (node: PBNode): Buffer => Buffer.from(encode(node))
14
-
15
- export const decodeNode = (data: Uint8Array): PBNode => decode(data)
21
+ export { createNode, decode as decodeNode, encode as encodeNode, PBNode }
@@ -1,4 +1,5 @@
1
- import { cidOfNode, cidToString, IPLDDag, IPLDNodeData, MetadataType } from '../../index.js'
1
+ import { CID } from 'multiformats'
2
+ import { cidToString } from '../../index.js'
2
3
 
3
4
  export type OffchainFileMetadata = {
4
5
  type: 'file'
@@ -16,25 +17,19 @@ export interface ChunkInfo {
16
17
  }
17
18
 
18
19
  export const fileMetadata = (
19
- dag: IPLDDag,
20
+ headCID: CID,
21
+ chunks: ChunkInfo[],
20
22
  totalSize: number,
21
- name?: string,
22
- mimeType?: string,
23
+ name?: string | null,
24
+ mimeType?: string | null,
23
25
  ): OffchainFileMetadata => {
24
- const chunks = Array.from(dag.nodes.values()).filter(
25
- (n) => n.Data && IPLDNodeData.decode(n.Data).data,
26
- )
27
-
28
26
  return {
29
27
  type: 'file',
30
- dataCid: cidToString(dag.headCID),
31
- name,
32
- mimeType,
28
+ dataCid: cidToString(headCID),
29
+ name: name ?? undefined,
30
+ mimeType: mimeType ?? undefined,
33
31
  totalSize,
34
32
  totalChunks: chunks.length,
35
- chunks: chunks.map((chunk) => ({
36
- cid: cidToString(cidOfNode(chunk)),
37
- size: chunk.Data?.length ?? 0,
38
- })),
33
+ chunks,
39
34
  }
40
35
  }
@@ -1,3 +1,8 @@
1
+ import { CID } from 'multiformats'
2
+ import { cidOfNode, cidToString } from '../../cid/index.js'
3
+ import { PBNode } from '../../ipld/index.js'
4
+ import { IPLDNodeData, MetadataType } from '../onchain/index.js'
5
+
1
6
  interface ChildrenMetadata {
2
7
  type: 'folder' | 'file'
3
8
  name?: string
@@ -14,17 +19,33 @@ export type OffchainFolderMetadata = {
14
19
  children: ChildrenMetadata[]
15
20
  }
16
21
 
22
+ export const childrenMetadataFromNode = (node: PBNode): ChildrenMetadata => {
23
+ const ipldData = IPLDNodeData.decode(node.Data!)
24
+ if (ipldData.type !== MetadataType.File && ipldData.type !== MetadataType.Folder) {
25
+ throw new Error('Invalid metadata type')
26
+ }
27
+
28
+ return {
29
+ type: ipldData.type === MetadataType.File ? 'file' : 'folder',
30
+ cid: cidToString(cidOfNode(node)),
31
+ totalSize: ipldData.size ?? 0,
32
+ name: ipldData.name,
33
+ }
34
+ }
35
+
17
36
  export const folderMetadata = (
18
- cid: string,
37
+ cid: CID | string,
19
38
  children: ChildrenMetadata[],
20
- name?: string,
39
+ name?: string | null,
21
40
  ): OffchainFolderMetadata => {
41
+ cid = typeof cid === 'string' ? cid : cidToString(cid)
42
+
22
43
  return {
23
44
  dataCid: cid,
24
45
  totalSize: children.reduce((acc, child) => acc + child.totalSize, 0),
25
46
  totalFiles: children.length,
26
47
  children,
27
48
  type: 'folder',
28
- name,
49
+ name: name ?? undefined,
29
50
  }
30
51
  }
@@ -1,4 +1,4 @@
1
- import { decode } from '@ipld/dag-pb'
1
+ import { decodeNode } from '../../ipld/index.js'
2
2
  import { IPLDNodeData } from '../onchain/index.js'
3
3
 
4
4
  export const encodeIPLDNodeData = (metadata: IPLDNodeData): Uint8Array => {
@@ -6,7 +6,7 @@ export const encodeIPLDNodeData = (metadata: IPLDNodeData): Uint8Array => {
6
6
  }
7
7
 
8
8
  export const decodeIPLDNodeData = (data: Uint8Array): IPLDNodeData => {
9
- const decoded = decode(data)
9
+ const decoded = decodeNode(data)
10
10
  if (!decoded.Data) {
11
11
  throw new Error('Invalid data')
12
12
  }