@autonomys/auto-dag-data 1.0.6 → 1.0.7

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.
@@ -3,41 +3,71 @@ import type { AwaitIterable } from 'interface-store'
3
3
  import { CID } from 'multiformats'
4
4
  import { cidOfNode } from '../cid/index.js'
5
5
  import { decodeIPLDNodeData, FileUploadOptions, OffchainMetadata } from '../metadata/index.js'
6
+ import { stringifyMetadata } from '../utils/metadata.js'
6
7
  import { Builders, fileBuilders, metadataBuilders } from './builders.js'
7
8
  import { createFolderInlinkIpldNode, createFolderIpldNode } from './nodes.js'
8
9
  import { chunkBuffer, encodeNode, PBNode } from './utils.js'
9
10
 
10
11
  type ChunkerLimits = {
11
- maxChunkSize: number
12
+ maxNodeSize: number
12
13
  maxLinkPerNode: number
13
14
  }
14
15
 
15
16
  type ChunkerOptions = ChunkerLimits & FileUploadOptions
16
17
 
17
- export const DEFAULT_MAX_CHUNK_SIZE = 64 * 1024
18
+ const DEFAULT_NODE_MAX_SIZE = 65535
18
19
 
19
- const ESTIMATED_LINK_SIZE_IN_BYTES = 64
20
- export const DEFAULT_MAX_LINK_PER_NODE = DEFAULT_MAX_CHUNK_SIZE / ESTIMATED_LINK_SIZE_IN_BYTES
20
+ // u8 -> 1 byte (may grow in the future but unlikely further than 255)
21
+ const NODE_TYPE_SIZE = 1
22
+ // u32 -> 4 bytes
23
+ const NODE_LINK_DEPTH_SIZE = 4
24
+ // u64 -> 8 bytes
25
+ const NODE_SIZE_SIZE = 8
26
+ // Limit at 255 string length (Mac Limit)
27
+ const MAX_NAME_SIZE = 255
28
+ const END_OF_STRING_BYTE = 1
29
+ const NODE_NAME_SIZE = MAX_NAME_SIZE + END_OF_STRING_BYTE
30
+ // Upload options may be amplified in the future
31
+ const NODE_UPLOAD_OPTIONS_SIZE = 100
32
+ // Reserve 100 bytes for future use
33
+ const NODE_RESERVED_SIZE = 100
34
+
35
+ export const NODE_METADATA_SIZE =
36
+ NODE_TYPE_SIZE +
37
+ NODE_LINK_DEPTH_SIZE +
38
+ NODE_SIZE_SIZE +
39
+ NODE_NAME_SIZE +
40
+ NODE_RESERVED_SIZE +
41
+ NODE_UPLOAD_OPTIONS_SIZE
42
+
43
+ export const DEFAULT_MAX_CHUNK_SIZE = DEFAULT_NODE_MAX_SIZE - NODE_METADATA_SIZE
44
+
45
+ export const LINK_SIZE_IN_BYTES = 40
46
+ export const DEFAULT_MAX_LINK_PER_NODE = Math.floor(DEFAULT_MAX_CHUNK_SIZE / LINK_SIZE_IN_BYTES)
21
47
 
