@helia/unixfs 7.0.4 → 7.1.0-080e4f91

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 (97) hide show
  1. package/dist/index.min.js +4 -4
  2. package/dist/index.min.js.map +4 -4
  3. package/dist/src/commands/add.d.ts +7 -7
  4. package/dist/src/commands/add.d.ts.map +1 -1
  5. package/dist/src/commands/add.js +12 -4
  6. package/dist/src/commands/add.js.map +1 -1
  7. package/dist/src/commands/cat.d.ts +3 -3
  8. package/dist/src/commands/cat.d.ts.map +1 -1
  9. package/dist/src/commands/cat.js +2 -2
  10. package/dist/src/commands/cat.js.map +1 -1
  11. package/dist/src/commands/chmod.d.ts +3 -3
  12. package/dist/src/commands/chmod.d.ts.map +1 -1
  13. package/dist/src/commands/chmod.js +3 -3
  14. package/dist/src/commands/chmod.js.map +1 -1
  15. package/dist/src/commands/cp.d.ts +3 -3
  16. package/dist/src/commands/cp.d.ts.map +1 -1
  17. package/dist/src/commands/cp.js +4 -4
  18. package/dist/src/commands/cp.js.map +1 -1
  19. package/dist/src/commands/ls.d.ts +3 -3
  20. package/dist/src/commands/ls.d.ts.map +1 -1
  21. package/dist/src/commands/ls.js +1 -1
  22. package/dist/src/commands/ls.js.map +1 -1
  23. package/dist/src/commands/mkdir.d.ts +3 -3
  24. package/dist/src/commands/mkdir.d.ts.map +1 -1
  25. package/dist/src/commands/mkdir.js +4 -4
  26. package/dist/src/commands/mkdir.js.map +1 -1
  27. package/dist/src/commands/rm.d.ts +3 -3
  28. package/dist/src/commands/rm.d.ts.map +1 -1
  29. package/dist/src/commands/rm.js +3 -3
  30. package/dist/src/commands/rm.js.map +1 -1
  31. package/dist/src/commands/stat.d.ts +2 -2
  32. package/dist/src/commands/stat.js +2 -2
  33. package/dist/src/commands/touch.d.ts +3 -3
  34. package/dist/src/commands/touch.d.ts.map +1 -1
  35. package/dist/src/commands/touch.js +3 -3
  36. package/dist/src/commands/touch.js.map +1 -1
  37. package/dist/src/commands/utils/add-link.d.ts +4 -6
  38. package/dist/src/commands/utils/add-link.d.ts.map +1 -1
  39. package/dist/src/commands/utils/add-link.js +23 -17
  40. package/dist/src/commands/utils/add-link.js.map +1 -1
  41. package/dist/src/commands/utils/cid-to-directory.d.ts +1 -1
  42. package/dist/src/commands/utils/cid-to-directory.js +1 -1
  43. package/dist/src/commands/utils/cid-to-pblink.d.ts +1 -1
  44. package/dist/src/commands/utils/cid-to-pblink.js +1 -1
  45. package/dist/src/commands/utils/hamt-utils.d.ts +9 -8
  46. package/dist/src/commands/utils/hamt-utils.d.ts.map +1 -1
  47. package/dist/src/commands/utils/hamt-utils.js +18 -16
  48. package/dist/src/commands/utils/hamt-utils.js.map +1 -1
  49. package/dist/src/commands/utils/is-over-shard-threshold.d.ts +6 -2
  50. package/dist/src/commands/utils/is-over-shard-threshold.d.ts.map +1 -1
  51. package/dist/src/commands/utils/is-over-shard-threshold.js +58 -13
  52. package/dist/src/commands/utils/is-over-shard-threshold.js.map +1 -1
  53. package/dist/src/commands/utils/persist.d.ts +3 -3
  54. package/dist/src/commands/utils/persist.d.ts.map +1 -1
  55. package/dist/src/commands/utils/remove-link.d.ts +5 -6
  56. package/dist/src/commands/utils/remove-link.d.ts.map +1 -1
  57. package/dist/src/commands/utils/remove-link.js +10 -9
  58. package/dist/src/commands/utils/remove-link.js.map +1 -1
  59. package/dist/src/commands/utils/resolve.d.ts +1 -1
  60. package/dist/src/commands/utils/resolve.js +4 -4
  61. package/dist/src/index.d.ts +24 -54
  62. package/dist/src/index.d.ts.map +1 -1
  63. package/dist/src/index.js +3 -3
  64. package/dist/src/index.js.map +1 -1
  65. package/dist/src/unixfs.d.ts +1 -1
  66. package/dist/src/unixfs.js +9 -9
  67. package/dist/src/utils/glob-source.js +2 -2
  68. package/dist/src/utils/url-source.d.ts +1 -1
  69. package/dist/src/utils/url-source.js +1 -1
  70. package/package.json +5 -6
  71. package/src/commands/add.ts +19 -11
  72. package/src/commands/cat.ts +5 -5
  73. package/src/commands/chmod.ts +6 -6
  74. package/src/commands/cp.ts +7 -7
  75. package/src/commands/ls.ts +4 -4
  76. package/src/commands/mkdir.ts +7 -7
  77. package/src/commands/rm.ts +6 -6
  78. package/src/commands/stat.ts +4 -4
  79. package/src/commands/touch.ts +6 -6
  80. package/src/commands/utils/add-link.ts +30 -24
  81. package/src/commands/utils/cid-to-directory.ts +2 -2
  82. package/src/commands/utils/cid-to-pblink.ts +2 -2
  83. package/src/commands/utils/hamt-utils.ts +29 -23
  84. package/src/commands/utils/is-over-shard-threshold.ts +88 -13
  85. package/src/commands/utils/persist.ts +3 -3
  86. package/src/commands/utils/remove-link.ts +17 -17
  87. package/src/commands/utils/resolve.ts +5 -5
  88. package/src/index.ts +25 -61
  89. package/src/unixfs.ts +10 -10
  90. package/src/utils/glob-source.ts +2 -2
  91. package/src/utils/url-source.ts +2 -2
  92. package/dist/src/commands/utils/dir-sharded.d.ts +0 -74
  93. package/dist/src/commands/utils/dir-sharded.d.ts.map +0 -1
  94. package/dist/src/commands/utils/dir-sharded.js +0 -235
  95. package/dist/src/commands/utils/dir-sharded.js.map +0 -1
  96. package/dist/typedoc-urls.json +0 -76
  97. package/src/commands/utils/dir-sharded.ts +0 -321
