@helia/unixfs 0.0.0

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 (104) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +53 -0
  3. package/dist/index.min.js +3 -0
  4. package/dist/src/commands/add.d.ts +6 -0
  5. package/dist/src/commands/add.d.ts.map +1 -0
  6. package/dist/src/commands/add.js +38 -0
  7. package/dist/src/commands/add.js.map +1 -0
  8. package/dist/src/commands/cat.d.ts +5 -0
  9. package/dist/src/commands/cat.d.ts.map +1 -0
  10. package/dist/src/commands/cat.js +22 -0
  11. package/dist/src/commands/cat.js.map +1 -0
  12. package/dist/src/commands/chmod.d.ts +5 -0
  13. package/dist/src/commands/chmod.d.ts.map +1 -0
  14. package/dist/src/commands/chmod.js +108 -0
  15. package/dist/src/commands/chmod.js.map +1 -0
  16. package/dist/src/commands/cp.d.ts +5 -0
  17. package/dist/src/commands/cp.d.ts.map +1 -0
  18. package/dist/src/commands/cp.js +28 -0
  19. package/dist/src/commands/cp.js.map +1 -0
  20. package/dist/src/commands/ls.d.ts +5 -0
  21. package/dist/src/commands/ls.d.ts.map +1 -0
  22. package/dist/src/commands/ls.js +26 -0
  23. package/dist/src/commands/ls.js.map +1 -0
  24. package/dist/src/commands/mkdir.d.ts +5 -0
  25. package/dist/src/commands/mkdir.d.ts.map +1 -0
  26. package/dist/src/commands/mkdir.js +53 -0
  27. package/dist/src/commands/mkdir.js.map +1 -0
  28. package/dist/src/commands/rm.d.ts +5 -0
  29. package/dist/src/commands/rm.d.ts.map +1 -0
  30. package/dist/src/commands/rm.js +19 -0
  31. package/dist/src/commands/rm.js.map +1 -0
  32. package/dist/src/commands/stat.d.ts +5 -0
  33. package/dist/src/commands/stat.d.ts.map +1 -0
  34. package/dist/src/commands/stat.js +108 -0
  35. package/dist/src/commands/stat.js.map +1 -0
  36. package/dist/src/commands/touch.d.ts +5 -0
  37. package/dist/src/commands/touch.d.ts.map +1 -0
  38. package/dist/src/commands/touch.js +111 -0
  39. package/dist/src/commands/touch.js.map +1 -0
  40. package/dist/src/commands/utils/add-link.d.ts +21 -0
  41. package/dist/src/commands/utils/add-link.d.ts.map +1 -0
  42. package/dist/src/commands/utils/add-link.js +224 -0
  43. package/dist/src/commands/utils/add-link.js.map +1 -0
  44. package/dist/src/commands/utils/cid-to-directory.d.ts +10 -0
  45. package/dist/src/commands/utils/cid-to-directory.d.ts.map +1 -0
  46. package/dist/src/commands/utils/cid-to-directory.js +13 -0
  47. package/dist/src/commands/utils/cid-to-directory.js.map +1 -0
  48. package/dist/src/commands/utils/cid-to-pblink.d.ts +6 -0
  49. package/dist/src/commands/utils/cid-to-pblink.d.ts.map +1 -0
  50. package/dist/src/commands/utils/cid-to-pblink.js +19 -0
  51. package/dist/src/commands/utils/cid-to-pblink.js.map +1 -0
  52. package/dist/src/commands/utils/dir-sharded.d.ts +67 -0
  53. package/dist/src/commands/utils/dir-sharded.d.ts.map +1 -0
  54. package/dist/src/commands/utils/dir-sharded.js +136 -0
  55. package/dist/src/commands/utils/dir-sharded.js.map +1 -0
  56. package/dist/src/commands/utils/errors.d.ts +17 -0
  57. package/dist/src/commands/utils/errors.d.ts.map +1 -0
  58. package/dist/src/commands/utils/errors.js +27 -0
  59. package/dist/src/commands/utils/errors.js.map +1 -0
  60. package/dist/src/commands/utils/hamt-constants.d.ts +4 -0
  61. package/dist/src/commands/utils/hamt-constants.d.ts.map +1 -0
  62. package/dist/src/commands/utils/hamt-constants.js +13 -0
  63. package/dist/src/commands/utils/hamt-constants.js.map +1 -0
  64. package/dist/src/commands/utils/hamt-utils.d.ts +37 -0
  65. package/dist/src/commands/utils/hamt-utils.d.ts.map +1 -0
  66. package/dist/src/commands/utils/hamt-utils.js +202 -0
  67. package/dist/src/commands/utils/hamt-utils.js.map +1 -0
  68. package/dist/src/commands/utils/persist.d.ts +10 -0
  69. package/dist/src/commands/utils/persist.d.ts.map +1 -0
  70. package/dist/src/commands/utils/persist.js +12 -0
  71. package/dist/src/commands/utils/persist.js.map +1 -0
  72. package/dist/src/commands/utils/remove-link.d.ts +11 -0
  73. package/dist/src/commands/utils/remove-link.d.ts.map +1 -0
  74. package/dist/src/commands/utils/remove-link.js +92 -0
  75. package/dist/src/commands/utils/remove-link.js.map +1 -0
  76. package/dist/src/commands/utils/resolve.d.ts +28 -0
  77. package/dist/src/commands/utils/resolve.d.ts.map +1 -0
  78. package/dist/src/commands/utils/resolve.js +85 -0
  79. package/dist/src/commands/utils/resolve.js.map +1 -0
  80. package/dist/src/index.d.ts +97 -0
  81. package/dist/src/index.d.ts.map +1 -0
  82. package/dist/src/index.js +48 -0
  83. package/dist/src/index.js.map +1 -0
  84. package/package.json +166 -0
  85. package/src/commands/add.ts +46 -0
  86. package/src/commands/cat.ts +31 -0
  87. package/src/commands/chmod.ts +133 -0
  88. package/src/commands/cp.ts +41 -0
  89. package/src/commands/ls.ts +36 -0
  90. package/src/commands/mkdir.ts +71 -0
  91. package/src/commands/rm.ts +31 -0
  92. package/src/commands/stat.ts +137 -0
  93. package/src/commands/touch.ts +136 -0
  94. package/src/commands/utils/add-link.ts +319 -0
  95. package/src/commands/utils/cid-to-directory.ts +23 -0
  96. package/src/commands/utils/cid-to-pblink.ts +26 -0
  97. package/src/commands/utils/dir-sharded.ts +219 -0
  98. package/src/commands/utils/errors.ts +31 -0
  99. package/src/commands/utils/hamt-constants.ts +14 -0
  100. package/src/commands/utils/hamt-utils.ts +285 -0
  101. package/src/commands/utils/persist.ts +22 -0
  102. package/src/commands/utils/remove-link.ts +151 -0
  103. package/src/commands/utils/resolve.ts +130 -0
  104. package/src/index.ts +174 -0