22
48
  export const processFileToIPLDFormat = (
23
49
  blockstore: BaseBlockstore,
24
50
  file: AwaitIterable<Buffer>,
25
- totalSize: number,
51
+ totalSize: bigint,
26
52
  filename?: string,
27
53
  {
28
- maxChunkSize = DEFAULT_MAX_CHUNK_SIZE,
54
+ maxNodeSize = DEFAULT_MAX_CHUNK_SIZE,
29
55
  maxLinkPerNode = DEFAULT_MAX_LINK_PER_NODE,
30
56
  encryption = undefined,
31
57
  compression = undefined,
32
58
  }: Partial<ChunkerOptions> = {
33
- maxChunkSize: DEFAULT_MAX_CHUNK_SIZE,
59
+ maxNodeSize: DEFAULT_MAX_CHUNK_SIZE,
34
60
  maxLinkPerNode: DEFAULT_MAX_LINK_PER_NODE,
35
61
  encryption: undefined,
36
62
  compression: undefined,
37
63
  },
38
64
  ): Promise<CID> => {
65
+ if (filename && filename.length > MAX_NAME_SIZE) {
66
+ throw new Error(`Filename is too long: ${filename.length} > ${MAX_NAME_SIZE}`)
67
+ }
68
+
39
69
  return processBufferToIPLDFormat(blockstore, file, filename, totalSize, fileBuilders, {
40
- maxChunkSize,
70
+ maxNodeSize,
41
71
  maxLinkPerNode,
42
72
  encryption,
43
73
  compression,
@@ -47,20 +77,24 @@ export const processFileToIPLDFormat = (
47
77
  export const processMetadataToIPLDFormat = async (
48
78
  blockstore: BaseBlockstore,
49
79
  metadata: OffchainMetadata,
50
- limits: { maxChunkSize: number; maxLinkPerNode: number } = {
51
- maxChunkSize: DEFAULT_MAX_CHUNK_SIZE,
80
+ limits: { maxNodeSize: number; maxLinkPerNode: number } = {
81
+ maxNodeSize: DEFAULT_MAX_CHUNK_SIZE,
52
82
  maxLinkPerNode: DEFAULT_MAX_LINK_PER_NODE,
53
83
  },
54
84
  ): Promise<CID> => {
55
- const buffer = Buffer.from(JSON.stringify(metadata))
56
- const name = `${metadata.name}.metadata.json`
85
+ if (metadata.name && metadata.name.length > MAX_NAME_SIZE) {
86
+ throw new Error(`Filename is too long: ${metadata.name.length} > ${MAX_NAME_SIZE}`)
87
+ }
88
+
89
+ const buffer = Buffer.from(stringifyMetadata(metadata))
90
+
57
91
  return processBufferToIPLDFormat(
58
92
  blockstore,
59
93
  (async function* () {
60
94
  yield buffer
61
95
  })(),
62
- name,
63
- buffer.byteLength,
96
+ metadata.name,
97
+ BigInt(buffer.byteLength),
64
98
  metadataBuilders,
65
99
  limits,
66
100
  )
@@ -70,21 +104,25 @@ const processBufferToIPLDFormat = async (
70
104
  blockstore: BaseBlockstore,
71
105
  buffer: AwaitIterable<Buffer>,
72
106
  filename: string | undefined,
73
- totalSize: number,
107
+ totalSize: bigint,
74
108
  builders: Builders,
75
109
  {
76
- maxChunkSize = DEFAULT_MAX_CHUNK_SIZE,
110
+ maxNodeSize: maxNodeSize = DEFAULT_MAX_CHUNK_SIZE,
77
111
  maxLinkPerNode = DEFAULT_MAX_LINK_PER_NODE,
78
112
  encryption = undefined,
79
113
  compression = undefined,
80
114
  }: ChunkerOptions = {
81
- maxChunkSize: DEFAULT_MAX_CHUNK_SIZE,
115
+ maxNodeSize: DEFAULT_MAX_CHUNK_SIZE,
82
116
  maxLinkPerNode: DEFAULT_MAX_LINK_PER_NODE,
83
117
  encryption: undefined,
84
118
  compression: undefined,
85
119
  },
86
120
  ): Promise<CID> => {
87
- const bufferChunks = chunkBuffer(buffer, { maxChunkSize: maxChunkSize })
121
+ if (filename && filename.length > MAX_NAME_SIZE) {
122
+ throw new Error(`Filename is too long: ${filename.length} > ${MAX_NAME_SIZE}`)
123
+ }
124
+
125
+ const bufferChunks = chunkBuffer(buffer, { maxChunkSize: maxNodeSize - NODE_METADATA_SIZE })
88
126
 
89
127
  let CIDs: CID[] = []
90
128
  for await (const chunk of bufferChunks) {
@@ -96,7 +134,7 @@ const processBufferToIPLDFormat = async (
96
134
 
97
135
  return processBufferToIPLDFormatFromChunks(blockstore, CIDs, filename, totalSize, builders, {
98
136
  maxLinkPerNode,
99
- maxChunkSize,
137
+ maxNodeSize,
100
138
  encryption,
101
139
  compression,
102
140
  })
@@ -106,20 +144,24 @@ export const processBufferToIPLDFormatFromChunks = async (
106
144
  blockstore: BaseBlockstore,
107
145
  chunks: AwaitIterable<CID>,
108
146
  filename: string | undefined,
109
- totalSize: number,
147
+ totalSize: bigint,
110
148
  builders: Builders,
111
149
  {
112
- maxChunkSize = DEFAULT_MAX_CHUNK_SIZE,
150
+ maxNodeSize: maxNodeSize = DEFAULT_MAX_CHUNK_SIZE,
113
151
  maxLinkPerNode = DEFAULT_MAX_LINK_PER_NODE,
114
152
  encryption = undefined,
115
153
  compression = undefined,
116
154
  }: Partial<ChunkerOptions> = {
117
- maxChunkSize: DEFAULT_MAX_CHUNK_SIZE,
155
+ maxNodeSize: DEFAULT_MAX_CHUNK_SIZE,
118
156
  maxLinkPerNode: DEFAULT_MAX_LINK_PER_NODE,
119
157
  encryption: undefined,
120
158
  compression: undefined,
121
159
  },
122
160
  ): Promise<CID> => {
161
+ if (filename && filename.length > MAX_NAME_SIZE) {
162
+ throw new Error(`Filename is too long: ${filename.length} > ${MAX_NAME_SIZE}`)
163
+ }
164
+
123
165
  let chunkCount = 0
124
166
  let CIDs: CID[] = []
125
167
  for await (const chunk of chunks) {
@@ -147,7 +189,7 @@ export const processBufferToIPLDFormatFromChunks = async (
147
189
  for (let i = 0; i < CIDs.length; i += maxLinkPerNode) {
148
190
  const chunk = CIDs.slice(i, i + maxLinkPerNode)
149
191
 
150
- const node = builders.inlink(chunk, chunk.length, depth, maxChunkSize)
192
+ const node = builders.inlink(chunk, chunk.length, depth, maxNodeSize)
151
193
  const cid = cidOfNode(node)
152
194
  await blockstore.put(cid, encodeNode(node))
153
195
  newCIDs.push(cid)
@@ -155,7 +197,7 @@ export const processBufferToIPLDFormatFromChunks = async (
155
197
  depth++
156
198
  CIDs = newCIDs
157
199
  }
158
- const head = builders.root(CIDs, totalSize, depth, filename, maxChunkSize, {
200
+ const head = builders.root(CIDs, totalSize, depth, filename, maxNodeSize, {
159
201
  compression,
160
202
  encryption,
161
203
  })
@@ -169,19 +211,23 @@ export const processFolderToIPLDFormat = async (
169
211
  blockstore: BaseBlockstore,
170
212
  children: CID[],
171
213
  name: string,
172
- size: number,
214
+ size: bigint,
173
215
  {
174
216
  maxLinkPerNode = DEFAULT_MAX_LINK_PER_NODE,
175
- maxChunkSize = DEFAULT_MAX_CHUNK_SIZE,
217
+ maxNodeSize: maxNodeSize = DEFAULT_MAX_CHUNK_SIZE,
176
218
  compression = undefined,
177
219
  encryption = undefined,
178
220
  }: Partial<ChunkerOptions> = {
179
221
  maxLinkPerNode: DEFAULT_MAX_LINK_PER_NODE,
180
- maxChunkSize: DEFAULT_MAX_CHUNK_SIZE,
222
+ maxNodeSize: DEFAULT_MAX_CHUNK_SIZE,
181
223
  compression: undefined,
182
224
  encryption: undefined,
183
225
  },
184
226
  ): Promise<CID> => {
227
+ if (name.length > MAX_NAME_SIZE) {
228
+ throw new Error(`Filename is too long: ${name.length} > ${MAX_NAME_SIZE}`)
229
+ }
230
+
185
231
  let cids = children
186
232
  let depth = 0
187
233
  while (cids.length > maxLinkPerNode) {
@@ -197,7 +243,7 @@ export const processFolderToIPLDFormat = async (
197
243
  depth++
198
244
  }
199
245
 
200
- const node = createFolderIpldNode(cids, name, depth, size, maxChunkSize, {
246
+ const node = createFolderIpldNode(cids, name, depth, size, maxNodeSize, {
201
247
  compression,
202
248
  encryption,
203
249
  })
@@ -215,12 +261,15 @@ export const processChunksToIPLDFormat = async (
215
261
  blockstore: BaseBlockstore,
216
262
  chunks: AwaitIterable<Buffer>,
217
263
  builders: Builders,
218
- { maxChunkSize = DEFAULT_MAX_CHUNK_SIZE }: { maxChunkSize?: number },
264
+ { maxNodeSize = DEFAULT_MAX_CHUNK_SIZE }: { maxNodeSize?: number },
219
265
  ): Promise<Buffer> => {
220
- const bufferChunks = chunkBuffer(chunks, { maxChunkSize, ignoreLastChunk: false })
266
+ const bufferChunks = chunkBuffer(chunks, {
267
+ maxChunkSize: maxNodeSize - NODE_METADATA_SIZE,
268
+ ignoreLastChunk: false,
269
+ })
221
270
 
222
271
  for await (const chunk of bufferChunks) {
223
- if (chunk.byteLength < maxChunkSize) {
272
+ if (chunk.byteLength < maxNodeSize) {
224
273
  return chunk
225
274
  }
226
275
 
package/src/ipld/nodes.ts CHANGED
@@ -1,19 +1,26 @@
1
1
  import { CID } from 'multiformats/cid'
2
2
  import { FileUploadOptions, OffchainMetadata } from '../metadata/index.js'
3
3
  import { encodeIPLDNodeData, MetadataType } from '../metadata/onchain/index.js'
4
+ import { stringifyMetadata } from '../utils/metadata.js'
4
5
  import { DEFAULT_MAX_CHUNK_SIZE, ensureNodeMaxSize } from './chunker.js'
5
6
  import { createNode, PBNode } from './index.js'
6
7
 
7
8
  /// Creates a file chunk ipld node
8
- export const createFileChunkIpldNode = (data: Buffer): PBNode =>
9
- createNode(
10
- encodeIPLDNodeData({
11
- type: MetadataType.FileChunk,
12
- size: data.length,
13
- linkDepth: 0,
14
- data,
15
- }),
16
- [],
9
+ export const createFileChunkIpldNode = (
10
+ data: Buffer,
11
+ maxNodeSize: number = DEFAULT_MAX_CHUNK_SIZE,
12
+ ): PBNode =>
13
+ ensureNodeMaxSize(
14
+ createNode(
15
+ encodeIPLDNodeData({
16
+ type: MetadataType.FileChunk,
17
+ size: BigInt(data.length).valueOf(),
18
+ linkDepth: 0,
19
+ data,
20
+ }),
21
+ [],
22
+ ),
23
+ maxNodeSize,
17
24
  )
18
25
 
19
26
  // Creates a file ipld node
@@ -21,7 +28,7 @@ export const createFileChunkIpldNode = (data: Buffer): PBNode =>
21
28
  // @todo: add the file's metadata
22
29
  export const createChunkedFileIpldNode = (
23
30
  links: CID[],
24
- size: number,
31
+ size: bigint,
25
32
  linkDepth: number,
26
33
  name?: string,
27
34
  maxNodeSize: number = DEFAULT_MAX_CHUNK_SIZE,
@@ -52,7 +59,7 @@ export const createFileInlinkIpldNode = (
52
59
  createNode(
53
60
  encodeIPLDNodeData({
54
61
  type: MetadataType.FileInlink,
55
- size,
62
+ size: BigInt(size).valueOf(),
56
63
  linkDepth,
57
64
  }),
58
65
  links.map((cid) => ({ Hash: cid })),
@@ -67,17 +74,21 @@ export const createSingleFileIpldNode = (
67
74
  data: Buffer,
68
75
  name?: string,
69
76
  uploadOptions?: FileUploadOptions,
77
+ maxNodeSize: number = DEFAULT_MAX_CHUNK_SIZE,
70
78
  ): PBNode =>
71
- createNode(
72
- encodeIPLDNodeData({
73
- type: MetadataType.File,
74
- name,
75
- size: data.length,
76
- linkDepth: 0,
77
- data,
78
- uploadOptions,
79
- }),
80
- [],
79
+ ensureNodeMaxSize(
80
+ createNode(
81
+ encodeIPLDNodeData({
82
+ type: MetadataType.File,
83
+ name,
84
+ size: BigInt(data.length).valueOf(),
85
+ linkDepth: 0,
86
+ data,
87
+ uploadOptions,
88
+ }),
89
+ [],
90
+ ),
91
+ maxNodeSize,
81
92
  )
82
93
 
83
94
  // Creates a file ipld node
@@ -93,7 +104,7 @@ export const createMetadataInlinkIpldNode = (
93
104
  createNode(
94
105
  encodeIPLDNodeData({
95
106
  type: MetadataType.FileInlink,
96
- size,
107
+ size: BigInt(size).valueOf(),
97
108
  linkDepth,
98
109
  }),
99
110
  links.map((cid) => ({ Hash: cid })),
@@ -109,26 +120,32 @@ export const createSingleMetadataIpldNode = (data: Buffer, name?: string): PBNod
109
120
  encodeIPLDNodeData({
110
121
  type: MetadataType.Metadata,
111
122
  name,
112
- size: data.length,
123
+ size: BigInt(data.length).valueOf(),
113
124
  linkDepth: 0,
114
125
  data,
115
126
  }),
116
127
  [],
117
128
  )
118
129
 
119
- export const createMetadataChunkIpldNode = (data: Buffer): PBNode =>
120
- createNode(
121
- encodeIPLDNodeData({
122
- type: MetadataType.MetadataChunk,
123
- size: data.length,
124
- linkDepth: 0,
125
- data,
126
- }),
130
+ export const createMetadataChunkIpldNode = (
131
+ data: Buffer,
132
+ maxNodeSize: number = DEFAULT_MAX_CHUNK_SIZE,
133
+ ): PBNode =>
134
+ ensureNodeMaxSize(
135
+ createNode(
136
+ encodeIPLDNodeData({
137
+ type: MetadataType.MetadataChunk,
138
+ size: BigInt(data.length).valueOf(),
139
+ linkDepth: 0,
140
+ data,
141
+ }),
142
+ ),
143
+ maxNodeSize,
127
144
  )
128
145
 
129
146
  export const createChunkedMetadataIpldNode = (
130
147
  links: CID[],
131
- size: number,
148
+ size: bigint,
132
149
  linkDepth: number,
133
150
  name?: string,
134
151
  maxNodeSize: number = DEFAULT_MAX_CHUNK_SIZE,
@@ -153,7 +170,7 @@ export const createFolderIpldNode = (
153
170
  links: CID[],
154
171
  name: string,
155
172
  linkDepth: number,
156
- size: number,
173
+ size: bigint,
157
174
  maxNodeSize: number = DEFAULT_MAX_CHUNK_SIZE,
158
175
  uploadOptions?: FileUploadOptions,
159
176
  ): PBNode =>
@@ -192,7 +209,7 @@ export const createMetadataNode = (
192
209
  metadata: OffchainMetadata,
193
210
  maxNodeSize: number = DEFAULT_MAX_CHUNK_SIZE,
194
211
  ): PBNode => {
195
- const data = Buffer.from(JSON.stringify(metadata))
212
+ const data = Buffer.from(stringifyMetadata(metadata))
196
213
 
197
214
  return ensureNodeMaxSize(
198
215
  createNode(
@@ -6,21 +6,21 @@ export type OffchainFileMetadata = {
6
6
  dataCid: string
7
7
  name?: string
8
8
  mimeType?: string
9
- totalSize: number
9
+ totalSize: bigint
10
10
  totalChunks: number
11
11
  chunks: ChunkInfo[]
12
12
  uploadOptions?: FileUploadOptions
13
13
  }
14
14
 
15
15
  export interface ChunkInfo {
16
- size: number
16
+ size: bigint
17
17
  cid: string
18
18
  }
19
19
 
20
20
  export const fileMetadata = (
21
21
  headCID: CID,
22
22
  chunks: ChunkInfo[],
23
- totalSize: number,
23
+ totalSize: bigint,
24
24
  name?: string | null,
25
25
  mimeType?: string | null,
26
26
  uploadOptions: FileUploadOptions = {
@@ -7,14 +7,14 @@ interface ChildrenMetadata {
7
7
  type: 'folder' | 'file'
8
8
  name?: string
9
9
  cid: string
10
- totalSize: number
10
+ totalSize: bigint
11
11
  }
12
12
 
13
13
  export type OffchainFolderMetadata = {
14
14
  type: 'folder'
15
15
  dataCid: string
16
16
  name?: string
17
- totalSize: number
17
+ totalSize: bigint
18
18
  totalFiles: number
19
19
  children: ChildrenMetadata[]
20
20
  uploadOptions: FileUploadOptions
@@ -29,7 +29,7 @@ export const childrenMetadataFromNode = (node: PBNode): ChildrenMetadata => {
29
29
  return {
30
30
  type: ipldData.type === MetadataType.File ? 'file' : 'folder',
31
31
  cid: cidToString(cidOfNode(node)),
32
- totalSize: ipldData.size ?? 0,
32
+ totalSize: ipldData.size ?? BigInt(0).valueOf(),
33
33
  name: ipldData.name,
34
34
  }
35
35
  }
@@ -44,7 +44,7 @@ export const folderMetadata = (
44
44
 
45
45
  return {
46
46
  dataCid: cid,
47
- totalSize: children.reduce((acc, child) => acc + child.totalSize, 0),
47
+ totalSize: children.reduce((acc, child) => acc + child.totalSize, BigInt(0).valueOf()),
48
48
  totalFiles: children.length,
49
49
  children,
50
50
  type: 'folder',
@@ -1,12 +1,13 @@
1
1
  syntax = "proto3";
2
2
 
3
3
  message IPLDNodeData {
4
- MetadataType type = 1;
5
- int32 linkDepth = 2;
6
- optional int32 size = 3;
7
- optional string name = 4;
8
- optional bytes data = 5;
9
- optional FileUploadOptions uploadOptions = 6;
4
+ MetadataType type = 1; // maxLength = 1
5
+ int32 linkDepth = 2; // maxLength = 4
6
+ optional int64 size = 3; // maxLength = 8
7
+ optional string name = 4; // maxLength = 256
8
+ optional bytes data = 5; // maxLength = XXX
9
+ optional FileUploadOptions uploadOptions = 6; // maxLength = 100
10
+ /// Reserve 100 bytes for future use
10
11
  }
11
12
 
12
13
  // MetadataType defines the possible types of metadata.
@@ -10,7 +10,7 @@ import type { Uint8ArrayList } from 'uint8arraylist'
10
10
  export interface IPLDNodeData {
11
11
  type: MetadataType
12
12
  linkDepth: number
13
- size?: number
13
+ size?: bigint
14
14
  name?: string
15
15
  data?: Uint8Array
16
16
  uploadOptions?: FileUploadOptions
@@ -38,7 +38,7 @@ export namespace IPLDNodeData {
38
38
 
39
39
  if (obj.size != null) {
40
40
  w.uint32(24)
41
- w.int32(obj.size)
41
+ w.int64(obj.size)
42
42
  }
43
43
 
44
44
  if (obj.name != null) {
@@ -80,7 +80,7 @@ export namespace IPLDNodeData {
80
80
  break
81
81
  }
82
82
  case 3: {
83
- obj.size = reader.int32()
83
+ obj.size = reader.int64()
84
84
  break
85
85
  }
86
86
  case 4: {
@@ -0,0 +1,6 @@
1
+ import { OffchainMetadata } from '../metadata/index.js'
2
+
3
+ export const stringifyMetadata = (metadata: OffchainMetadata): string =>
4
+ JSON.stringify(metadata, (_, v) =>
5
+ typeof v === 'bigint' || v instanceof BigInt ? v.toString() : v,
6
+ )