@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,151 @@
1
+
2
+ import * as dagPB from '@ipld/dag-pb'
3
+ import { CID } from 'multiformats/cid'
4
+ import { logger } from '@libp2p/logger'
5
+ import { UnixFS } from 'ipfs-unixfs'
6
+ import {
7
+ generatePath,
8
+ updateHamtDirectory,
9
+ UpdateHamtResult
10
+ } from './hamt-utils.js'
11
+ import type { PBNode, PBLink } from '@ipld/dag-pb'
12
+ import type { Blockstore } from 'interface-blockstore'
13
+ import { sha256 } from 'multiformats/hashes/sha2'
14
+ import type { Bucket } from 'hamt-sharding'
15
+ import type { Directory } from './cid-to-directory.js'
16
+ import type { AbortOptions } from '@libp2p/interfaces'
17
+ import { InvalidPBNodeError } from './errors.js'
18
+ import { InvalidParametersError } from '@helia/interface/errors'
19
+
20
+ const log = logger('helia:unixfs:utils:remove-link')
21
+
22
+ export interface RemoveLinkResult {
23
+ node: PBNode
24
+ cid: CID
25
+ }
26
+
27
+ export async function removeLink (parent: Directory, name: string, blockstore: Blockstore, options: AbortOptions): Promise<RemoveLinkResult> {
28
+ if (parent.node.Data == null) {
29
+ throw new InvalidPBNodeError('Parent node had no data')
30
+ }
31
+
32
+ const meta = UnixFS.unmarshal(parent.node.Data)
33
+
34
+ if (meta.type === 'hamt-sharded-directory') {
35
+ log(`Removing ${name} from sharded directory`)
36
+
37
+ return await removeFromShardedDirectory(parent, name, blockstore, options)
38
+ }
39
+
40
+ log(`Removing link ${name} regular directory`)
41
+
42
+ return await removeFromDirectory(parent, name, blockstore, options)
43
+ }
44
+
45
+ const removeFromDirectory = async (parent: Directory, name: string, blockstore: Blockstore, options: AbortOptions): Promise<RemoveLinkResult> => {
46
+ // Remove existing link if it exists
47
+ parent.node.Links = parent.node.Links.filter((link) => {
48
+ return link.Name !== name
49
+ })
50
+
51
+ const parentBlock = dagPB.encode(parent.node)
52
+ const hash = await sha256.digest(parentBlock)
53
+ const parentCid = CID.create(parent.cid.version, dagPB.code, hash)
54
+
55
+ await blockstore.put(parentCid, parentBlock, options)
56
+
57
+ log(`Updated regular directory ${parentCid}`)
58
+
59
+ return {
60
+ node: parent.node,
61
+ cid: parentCid
62
+ }
63
+ }
64
+
65
+ const removeFromShardedDirectory = async (parent: Directory, name: string, blockstore: Blockstore, options: AbortOptions): Promise<UpdateHamtResult> => {
66
+ const {
67
+ rootBucket, path
68
+ } = await generatePath(parent, name, blockstore, options)
69
+
70
+ await rootBucket.del(name)
71
+
72
+ const {
73
+ node
74
+ } = await updateShard(parent, blockstore, path, name, options)
75
+
76
+ return await updateHamtDirectory(parent, blockstore, node.Links, rootBucket, options)
77
+ }
78
+
79
+ const updateShard = async (parent: Directory, blockstore: Blockstore, positions: Array<{ bucket: Bucket<any>, prefix: string, node?: PBNode }>, name: string, options: AbortOptions): Promise<{ node: PBNode, cid: CID, size: number }> => {
80
+ const last = positions.pop()
81
+
82
+ if (last == null) {
83
+ throw new InvalidParametersError('Could not find parent')
84
+ }
85
+
86
+ const {
87
+ bucket,
88
+ prefix,
89
+ node
90
+ } = last
91
+
92
+ if (node == null) {
93
+ throw new InvalidParametersError('Could not find parent')
94
+ }
95
+
96
+ const link = node.Links
97
+ .find(link => (link.Name ?? '').substring(0, 2) === prefix)
98
+
99
+ if (link == null) {
100
+ throw new InvalidParametersError(`No link found with prefix ${prefix} for file ${name}`)
101
+ }
102
+
103
+ if (link.Name === `${prefix}${name}`) {
104
+ log(`Removing existing link ${link.Name}`)
105
+
106
+ const links = node.Links.filter((nodeLink) => {
107
+ return nodeLink.Name !== link.Name
108
+ })
109
+
110
+ await bucket.del(name)
111
+
112
+ parent.node = node
113
+
114
+ return await updateHamtDirectory(parent, blockstore, links, bucket, options)
115
+ }
116
+
117
+ log(`Descending into sub-shard ${link.Name} for ${prefix}${name}`)
118
+
119
+ const result = await updateShard(parent, blockstore, positions, name, options)
120
+
121
+ const child: Required<PBLink> = {
122
+ Hash: result.cid,
123
+ Tsize: result.size,
124
+ Name: prefix
125
+ }
126
+
127
+ if (result.node.Links.length === 1) {
128
+ log(`Removing subshard for ${prefix}`)
129
+
130
+ // convert shard back to normal dir
131
+ const link = result.node.Links[0]
132
+
133
+ child.Name = `${prefix}${(link.Name ?? '').substring(2)}`
134
+ child.Hash = link.Hash
135
+ child.Tsize = link.Tsize ?? 0
136
+ }
137
+
138
+ log(`Updating shard ${prefix} with name ${child.Name}`)
139
+
140
+ return await updateShardParent(parent, child, prefix, blockstore, bucket, options)
141
+ }
142
+
143
+ const updateShardParent = async (parent: Directory, child: Required<PBLink>, oldName: string, blockstore: Blockstore, bucket: Bucket<any>, options: AbortOptions): Promise<UpdateHamtResult> => {
144
+ // Remove existing link if it exists
145
+ const parentLinks = parent.node.Links.filter((link) => {
146
+ return link.Name !== oldName
147
+ })
148
+ parentLinks.push(child)
149
+
150
+ return await updateHamtDirectory(parent, blockstore, parentLinks, bucket, options)
151
+ }
@@ -0,0 +1,130 @@
1
+ import type { CID } from 'multiformats/cid'
2
+ import { Blockstore, exporter } from 'ipfs-unixfs-exporter'
3
+ import type { AbortOptions } from '@libp2p/interfaces'
4
+ import { InvalidParametersError } from '@helia/interface/errors'
5
+ import { logger } from '@libp2p/logger'
6
+ import { DoesNotExistError } from './errors.js'
7
+ import { addLink } from './add-link.js'
8
+ import { cidToDirectory } from './cid-to-directory.js'
9
+ import { cidToPBLink } from './cid-to-pblink.js'
10
+
11
+ const log = logger('helia:unixfs:components:utils:add-link')
12
+
13
+ export interface Segment {
14
+ name: string
15
+ cid: CID
16
+ size: number
17
+ }
18
+
19
+ export interface ResolveResult {
20
+ /**
21
+ * The CID at the end of the path
22
+ */
23
+ cid: CID
24
+
25
+ path?: string
26
+
27
+ /**
28
+ * If present, these are the CIDs and path segments that were traversed through to reach the final CID
29
+ *
30
+ * If not present, there was no path passed or the path was an empty string
31
+ */
32
+ segments?: Segment[]
33
+ }
34
+
35
+ export async function resolve (cid: CID, path: string | undefined, blockstore: Blockstore, options: AbortOptions): Promise<ResolveResult> {
36
+ log('resolve "%s" under %c', path, cid)
37
+
38
+ if (path == null || path === '') {
39
+ return { cid }
40
+ }
41
+
42
+ const parts = path.split('/').filter(Boolean)
43
+ const segments: Segment[] = [{
44
+ name: '',
45
+ cid,
46
+ size: 0
47
+ }]
48
+
49
+ for (let i = 0; i < parts.length; i++) {
50
+ const part = parts[i]
51
+ const result = await exporter(cid, blockstore, options)
52
+
53
+ if (result.type === 'file') {
54
+ if (i < parts.length - 1) {
55
+ throw new InvalidParametersError('Path was invalid')
56
+ }
57
+
58
+ cid = result.cid
59
+ } else if (result.type === 'directory') {
60
+ let dirCid: CID | undefined
61
+
62
+ for await (const entry of result.content()) {
63
+ if (entry.name === part) {
64
+ dirCid = entry.cid
65
+ }
66
+ }
67
+
68
+ if (dirCid == null) {
69
+ throw new DoesNotExistError('Could not find path in directory')
70
+ }
71
+
72
+ cid = dirCid
73
+
74
+ segments.push({
75
+ name: part,
76
+ cid,
77
+ size: result.size
78
+ })
79
+ } else {
80
+ throw new InvalidParametersError('Could not resolve path')
81
+ }
82
+ }
83
+
84
+ return {
85
+ cid,
86
+ path,
87
+ segments
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Where we have descended into a DAG to update a child node, ascend up the DAG creating
93
+ * new hashes and blocks for the changed content
94
+ */
95
+ export async function updatePathCids (cid: CID, result: ResolveResult, blockstore: Blockstore, options: AbortOptions): Promise<CID> {
96
+ if (result.segments == null || result.segments.length === 0) {
97
+ return cid
98
+ }
99
+
100
+ let child = result.segments.pop()
101
+
102
+ if (child == null) {
103
+ throw new Error('Insufficient segments')
104
+ }
105
+
106
+ child.cid = cid
107
+
108
+ result.segments.reverse()
109
+
110
+ for (const parent of result.segments) {
111
+ const [
112
+ directory,
113
+ pblink
114
+ ] = await Promise.all([
115
+ cidToDirectory(parent.cid, blockstore, options),
116
+ cidToPBLink(child.cid, child.name, blockstore, options)
117
+ ])
118
+
119
+ const result = await addLink(directory, pblink, blockstore, {
120
+ ...options,
121
+ allowOverwriting: true
122
+ })
123
+
124
+ cid = result.cid
125
+ parent.cid = cid
126
+ child = parent
127
+ }
128
+
129
+ return cid
130
+ }
package/src/index.ts ADDED
@@ -0,0 +1,174 @@
1
+ import type { CID, Version } from 'multiformats/cid'
2
+ import type { Blockstore } from 'interface-blockstore'
3
+ import type { AbortOptions } from '@libp2p/interfaces'
4
+ import type { ImportCandidate, ImportResult, UserImporterOptions } from 'ipfs-unixfs-importer'
5
+ import { add, addStream } from './commands/add.js'
6
+ import { cat } from './commands/cat.js'
7
+ import { mkdir } from './commands/mkdir.js'
8
+ import type { Mtime } from 'ipfs-unixfs'
9
+ import { cp } from './commands/cp.js'
10
+ import { rm } from './commands/rm.js'
11
+ import { stat } from './commands/stat.js'
12
+ import { touch } from './commands/touch.js'
13
+ import { chmod } from './commands/chmod.js'
14
+ import type { UnixFSEntry } from 'ipfs-unixfs-exporter'
15
+ import { ls } from './commands/ls.js'
16
+
17
+ export interface UnixFSComponents {
18
+ blockstore: Blockstore
19
+ }
20
+
21
+ export interface CatOptions extends AbortOptions {
22
+ offset?: number
23
+ length?: number
24
+ path?: string
25
+ }
26
+
27
+ export interface ChmodOptions extends AbortOptions {
28
+ recursive: boolean
29
+ path?: string
30
+ }
31
+
32
+ export interface CpOptions extends AbortOptions {
33
+ force: boolean
34
+ }
35
+
36
+ export interface LsOptions extends AbortOptions {
37
+ path?: string
38
+ offset?: number
39
+ length?: number
40
+ }
41
+
42
+ export interface MkdirOptions extends AbortOptions {
43
+ cidVersion: Version
44
+ force: boolean
45
+ mode?: number
46
+ mtime?: Mtime
47
+ }
48
+
49
+ export interface RmOptions extends AbortOptions {
50
+
51
+ }
52
+
53
+ export interface StatOptions extends AbortOptions {
54
+ path?: string
55
+ }
56
+
57
+ export interface UnixFSStats {
58
+ /**
59
+ * The file or directory CID
60
+ */
61
+ cid: CID
62
+
63
+ /**
64
+ * The file or directory mode
65
+ */
66
+ mode?: number
67
+
68
+ /**
69
+ * The file or directory mtime
70
+ */
71
+ mtime?: Mtime
72
+
73
+ /**
74
+ * The size of the file in bytes
75
+ */
76
+ fileSize: number
77
+
78
+ /**
79
+ * The size of the DAG that holds the file in bytes
80
+ */
81
+ dagSize: number
82
+
83
+ /**
84
+ * How much of the file is in the local block store
85
+ */
86
+ localFileSize: number
87
+
88
+ /**
89
+ * How much of the DAG that holds the file is in the local blockstore
90
+ */
91
+ localDagSize: number
92
+
93
+ /**
94
+ * How many blocks make up the DAG - nb. this will only be accurate
95
+ * if all blocks are present in the local blockstore
96
+ */
97
+ blocks: number
98
+
99
+ /**
100
+ * The type of file
101
+ */
102
+ type: 'file' | 'directory' | 'raw'
103
+ }
104
+
105
+ export interface TouchOptions extends AbortOptions {
106
+ mtime?: Mtime
107
+ path?: string
108
+ recursive: boolean
109
+ }
110
+
111
+ export interface UnixFS {
112
+ add: (source: Uint8Array | Iterator<Uint8Array> | AsyncIterator<Uint8Array> | ImportCandidate, options?: Partial<UserImporterOptions>) => Promise<CID>
113
+ addStream: (source: Iterable<ImportCandidate> | AsyncIterable<ImportCandidate>, options?: Partial<UserImporterOptions>) => AsyncGenerator<ImportResult>
114
+ cat: (cid: CID, options?: Partial<CatOptions>) => AsyncIterable<Uint8Array>
115
+ chmod: (source: CID, mode: number, options?: Partial<ChmodOptions>) => Promise<CID>
116
+ cp: (source: CID, target: CID, name: string, options?: Partial<CpOptions>) => Promise<CID>
117
+ ls: (cid: CID, options?: Partial<LsOptions>) => AsyncIterable<UnixFSEntry>
118
+ mkdir: (cid: CID, dirname: string, options?: Partial<MkdirOptions>) => Promise<CID>
119
+ rm: (cid: CID, path: string, options?: Partial<RmOptions>) => Promise<CID>
120
+ stat: (cid: CID, options?: Partial<StatOptions>) => Promise<UnixFSStats>
121
+ touch: (cid: CID, options?: Partial<TouchOptions>) => Promise<CID>
122
+ }
123
+
124
+ class DefaultUnixFS implements UnixFS {
125
+ private readonly components: UnixFSComponents
126
+
127
+ constructor (components: UnixFSComponents) {
128
+ this.components = components
129
+ }
130
+
131
+ async add (source: Uint8Array | Iterator<Uint8Array> | AsyncIterator<Uint8Array> | ImportCandidate, options: Partial<UserImporterOptions> = {}): Promise<CID> {
132
+ return await add(source, this.components.blockstore, options)
133
+ }
134
+
135
+ async * addStream (source: Iterable<ImportCandidate> | AsyncIterable<ImportCandidate>, options: Partial<UserImporterOptions> = {}): AsyncGenerator<ImportResult> {
136
+ yield * addStream(source, this.components.blockstore, options)
137
+ }
138
+
139
+ async * cat (cid: CID, options: Partial<CatOptions> = {}): AsyncIterable<Uint8Array> {
140
+ yield * cat(cid, this.components.blockstore, options)
141
+ }
142
+
143
+ async chmod (source: CID, mode: number, options: Partial<ChmodOptions> = {}): Promise<CID> {
144
+ return await chmod(source, mode, this.components.blockstore, options)
145
+ }
146
+
147
+ async cp (source: CID, target: CID, name: string, options: Partial<CpOptions> = {}): Promise<CID> {
148
+ return await cp(source, target, name, this.components.blockstore, options)
149
+ }
150
+
151
+ async * ls (cid: CID, options: Partial<LsOptions> = {}): AsyncIterable<UnixFSEntry> {
152
+ yield * ls(cid, this.components.blockstore, options)
153
+ }
154
+
155
+ async mkdir (cid: CID, dirname: string, options: Partial<MkdirOptions> = {}): Promise<CID> {
156
+ return await mkdir(cid, dirname, this.components.blockstore, options)
157
+ }
158
+
159
+ async rm (cid: CID, path: string, options: Partial<RmOptions> = {}): Promise<CID> {
160
+ return await rm(cid, path, this.components.blockstore, options)
161
+ }
162
+
163
+ async stat (cid: CID, options: Partial<StatOptions> = {}): Promise<UnixFSStats> {
164
+ return await stat(cid, this.components.blockstore, options)
165
+ }
166
+
167
+ async touch (cid: CID, options: Partial<TouchOptions> = {}): Promise<CID> {
168
+ return await touch(cid, this.components.blockstore, options)
169
+ }
170
+ }
171
+
172
+ export function unixfs (helia: { blockstore: Blockstore }): UnixFS {
173
+ return new DefaultUnixFS(helia)
174
+ }