@@ -7,23 +7,21 @@ import { sha256 } from 'multiformats/hashes/sha2'
7
7
  // @ts-expect-error no types
8
8
  import SparseArray from 'sparse-array'
9
9
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
10
- import { DEFAULT_SHARD_SPLIT_THRESHOLD_BYTES } from '../../constants.ts'
11
- import { AlreadyExistsError, InvalidParametersError, InvalidPBNodeError } from '../../errors.js'
12
- import { wrapHash } from './consumable-hash.js'
13
- import { hamtBucketBits, hamtHashFn } from './hamt-constants.js'
10
+ import { AlreadyExistsError, InvalidParametersError, InvalidPBNodeError } from '../../errors.ts'
11
+ import { wrapHash } from './consumable-hash.ts'
12
+ import { hamtBucketBits, hamtHashFn } from './hamt-constants.ts'
14
13
  import {
15
14
  createShard,
16
15
  recreateShardedDirectory,
17
16
  toPrefix,
18
17
  updateShardedDirectory
19
- } from './hamt-utils.js'
20
- import { isOverShardThreshold } from './is-over-shard-threshold.js'
21
- import type { Directory } from './cid-to-directory.js'
22
- import type { GetStore, PutStore } from '../../unixfs.js'
18
+ } from './hamt-utils.ts'
19
+ import { isOverShardThreshold } from './is-over-shard-threshold.ts'
20
+ import type { Directory } from './cid-to-directory.ts'
21
+ import type { GetStore, PutStore } from '../../unixfs.ts'
23
22
  import type { PBNode, PBLink } from '@ipld/dag-pb'
24
23
  import type { AbortOptions } from '@libp2p/interface'
25
- import type { ImportResult } from 'ipfs-unixfs-importer'
26
- import type { Version } from 'multiformats/cid'
24
+ import type { ImporterOptions, ImportResult } from 'ipfs-unixfs-importer'
27
25
 
28
26
  const log = logger('helia:unixfs:components:utils:add-link')
29
27
 
@@ -32,10 +30,8 @@ export interface AddLinkResult {
32
30
  cid: CID
33
31
  }
34
32
 