@@ -0,0 +1,219 @@
1
+ import { encode, prepare } from '@ipld/dag-pb'
2
+ import { UnixFS } from 'ipfs-unixfs'
3
+ import { persist } from './persist.js'
4
+ import { createHAMT, Bucket, BucketChild } from 'hamt-sharding'
5
+ import {
6
+ hamtHashCode,
7
+ hamtHashFn,
8
+ hamtBucketBits
9
+ } from './hamt-constants.js'
10
+ import type { CID, Version } from 'multiformats/cid'
11
+ import type { PBNode } from '@ipld/dag-pb/interface'
12
+ import type { Mtime } from 'ipfs-unixfs'
13
+ import type { BlockCodec } from 'multiformats/codecs/interface'
14
+ import type { Blockstore } from 'ipfs-unixfs-importer'
15
+
16
+ export interface ImportResult {
17
+ cid: CID
18
+ node: PBNode
19
+ size: number
20
+ }
21
+
22
+ export interface DirContents {
23
+ cid?: CID
24
+ size?: number
25
+ }
26
+
27
+ export interface DirOptions {
28
+ mtime?: Mtime
29
+ mode?: number
30
+ codec?: BlockCodec<any, any>
31
+ cidVersion?: Version
32
+ onlyHash?: boolean
33
+ signal?: AbortSignal
34
+ }
35
+
36
+ export interface DirProps {
37
+ root: boolean
38
+ dir: boolean
39
+ path: string
40
+ dirty: boolean
41
+ flat: boolean
42
+ parent?: Dir
43
+ parentKey?: string
44
+ unixfs?: UnixFS
45
+ mode?: number
46
+ mtime?: Mtime
47
+ }
48
+
49
+ export abstract class Dir {
50
+ protected options: DirOptions
51
+ protected root: boolean
52
+ protected dir: boolean
53
+ protected path: string
54
+ protected dirty: boolean
55
+ protected flat: boolean
56
+ protected parent?: Dir
57
+ protected parentKey?: string
58
+ protected unixfs?: UnixFS
59
+ protected mode?: number
60
+ public mtime?: Mtime
61
+ protected cid?: CID
62
+ protected size?: number
63
+
64
+ constructor (props: DirProps, options: DirOptions) {
65
+ this.options = options ?? {}
66
+ this.root = props.root
67
+ this.dir = props.dir
68
+ this.path = props.path
69
+ this.dirty = props.dirty
70
+ this.flat = props.flat
71
+ this.parent = props.parent
72
+ this.parentKey = props.parentKey
73
+ this.unixfs = props.unixfs
74
+ this.mode = props.mode
75
+ this.mtime = props.mtime
76
+ }
77
+ }
78
+
79
+ export class DirSharded extends Dir {
80
+ public _bucket: Bucket<DirContents>
81
+
82
+ constructor (props: DirProps, options: DirOptions) {
83
+ super(props, options)
84
+
85
+ /** @type {Bucket<DirContents>} */
86
+ this._bucket = createHAMT({
87
+ hashFn: hamtHashFn,
88
+ bits: hamtBucketBits
89
+ })
90
+ }
91
+
92
+ async put (name: string, value: DirContents): Promise<void> {
93
+ await this._bucket.put(name, value)
94
+ }
95
+
96
+ async get (name: string): Promise<DirContents | undefined> {
97
+ return await this._bucket.get(name)
98
+ }
99
+
100
+ childCount (): number {
101
+ return this._bucket.leafCount()
102
+ }
103
+
104
+ directChildrenCount (): number {
105
+ return this._bucket.childrenCount()
106
+ }
107
+
108
+ onlyChild (): Bucket<DirContents> | BucketChild<DirContents> {
109
+ return this._bucket.onlyChild()
110
+ }
111
+
112
+ async * eachChildSeries (): AsyncGenerator<{ key: string, child: DirContents }> {
113
+ for await (const { key, value } of this._bucket.eachLeafSeries()) {
114
+ yield {
115
+ key,
116
+ child: value
117
+ }
118
+ }
119
+ }
120
+
121
+ async * flush (blockstore: Blockstore): AsyncIterable<ImportResult> {
122
+ yield * flush(this._bucket, blockstore, this, this.options)
123
+ }
124
+ }
125
+
126
+ async function * flush (bucket: Bucket<any>, blockstore: Blockstore, shardRoot: any, options: DirOptions): AsyncIterable<ImportResult> {
127
+ const children = bucket._children
128
+ const links = []
129
+ let childrenSize = 0
130
+
131
+ for (let i = 0; i < children.length; i++) {
132
+ const child = children.get(i)
133
+
134
+ if (child == null) {
135
+ continue
136
+ }
137
+
138
+ const labelPrefix = i.toString(16).toUpperCase().padStart(2, '0')
139
+
140
+ if (child instanceof Bucket) {
141
+ let shard: ImportResult | undefined
142
+
143
+ for await (const subShard of flush(child, blockstore, null, options)) {
144
+ shard = subShard
145
+ }
146
+
147
+ if (shard == null) {
148
+ throw new Error('Could not flush sharded directory, no subshard found')
149
+ }
150
+
151
+ links.push({
152
+ Name: labelPrefix,
153
+ Tsize: shard.size,
154
+ Hash: shard.cid
155
+ })
156
+ childrenSize += shard.size
157
+ } else if (typeof child.value.flush === 'function') {
158
+ const dir = child.value
159
+ let flushedDir
160
+
161
+ for await (const entry of dir.flush(blockstore)) {
162
+ flushedDir = entry
163
+
164
+ yield flushedDir
165
+ }
166
+
167
+ const label = labelPrefix + child.key
168
+ links.push({
169
+ Name: label,
170
+ Tsize: flushedDir.size,
171
+ Hash: flushedDir.cid
172
+ })
173
+
174
+ childrenSize += flushedDir.size // eslint-disable-line @typescript-eslint/restrict-plus-operands
175
+ } else {
176
+ const value = child.value
177
+
178
+ if (value.cid == null) {
179
+ continue
180
+ }
181
+
182
+ const label = labelPrefix + child.key
183
+ const size = value.size
184
+
185
+ links.push({
186
+ Name: label,
187
+ Tsize: size,
188
+ Hash: value.cid
189
+ })
190
+ childrenSize += size ?? 0 // eslint-disable-line @typescript-eslint/restrict-plus-operands
191
+ }
192
+ }
193
+
194
+ // go-ipfs uses little endian, that's why we have to
195
+ // reverse the bit field before storing it
196
+ const data = Uint8Array.from(children.bitField().reverse())
197
+ const dir = new UnixFS({
198
+ type: 'hamt-sharded-directory',
199
+ data,
200
+ fanout: bucket.tableSize(),
201
+ hashType: hamtHashCode,
202
+ mtime: shardRoot?.mtime,
203
+ mode: shardRoot?.mode
204
+ })
205
+
206
+ const node = {
207
+ Data: dir.marshal(),
208
+ Links: links
209
+ }
210
+ const buffer = encode(prepare(node))
211
+ const cid = await persist(buffer, blockstore, options)
212
+ const size = buffer.length + childrenSize
213
+
214
+ yield {
215
+ cid,
216
+ node,
217
+ size
218
+ }
219
+ }
@@ -0,0 +1,31 @@
1
+ import { HeliaError } from '@helia/interface/errors'
2
+
3
+ export class NotUnixFSError extends HeliaError {
4
+ constructor (message = 'not a Unixfs node') {
5
+ super(message, 'NotUnixFSError', 'ERR_NOT_UNIXFS')
6
+ }
7
+ }
8
+
9
+ export class InvalidPBNodeError extends HeliaError {
10
+ constructor (message = 'invalid PBNode') {
11
+ super(message, 'InvalidPBNodeError', 'ERR_INVALID_PBNODE')
12
+ }
13
+ }
14
+
15
+ export class UnknownError extends HeliaError {
16
+ constructor (message = 'unknown error') {
17
+ super(message, 'InvalidPBNodeError', 'ERR_UNKNOWN_ERROR')
18
+ }
19
+ }
20
+
21
+ export class AlreadyExistsError extends HeliaError {
22
+ constructor (message = 'path already exists') {
23
+ super(message, 'NotUnixFSError', 'ERR_ALREADY_EXISTS')
24
+ }
25
+ }
26
+
27
+ export class DoesNotExistError extends HeliaError {
28
+ constructor (message = 'path does not exist') {
29
+ super(message, 'NotUnixFSError', 'ERR_DOES_NOT_EXIST')
30
+ }
31
+ }
@@ -0,0 +1,14 @@
1
+ import { murmur3128 } from '@multiformats/murmur3'
2
+
3
+ export const hamtHashCode = murmur3128.code
4
+ export const hamtBucketBits = 8
5
+
6
+ export async function hamtHashFn (buf: Uint8Array): Promise<Uint8Array> {
7
+ return (await murmur3128.encode(buf))
8
+ // Murmur3 outputs 128 bit but, accidentally, IPFS Go's
9
+ // implementation only uses the first 64, so we must do the same
10
+ // for parity..
11
+ .subarray(0, 8)
12
+ // Invert buffer because that's how Go impl does it
13
+ .reverse()
14
+ }
@@ -0,0 +1,285 @@
1
+ import * as dagPB from '@ipld/dag-pb'
2
+ import {
3
+ Bucket,
4
+ createHAMT
5
+ } from 'hamt-sharding'
6
+ import { DirSharded } from './dir-sharded.js'
7
+ import { logger } from '@libp2p/logger'
8
+ import { UnixFS } from 'ipfs-unixfs'
9
+ import last from 'it-last'
10
+ import { CID } from 'multiformats/cid'
11
+ import {
12
+ hamtHashCode,
13
+ hamtHashFn,
14
+ hamtBucketBits
15
+ } from './hamt-constants.js'
16
+ import type { PBLink, PBNode } from '@ipld/dag-pb/interface'
17
+ import { sha256 } from 'multiformats/hashes/sha2'
18
+ import type { Blockstore } from 'interface-blockstore'
19
+ import type { Mtime } from 'ipfs-unixfs'
20
+ import type { Directory } from './cid-to-directory.js'
21
+ import type { AbortOptions } from '@libp2p/interfaces'
22
+ import type { ImportResult } from 'ipfs-unixfs-importer'
23
+
24
+ const log = logger('helia:unixfs:commands:utils:hamt-utils')
25
+
26
+ export interface UpdateHamtResult {
27
+ node: PBNode
28
+ cid: CID
29
+ size: number
30
+ }
31
+
32
+ export const updateHamtDirectory = async (parent: Directory, blockstore: Blockstore, links: PBLink[], bucket: Bucket<any>, options: AbortOptions): Promise<UpdateHamtResult> => {
33
+ if (parent.node.Data == null) {
34
+ throw new Error('Could not update HAMT directory because parent had no data')
35
+ }
36
+
37
+ // update parent with new bit field
38
+ const data = Uint8Array.from(bucket._children.bitField().reverse())
39
+ const node = UnixFS.unmarshal(parent.node.Data)
40
+ const dir = new UnixFS({
41
+ type: 'hamt-sharded-directory',
42
+ data,
43
+ fanout: bucket.tableSize(),
44
+ hashType: hamtHashCode,
45
+ mode: node.mode,
46
+ mtime: node.mtime
47
+ })
48
+
49
+ parent.node = {
50
+ Data: dir.marshal(),
51
+ Links: links.sort((a, b) => (a.Name ?? '').localeCompare(b.Name ?? ''))
52
+ }
53
+ const buf = dagPB.encode(parent.node)
54
+ const hash = await sha256.digest(buf)
55
+ const cid = CID.create(parent.cid.version, dagPB.code, hash)
56
+
57
+ await blockstore.put(cid, buf, options)
58
+
59
+ return {
60
+ node: parent.node,
61
+ cid,
62
+ size: links.reduce((sum, link) => sum + (link.Tsize ?? 0), buf.length)
63
+ }
64
+ }
65
+
66
+ export const recreateHamtLevel = async (blockstore: Blockstore, links: PBLink[], rootBucket: Bucket<any>, parentBucket: Bucket<any>, positionAtParent: number, options: AbortOptions): Promise<Bucket<any>> => {
67
+ // recreate this level of the HAMT
68
+ const bucket = new Bucket({
69
+ hash: rootBucket._options.hash,
70
+ bits: rootBucket._options.bits
71
+ }, parentBucket, positionAtParent)
72
+ parentBucket._putObjectAt(positionAtParent, bucket)
73
+
74
+ await addLinksToHamtBucket(blockstore, links, bucket, rootBucket, options)
75
+
76
+ return bucket
77
+ }
78
+
79
+ export const recreateInitialHamtLevel = async (links: PBLink[]): Promise<Bucket<any>> => {
80
+ const bucket = createHAMT({
81
+ hashFn: hamtHashFn,
82
+ bits: hamtBucketBits
83
+ })
84
+
85
+ // populate sub bucket but do not recurse as we do not want to pull whole shard in
86
+ await Promise.all(
87
+ links.map(async link => {
88
+ const linkName = (link.Name ?? '')
89
+
90
+ if (linkName.length === 2) {
91
+ const pos = parseInt(linkName, 16)
92
+
93
+ const subBucket = new Bucket({
94
+ hash: bucket._options.hash,
95
+ bits: bucket._options.bits
96
+ }, bucket, pos)
97
+ bucket._putObjectAt(pos, subBucket)
98
+
99
+ await Promise.resolve(); return
100
+ }
101
+
102
+ await bucket.put(linkName.substring(2), {
103
+ size: link.Tsize,
104
+ cid: link.Hash
105
+ })
106
+ })
107
+ )
108
+
109
+ return bucket
110
+ }
111
+
112
+ export const addLinksToHamtBucket = async (blockstore: Blockstore, links: PBLink[], bucket: Bucket<any>, rootBucket: Bucket<any>, options: AbortOptions): Promise<void> => {
113
+ await Promise.all(
114
+ links.map(async link => {
115
+ const linkName = (link.Name ?? '')
116
+
117
+ if (linkName.length === 2) {
118
+ log('Populating sub bucket', linkName)
119
+ const pos = parseInt(linkName, 16)
120
+ const block = await blockstore.get(link.Hash, options)
121
+ const node = dagPB.decode(block)
122
+
123
+ const subBucket = new Bucket({
124
+ hash: rootBucket._options.hash,
125
+ bits: rootBucket._options.bits
126
+ }, bucket, pos)
127
+ bucket._putObjectAt(pos, subBucket)
128
+
129
+ await addLinksToHamtBucket(blockstore, node.Links, subBucket, rootBucket, options)
130
+
131
+ await Promise.resolve(); return
132
+ }
133
+
134
+ await rootBucket.put(linkName.substring(2), {
135
+ size: link.Tsize,
136
+ cid: link.Hash
137
+ })
138
+ })
139
+ )
140
+ }
141
+
142
+ export const toPrefix = (position: number): string => {
143
+ return position
144
+ .toString(16)
145
+ .toUpperCase()
146
+ .padStart(2, '0')
147
+ .substring(0, 2)
148
+ }
149
+
150
+ export interface HamtPath {
151
+ rootBucket: Bucket<any>
152
+ path: Array<{
153
+ bucket: Bucket<any>
154
+ prefix: string
155
+ node?: PBNode
156
+ }>
157
+ }
158
+
159
+ export const generatePath = async (parent: Directory, name: string, blockstore: Blockstore, options: AbortOptions): Promise<HamtPath> => {
160
+ // start at the root bucket and descend, loading nodes as we go
161
+ const rootBucket = await recreateInitialHamtLevel(parent.node.Links)
162
+ const position = await rootBucket._findNewBucketAndPos(name)
163
+
164
+ // the path to the root bucket
165
+ const path: Array<{ bucket: Bucket<any>, prefix: string, node?: PBNode }> = [{
166
+ bucket: position.bucket,
167
+ prefix: toPrefix(position.pos)
168
+ }]
169
+ let currentBucket = position.bucket
170
+
171
+ while (currentBucket !== rootBucket) {
172
+ path.push({
173
+ bucket: currentBucket,
174
+ prefix: toPrefix(currentBucket._posAtParent)
175
+ })
176
+
177
+ // @ts-expect-error - only the root bucket's parent will be undefined
178
+ currentBucket = currentBucket._parent
179
+ }
180
+
181
+ path.reverse()
182
+ path[0].node = parent.node
183
+
184
+ // load PbNode for each path segment
185
+ for (let i = 0; i < path.length; i++) {
186
+ const segment = path[i]
187
+
188
+ if (segment.node == null) {
189
+ throw new Error('Could not generate HAMT path')
190
+ }
191
+
192
+ // find prefix in links
193
+ const link = segment.node.Links
194
+ .filter(link => (link.Name ?? '').substring(0, 2) === segment.prefix)
195
+ .pop()
196
+
197
+ // entry was not in shard
198
+ if (link == null) {
199
+ // reached bottom of tree, file will be added to the current bucket
200
+ log(`Link ${segment.prefix}${name} will be added`)
201
+ // return path
202
+ continue
203
+ }
204
+
205
+ // found entry
206
+ if (link.Name === `${segment.prefix}${name}`) {
207
+ log(`Link ${segment.prefix}${name} will be replaced`)
208
+ // file already existed, file will be added to the current bucket
209
+ // return path
210
+ continue
211
+ }
212
+
213
+ // found subshard
214
+ log(`Found subshard ${segment.prefix}`)
215
+ const block = await blockstore.get(link.Hash, options)
216
+ const node = dagPB.decode(block)
217
+
218
+ // subshard hasn't been loaded, descend to the next level of the HAMT
219
+ if (path[i + 1] == null) {
220
+ log(`Loaded new subshard ${segment.prefix}`)
221
+
222
+ await recreateHamtLevel(blockstore, node.Links, rootBucket, segment.bucket, parseInt(segment.prefix, 16), options)
223
+ const position = await rootBucket._findNewBucketAndPos(name)
224
+
225
+ // i--
226
+ path.push({
227
+ bucket: position.bucket,
228
+ prefix: toPrefix(position.pos),
229
+ node
230
+ })
231
+
232
+ continue
233
+ }
234
+
235
+ const nextSegment = path[i + 1]
236
+
237
+ // add intermediate links to bucket
238
+ await addLinksToHamtBucket(blockstore, node.Links, nextSegment.bucket, rootBucket, options)
239
+
240
+ nextSegment.node = node
241
+ }
242
+
243
+ await rootBucket.put(name, true)
244
+
245
+ path.reverse()
246
+
247
+ return {
248
+ rootBucket,
249
+ path
250
+ }
251
+ }
252
+
253
+ export interface CreateShardOptions {
254
+ mtime?: Mtime
255
+ mode?: number
256
+ }
257
+
258
+ export const createShard = async (blockstore: Blockstore, contents: Array<{ name: string, size: number, cid: CID }>, options: CreateShardOptions = {}): Promise<ImportResult> => {
259
+ const shard = new DirSharded({
260
+ root: true,
261
+ dir: true,
262
+ parent: undefined,
263
+ parentKey: undefined,
264
+ path: '',
265
+ dirty: true,
266
+ flat: false,
267
+ mtime: options.mtime,
268
+ mode: options.mode
269
+ }, options)
270
+
271
+ for (let i = 0; i < contents.length; i++) {
272
+ await shard._bucket.put(contents[i].name, {
273
+ size: contents[i].size,
274
+ cid: contents[i].cid
275
+ })
276
+ }
277
+
278
+ const res = await last(shard.flush(blockstore))
279
+
280
+ if (res == null) {
281
+ throw new Error('Flushing shard yielded no result')
282
+ }
283
+
284
+ return res
285
+ }
@@ -0,0 +1,22 @@
1
+ import { CID, Version } from 'multiformats/cid'
2
+ import * as dagPB from '@ipld/dag-pb'
3
+ import { sha256 } from 'multiformats/hashes/sha2'
4
+ import type { BlockCodec } from 'multiformats/codecs/interface'
5
+ import type { AbortOptions } from '@libp2p/interfaces'
6
+ import type { Blockstore } from 'interface-blockstore'
7
+
8
+ export interface PersistOptions extends AbortOptions {
9
+ codec?: BlockCodec<any, any>
10
+ cidVersion?: Version
11
+ }
12
+
13
+ export const persist = async (buffer: Uint8Array, blockstore: Blockstore, options: PersistOptions = {}): Promise<CID> => {
14
+ const multihash = await sha256.digest(buffer)
15
+ const cid = CID.create(options.cidVersion ?? 1, dagPB.code, multihash)
16
+
17
+ await blockstore.put(cid, buffer, {
18
+ signal: options.signal
19
+ })
20
+
21
+ return cid
22
+ }