35
- export interface AddLinkOptions extends AbortOptions {
33
+ export interface AddLinkOptions extends AbortOptions, Pick<ImporterOptions, 'profile' | 'shardSplitThresholdBytes' | 'shardSplitStrategy' | 'shardFanoutBits' | 'cidVersion'> {
36
34
  allowOverwriting?: boolean
37
- shardSplitThresholdBytes?: number
38
- cidVersion?: Version
39
35
  }
40
36
 
41
37
  export async function addLink (parent: Directory, child: Required<PBLink>, blockstore: GetStore & PutStore, options: AddLinkOptions): Promise<AddLinkResult> {
@@ -55,10 +51,10 @@ export async function addLink (parent: Directory, child: Required<PBLink>, block
55
51
 
56
52
  const result = await addToDirectory(parent, child, blockstore, options)
57
53
 
58
- if (await isOverShardThreshold(result.node, blockstore, options.shardSplitThresholdBytes ?? DEFAULT_SHARD_SPLIT_THRESHOLD_BYTES, options)) {
54
+ if (await isOverShardThreshold(result.node, blockstore, options)) {
59
55
  log('converting directory to sharded directory')
60
56
 
61
- const converted = await convertToShardedDirectory(result, blockstore)
57
+ const converted = await convertToShardedDirectory(result, blockstore, options)
62
58
  result.cid = converted.cid
63
59
  result.node = dagPB.decode(await toBuffer(blockstore.get(converted.cid, options)))
64
60
  }
@@ -66,7 +62,7 @@ export async function addLink (parent: Directory, child: Required<PBLink>, block
66
62
  return result
67
63
  }
68
64
 
69
- const convertToShardedDirectory = async (parent: Directory, blockstore: PutStore): Promise<ImportResult> => {
65
+ const convertToShardedDirectory = async (parent: Directory, blockstore: PutStore, options: AddLinkOptions): Promise<ImportResult> => {
70
66
  if (parent.node.Data == null) {
71
67
  throw new InvalidParametersError('Invalid parent passed to convertToShardedDirectory')
72
68
  }
@@ -78,6 +74,7 @@ const convertToShardedDirectory = async (parent: Directory, blockstore: PutStore
78
74
  size: BigInt(link.Tsize ?? 0),
79
75
  cid: link.Hash
80
76
  })), {
77
+ ...options,
81
78
  mode: unixfs.mode,
82
79
  mtime: unixfs.mtime,
83
80
  cidVersion: parent.cid.version
@@ -130,9 +127,11 @@ const addToDirectory = async (parent: Directory, child: PBLink, blockstore: PutS
130
127
  // Persist the new parent PbNode
131
128
  const buf = dagPB.encode(parent.node)
132
129
  const hash = await sha256.digest(buf)
130
+ options?.signal?.throwIfAborted()
131
+
133
132
  const cid = CID.create(parent.cid.version, dagPB.code, hash)
134
133
 
135
- await blockstore.put(cid, buf)
134
+ await blockstore.put(cid, buf, options)
136
135
 
137
136
  return {
138
137
  node: parent.node,
@@ -157,6 +156,7 @@ const addToShardedDirectory = async (parent: Directory, child: Required<PBLink>,
157
156
 
158
157
  const linkName = `${prefix}${child.Name}`
159
158
  const existingLink = finalSegment.node.Links.find(l => (l.Name ?? '').startsWith(prefix))
159
+ const prefixLength = prefix.length
160
160
 
161
161
  if (existingLink != null) {
162
162
  log('link %s was present in shard', linkName)
@@ -175,7 +175,7 @@ const addToShardedDirectory = async (parent: Directory, child: Required<PBLink>,
175
175
  Hash: child.Hash,
176
176
  Tsize: child.Tsize
177
177
  })
178
- } else if (existingLink.Name?.length === 2) {
178
+ } else if (existingLink.Name?.length === prefixLength) {
179
179
  throw new Error('Existing link was sub-shard?!')
180
180
  } else {
181
181
  // conflict, add a new HAMT segment
@@ -185,23 +185,29 @@ const addToShardedDirectory = async (parent: Directory, child: Required<PBLink>,
185
185
  const sibling = finalSegment.node.Links.splice(index, 1)[0]
186
186
 
187
187
  // give the sibling a new HAMT prefix
188
- const siblingName = (sibling.Name ?? '').substring(2)
188
+ const siblingName = (sibling.Name ?? '').substring(prefixLength)
189
189
  const wrapped = wrapHash(hamtHashFn)
190
190
  const siblingHash = wrapped(uint8ArrayFromString(siblingName))
191
+ const hashBits = options.shardFanoutBits ?? hamtBucketBits
191
192
 
192
193
  // discard hash bits until we reach the sub-shard depth
193
194
  for (let i = 0; i < path.length; i++) {
194
- await siblingHash.take(hamtBucketBits)
195
+ await siblingHash.take(hashBits)
196
+ options?.signal?.throwIfAborted()
195
197
  }
196
198
 
197
199
  while (true) {
198
- const siblingIndex = await siblingHash.take(hamtBucketBits)
199
- const siblingPrefix = toPrefix(siblingIndex)
200
+ const siblingIndex = await siblingHash.take(hashBits)
201
+ options?.signal?.throwIfAborted()
202
+
203
+ const siblingPrefix = toPrefix(siblingIndex, hashBits)
200
204
  sibling.Name = `${siblingPrefix}${siblingName}`
201
205
 
202
206
  // calculate the target file's HAMT prefix in the new sub-shard
203
- const newIndex = await hash.take(hamtBucketBits)
204
- const newPrefix = toPrefix(newIndex)
207
+ const newIndex = await hash.take(hashBits)
208
+ options?.signal?.throwIfAborted()
209
+
210
+ const newPrefix = toPrefix(newIndex, hashBits)
205
211
 
206
212
  if (siblingPrefix === newPrefix) {
207
213
  // the two sibling names have caused another conflict - add an intermediate node to
@@ -1,6 +1,6 @@
1
1
  import { exporter } from 'ipfs-unixfs-exporter'
2
- import { NotADirectoryError } from '../../errors.js'
3
- import type { GetStore } from '../../unixfs.js'
2
+ import { NotADirectoryError } from '../../errors.ts'
3
+ import type { GetStore } from '../../unixfs.ts'
4
4
  import type { PBNode } from '@ipld/dag-pb'
5
5
  import type { ExporterOptions } from 'ipfs-unixfs-exporter'
6
6
  import type { CID } from 'multiformats/cid'
@@ -1,7 +1,7 @@
1
1
  import * as dagPb from '@ipld/dag-pb'
2
2
  import { exporter } from 'ipfs-unixfs-exporter'
3
- import { NotUnixFSError } from '../../errors.js'
4
- import type { GetStore } from '../../unixfs.js'
3
+ import { NotUnixFSError } from '../../errors.ts'
4
+ import type { GetStore } from '../../unixfs.ts'
5
5
  import type { PBNode, PBLink } from '@ipld/dag-pb'
6
6
  import type { ExporterOptions } from 'ipfs-unixfs-exporter'
7
7
  import type { CID } from 'multiformats/cid'
@@ -1,40 +1,41 @@
1
1
  import * as dagPB from '@ipld/dag-pb'
2
2
  import { logger } from '@libp2p/logger'
3
3
  import { UnixFS } from 'ipfs-unixfs'
4
+ import { DirSharded } from 'ipfs-unixfs-importer/utils'
4
5
  import last from 'it-last'
5
6
  import toBuffer from 'it-to-buffer'
6
7
  // @ts-expect-error no types
7
8
  import SparseArray from 'sparse-array'
8
9
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
9
- import { wrapHash } from './consumable-hash.js'
10
- import { DirSharded } from './dir-sharded.js'
10
+ import { wrapHash } from './consumable-hash.ts'
11
11
  import {
12
12
  hamtHashCode,
13
13
  hamtHashFn,
14
14
  hamtBucketBits
15
- } from './hamt-constants.js'
16
- import { persist } from './persist.js'
17
- import type { InfiniteHash } from './consumable-hash.js'
18
- import type { PersistOptions } from './persist.js'
19
- import type { GetStore, PutStore } from '../../unixfs.js'
15
+ } from './hamt-constants.ts'
16
+ import { persist } from './persist.ts'
17
+ import type { InfiniteHash } from './consumable-hash.ts'
18
+ import type { GetStore, PutStore } from '../../unixfs.ts'
20
19
  import type { AbortOptions } from '@libp2p/interface'
21
20
  import type { Blockstore } from 'interface-blockstore'
22
21
  import type { Mtime } from 'ipfs-unixfs'
23
- import type { ImportResult } from 'ipfs-unixfs-importer'
22
+ import type { ImporterOptions, ImportResult } from 'ipfs-unixfs-importer'
24
23
  import type { CID, Version } from 'multiformats/cid'
25
24
 
26
25
  const log = logger('helia:unixfs:commands:utils:hamt-utils')
27
26
 
28
- export interface UpdateHamtDirectoryOptions extends AbortOptions {
27
+ export interface UpdateHamtDirectoryOptions extends HAMTOptions {
29
28
  cidVersion?: Version
30
29
  }
31
30
 
32
- export const toPrefix = (position: number): string => {
31
+ export const toPrefix = (position: number, bits = hamtBucketBits): string => {
32
+ const length = (Math.pow(2, bits) - 1).toString(16).length
33
+
33
34
  return position
34
35
  .toString(16)
35
36
  .toUpperCase()
36
- .padStart(2, '0')
37
- .substring(0, 2)
37
+ .padStart(length, '0')
38
+ .substring(0, length)
38
39
  }
39
40
 
40
41
  export interface CreateShardOptions {
@@ -57,7 +58,7 @@ export const createShard = async (blockstore: PutStore, contents: Array<{ name:
57
58
  }, options)
58
59
 
59
60
  for (let i = 0; i < contents.length; i++) {
60
- await shard._bucket.put(contents[i].name, {
61
+ await shard.bucket.put(contents[i].name, {
61
62
  size: contents[i].size,
62
63
  cid: contents[i].cid
63
64
  })
@@ -78,12 +79,12 @@ export interface HAMTPath {
78
79
  node: dagPB.PBNode
79
80
  }
80
81
 
81
- export const updateShardedDirectory = async (path: HAMTPath[], blockstore: GetStore & PutStore, options: PersistOptions): Promise<{ cid: CID, node: dagPB.PBNode }> => {
82
+ export const updateShardedDirectory = async (path: HAMTPath[], blockstore: GetStore & PutStore, options: HAMTOptions): Promise<{ cid: CID, node: dagPB.PBNode }> => {
82
83
  // persist any metadata on the shard root
83
84
  const shardRoot = UnixFS.unmarshal(path[0].node.Data ?? new Uint8Array(0))
84
85
 
85
86
  // this is always the same
86
- const fanout = BigInt(Math.pow(2, hamtBucketBits))
87
+ const fanout = BigInt(Math.pow(2, options.shardFanoutBits ?? hamtBucketBits))
87
88
 
88
89
  // start from the leaf and ascend to the root
89
90
  path.reverse()
@@ -145,18 +146,23 @@ export const updateShardedDirectory = async (path: HAMTPath[], blockstore: GetSt
145
146
  return { cid, node }
146
147
  }
147
148
 
148
- export const recreateShardedDirectory = async (cid: CID, fileName: string, blockstore: Pick<Blockstore, 'get'>, options: AbortOptions): Promise<{ path: HAMTPath[], hash: InfiniteHash }> => {
149
+ export interface HAMTOptions extends AbortOptions, Pick<ImporterOptions, 'shardFanoutBits' | 'shardSplitStrategy' | 'shardSplitThresholdBytes'> {
150
+
151
+ }
152
+
153
+ export const recreateShardedDirectory = async (cid: CID, fileName: string, blockstore: Pick<Blockstore, 'get'>, options: HAMTOptions): Promise<{ path: HAMTPath[], hash: InfiniteHash }> => {
149
154
  const wrapped = wrapHash(hamtHashFn)
150
155
  const hash = wrapped(uint8ArrayFromString(fileName))
151
156
  const path: HAMTPath[] = []
157
+ const hashBits = options.shardFanoutBits ?? hamtBucketBits
152
158
 
153
159
  // descend the HAMT, loading each layer as we head towards the target child
154
160
  while (true) {
155
161
  const block = await toBuffer(blockstore.get(cid, options))
156
162
  const node = dagPB.decode(block)
157
163
  const children = new SparseArray()
158
- const index = await hash.take(hamtBucketBits)
159
- const prefix = toPrefix(index)
164
+ const index = await hash.take(hashBits)
165
+ const prefix = toPrefix(index, hashBits)
160
166
 
161
167
  path.push({
162
168
  prefix,
@@ -171,11 +177,11 @@ export const recreateShardedDirectory = async (cid: CID, fileName: string, block
171
177
  for (const link of node.Links) {
172
178
  const linkName = link.Name ?? ''
173
179
 
174
- if (linkName.length < 2) {
180
+ if (linkName.length < prefix.length) {
175
181
  throw new Error('Invalid HAMT - link name was too short')
176
182
  }
177
183
 
178
- const position = parseInt(linkName.substring(0, 2), 16)
184
+ const position = parseInt(linkName.substring(0, prefix.length), 16)
179
185
  children.set(position, true)
180
186
 
181
187
  // we found the child we are looking for
@@ -186,17 +192,17 @@ export const recreateShardedDirectory = async (cid: CID, fileName: string, block
186
192
 
187
193
  if (childLink == null) {
188
194
  log('no link found with prefix %s for %s', prefix, fileName)
189
- // hash.untake(hamtBucketBits)
195
+ // hash.untake(hashBits)
190
196
  break
191
197
  }
192
198
 
193
199
  const linkName = childLink.Name ?? ''
194
200
 
195
- if (linkName.length < 2) {
201
+ if (linkName.length < prefix.length) {
196
202
  throw new Error('Invalid HAMT - link name was too short')
197
203
  }
198
204
 
199
- if (linkName.length === 2) {
205
+ if (linkName.length === prefix.length) {
200
206
  // found sub-shard
201
207
  cid = childLink.Hash
202
208
  log('descend into sub-shard with prefix %s', linkName)
@@ -1,31 +1,54 @@
1
1
  import * as dagPb from '@ipld/dag-pb'
2
+ import { InvalidParametersError } from '@libp2p/interface'
2
3
  import { UnixFS } from 'ipfs-unixfs'
4
+ import { dataFieldSerializedSize, linkSerializedSize, utf8ByteLength } from 'ipfs-unixfs-importer/utils'
3
5
  import toBuffer from 'it-to-buffer'
4
- import { CID_V0, CID_V1 } from './dir-sharded.js'
6
+ import { DEFAULT_SHARD_SPLIT_THRESHOLD_BYTES } from '../../constants.ts'
7
+ import { hamtBucketBits } from './hamt-constants.ts'
5
8
  import type { GetStore } from '../../unixfs.js'
6
9
  import type { PBNode } from '@ipld/dag-pb'
7
10
  import type { AbortOptions } from '@libp2p/interface'
11
+ import type { ImporterOptions, ShardSplitStrategy } from 'ipfs-unixfs-importer'
12
+
13
+ export interface IsOverShardThresholdOptions extends AbortOptions, Pick<ImporterOptions, 'profile' | 'shardSplitThresholdBytes' | 'shardSplitStrategy' | 'shardFanoutBits'> {
14
+
15
+ }
8
16
 
9
17
  /**
10
18
  * Estimate node size only based on DAGLink name and CID byte lengths
11
19
  * https://github.com/ipfs/go-unixfsnode/blob/37b47f1f917f1b2f54c207682f38886e49896ef9/data/builder/directory.go#L81-L96
12
20
  *
13
- * If the node is a hamt sharded directory the calculation is based on if it was a regular directory.
21
+ * If the node is a hamt sharded directory the calculation is based on if it was
22
+ * a regular directory.
14
23
  */
15
- export async function isOverShardThreshold (node: PBNode, blockstore: GetStore, threshold: number, options: AbortOptions): Promise<boolean> {
24
+ export async function isOverShardThreshold (node: PBNode, blockstore: GetStore, options: IsOverShardThresholdOptions): Promise<boolean> {
16
25
  if (node.Data == null) {
17
26
  throw new Error('DagPB node had no data')
18
27
  }
19
28
 
29
+ const threshold = options.shardSplitThresholdBytes ?? DEFAULT_SHARD_SPLIT_THRESHOLD_BYTES
30
+ let strategy: ShardSplitStrategy = options.shardSplitStrategy ?? 'links-bytes'
31
+
32
+ if (options.profile === 'unixfs-v0-2015') {
33
+ strategy = 'links-bytes'
34
+ }
35
+
20
36
  const unixfs = UnixFS.unmarshal(node.Data)
21
- let size: number
37
+
38
+ if (!unixfs.isDirectory()) {
39
+ throw new Error('Can only estimate the size of directories or shards')
40
+ }
41
+
42
+ if (strategy !== 'links-bytes' && strategy !== 'block-bytes') {
43
+ throw new InvalidParametersError(`Invalid shard split threshold "${strategy}"`)
44
+ }
45
+
46
+ let size: number = 0
22
47
 
23
48
  if (unixfs.type === 'directory') {
24
- size = estimateNodeSize(node)
49
+ size = strategy === 'links-bytes' ? estimateNodeSize(node) : calculateNodeSize(unixfs, node)
25
50
  } else if (unixfs.type === 'hamt-sharded-directory') {
26
- size = await estimateShardSize(node, 0, threshold, blockstore, options)
27
- } else {
28
- throw new Error('Can only estimate the size of directories or shards')
51
+ size = strategy === 'links-bytes' ? await estimateShardSize(node, 0, threshold, blockstore, options) : await calculateShardSize(node, dataFieldSerializedSize(unixfs.mode, unixfs.mtime), threshold, blockstore, options)
29
52
  }
30
53
 
31
54
  return size > threshold
@@ -37,14 +60,26 @@ function estimateNodeSize (node: PBNode): number {
37
60
  // estimate size only based on DAGLink name and CID byte lengths
38
61
  // https://github.com/ipfs/go-unixfsnode/blob/37b47f1f917f1b2f54c207682f38886e49896ef9/data/builder/directory.go#L81-L96
39
62
  for (const link of node.Links) {
40
- size += (link.Name ?? '').length
41
- size += link.Hash.version === 1 ? CID_V1.bytes.byteLength : CID_V0.bytes.byteLength
63
+ size += utf8ByteLength(link.Name ?? '')
64
+ size += link.Hash.byteLength
42
65
  }
43
66
 
44
67
  return size
45
68
  }
46
69
 
47
- async function estimateShardSize (node: PBNode, current: number, max: number, blockstore: GetStore, options: AbortOptions): Promise<number> {
70
+ function calculateNodeSize (unixfs: UnixFS, node: PBNode): number {
71
+ let size = dataFieldSerializedSize(unixfs.mode, unixfs.mtime)
72
+
73
+ for (const link of node.Links) {
74
+ size += linkSerializedSize(
75
+ utf8ByteLength(link.Name ?? ''), link.Hash.byteLength, Number(link.Tsize ?? 0)
76
+ )
77
+ }
78
+
79
+ return size
80
+ }
81
+
82
+ async function estimateShardSize (node: PBNode, current: number, max: number, blockstore: GetStore, options: IsOverShardThresholdOptions): Promise<number> {
48
83
  if (current > max) {
49
84
  return max
50
85
  }
@@ -59,13 +94,15 @@ async function estimateShardSize (node: PBNode, current: number, max: number, bl
59
94
  return current
60
95
  }
61
96
 
97
+ const prefixLength = (Math.pow(2, options.shardFanoutBits ?? hamtBucketBits) - 1).toString(16).length
98
+
62
99
  for (const link of node.Links) {
63
100
  let name = link.Name ?? ''
64
101
 
65
102
  // remove hamt hash prefix from name
66
- name = name.substring(2)
103
+ name = name.substring(prefixLength)
67
104
 
68
- current += name.length
105
+ current += utf8ByteLength(name)
69
106
  current += link.Hash.bytes.byteLength
70
107
 
71
108
  if (link.Hash.code === dagPb.code) {
@@ -78,3 +115,41 @@ async function estimateShardSize (node: PBNode, current: number, max: number, bl
78
115
 
79
116
  return current
80
117
  }
118
+
119
+ async function calculateShardSize (node: PBNode, current: number, max: number, blockstore: GetStore, options: IsOverShardThresholdOptions): Promise<number> {
120
+ if (current > max) {
121
+ return max
122
+ }
123
+
124
+ if (node.Data == null) {
125
+ return current
126
+ }
127
+
128
+ const unixfs = UnixFS.unmarshal(node.Data)
129
+
130
+ if (!unixfs.isDirectory()) {
131
+ return current
132
+ }
133
+
134
+ const prefixLength = (Math.pow(2, options.shardFanoutBits ?? hamtBucketBits) - 1).toString(16).length
135
+
136
+ for (const link of node.Links) {
137
+ let name = link.Name ?? ''
138
+
139
+ // remove hamt hash prefix from name
140
+ name = name.substring(prefixLength)
141
+
142
+ current += linkSerializedSize(
143
+ utf8ByteLength(name), link.Hash.byteLength, Number(link.Tsize ?? 0)
144
+ )
145
+
146
+ if (link.Hash.code === dagPb.code) {
147
+ const block = await toBuffer(blockstore.get(link.Hash, options))
148
+ const node = dagPb.decode(block)
149
+
150
+ current += await calculateShardSize(node, current, max, blockstore, options)
151
+ }
152
+ }
153
+
154
+ return current
155
+ }
@@ -2,14 +2,14 @@ import * as dagPb from '@ipld/dag-pb'
2
2
  import { CID } from 'multiformats/cid'
3
3
  import { sha256 } from 'multiformats/hashes/sha2'
4
4
  import { DEFAULT_CID_VERSION } from '../../constants.ts'
5
- import type { PutStore } from '../../unixfs.js'
5
+ import type { PutStore } from '../../unixfs.ts'
6
+ import type { AbortOptions } from '@libp2p/interface'
6
7
  import type { Version as CIDVersion } from 'multiformats/cid'
7
8
  import type { BlockCodec } from 'multiformats/codecs/interface'
8
9
 
9
- export interface PersistOptions {
10
+ export interface PersistOptions extends AbortOptions {
10
11
  codec?: BlockCodec<any, any>
11
12
  cidVersion?: CIDVersion
12
- signal?: AbortSignal
13
13
  }
14
14
 
15
15
  export const persist = async (buffer: Uint8Array, blockstore: PutStore, options: PersistOptions): Promise<CID> => {
@@ -2,27 +2,26 @@ import * as dagPB from '@ipld/dag-pb'
2
2
  import { logger } from '@libp2p/logger'
3
3
  import { UnixFS } from 'ipfs-unixfs'
4
4
  import { exporter } from 'ipfs-unixfs-exporter'
5
- import { DEFAULT_SHARD_SPLIT_THRESHOLD_BYTES } from '../../constants.ts'
6
- import { InvalidParametersError, InvalidPBNodeError } from '../../errors.js'
5
+ import { InvalidParametersError, InvalidPBNodeError } from '../../errors.ts'
6
+ import { hamtBucketBits } from './hamt-constants.ts'
7
7
  import {
8
8
  recreateShardedDirectory,
9
-
10
9
  updateShardedDirectory
11
- } from './hamt-utils.js'
12
- import { isOverShardThreshold } from './is-over-shard-threshold.js'
13
- import { persist } from './persist.js'
14
- import type { Directory } from './cid-to-directory.js'
15
- import type { UpdateHamtDirectoryOptions } from './hamt-utils.js'
16
- import type { GetStore, PutStore } from '../../unixfs.js'
10
+ } from './hamt-utils.ts'
11
+ import { isOverShardThreshold } from './is-over-shard-threshold.ts'
12
+ import { persist } from './persist.ts'
13
+ import type { Directory } from './cid-to-directory.ts'
14
+ import type { UpdateHamtDirectoryOptions } from './hamt-utils.ts'
15
+ import type { GetStore, PutStore } from '../../unixfs.ts'
17
16
  import type { PBNode } from '@ipld/dag-pb'
18
17
  import type { AbortOptions } from '@libp2p/interface'
19
- import type { CID, Version } from 'multiformats/cid'
18
+ import type { ImporterOptions } from 'ipfs-unixfs-importer'
19
+ import type { CID } from 'multiformats/cid'
20
20
 
21
21
  const log = logger('helia:unixfs:utils:remove-link')
22
22
 
23
- export interface RmLinkOptions extends AbortOptions {
24
- shardSplitThresholdBytes?: number
25
- cidVersion?: Version
23
+ export interface RmLinkOptions extends AbortOptions, Pick<ImporterOptions, 'profile' | 'shardSplitThresholdBytes' | 'shardSplitStrategy' | 'shardFanoutBits' | 'cidVersion'> {
24
+
26
25
  }
27
26
 
28
27
  export interface RemoveLinkResult {
@@ -42,7 +41,7 @@ export async function removeLink (parent: Directory, name: string, blockstore: P
42
41
 
43
42
  const result = await removeFromShardedDirectory(parent, name, blockstore, options)
44
43
 
45
- if (!(await isOverShardThreshold(result.node, blockstore, options.shardSplitThresholdBytes ?? DEFAULT_SHARD_SPLIT_THRESHOLD_BYTES, options))) {
44
+ if (!(await isOverShardThreshold(result.node, blockstore, options))) {
46
45
  log('converting shard to flat directory %c', parent.cid)
47
46
 
48
47
  return convertToFlatDirectory(result, blockstore, options)
@@ -84,13 +83,14 @@ const removeFromShardedDirectory = async (parent: Directory, name: string, block
84
83
  throw new Error('Invalid HAMT, could not generate path')
85
84
  }
86
85
 
87
- const linkName = finalSegment.node.Links.filter(l => (l.Name ?? '').substring(2) === name).map(l => l.Name).pop()
86
+ const prefixLength = (Math.pow(2, options.shardFanoutBits ?? hamtBucketBits) - 1).toString(16).length
87
+ const linkName = finalSegment.node.Links.filter(l => (l.Name ?? '').substring(prefixLength) === name).map(l => l.Name).pop()
88
88
 
89
89
  if (linkName == null) {
90
90
  throw new Error('File not found')
91
91
  }
92
92
 
93
- const prefix = linkName.substring(0, 2)
93
+ const prefix = linkName.substring(0, prefixLength)
94
94
  const index = parseInt(prefix, 16)
95
95
 
96
96
  // remove the file from the shard
@@ -124,7 +124,7 @@ const removeFromShardedDirectory = async (parent: Directory, name: string, block
124
124
  nextSegment.node.Links = nextSegment.node.Links.filter(l => !(l.Name ?? '').startsWith(nextSegment.prefix))
125
125
  nextSegment.node.Links.push({
126
126
  Hash: link.Hash,
127
- Name: `${nextSegment.prefix}${(link.Name ?? '').substring(2)}`,
127
+ Name: `${nextSegment.prefix}${(link.Name ?? '').substring(prefixLength)}`,
128
128
  Tsize: link.Tsize
129
129
  })
130
130
  }
@@ -1,11 +1,11 @@
1
1
  import { logger } from '@libp2p/logger'
2
2
  import { walkPath } from 'ipfs-unixfs-exporter'
3
3
  import all from 'it-all'
4
- import { DoesNotExistError } from '../../errors.js'
5
- import { addLink } from './add-link.js'
6
- import { cidToDirectory } from './cid-to-directory.js'
7
- import { cidToPBLink } from './cid-to-pblink.js'
8
- import type { GetStore, PutStore } from '../../unixfs.js'
4
+ import { DoesNotExistError } from '../../errors.ts'
5
+ import { addLink } from './add-link.ts'
6
+ import { cidToDirectory } from './cid-to-directory.ts'
7
+ import { cidToPBLink } from './cid-to-pblink.ts'
8
+ import type { GetStore, PutStore } from '../../unixfs.ts'
9
9
  import type { AbortOptions } from '@libp2p/interface'
10
10
  import type { PathEntry } from 'ipfs-unixfs-exporter'
11
11
  import type { CID } from 'multiformats/cid'