cbyte 1.0.0 → 1.3.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.
package/cbyte.js CHANGED
@@ -1,49 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // Prevent MaxListenersExceededWarning
4
3
  require('events').defaultMaxListeners = 6000;
5
4
 
6
5
  const fs = require('fs');
7
6
  const crypto = require('crypto');
8
7
  const { program } = require('commander');
9
- const { request } = require('undici');
10
- const FormData = require('form-data');
8
+ const { create } = require('ipfs-core');
11
9
  const os = require('os');
12
10
  const path = require('path');
13
- const { spawn } = require('child_process');
14
- const net = require('net');
15
-
16
- const IPFS_API = process.env.IPFS_API || 'http://127.0.0.1:5001/api/v0';
17
-
18
- // ------------------- IPFS Daemon Helpers -------------------
19
- function isPortOpen(host, port) {
20
- return new Promise((resolve) => {
21
- const socket = new net.Socket();
22
- socket.setTimeout(1000);
23
- socket.once('connect', () => { socket.destroy(); resolve(true); });
24
- socket.once('timeout', () => { socket.destroy(); resolve(false); });
25
- socket.once('error', () => { resolve(false); });
26
- socket.connect(port, host);
27
- });
28
- }
29
-
30
- async function ensureIpfsDaemon() {
31
- const host = '127.0.0.1';
32
- const port = 5001;
33
11
 
34
- const running = await isPortOpen(host, port);
35
- if (running) return;
12
+ let ipfs = null;
36
13
 
37
- console.log('IPFS daemon not running. Starting it now...');
38
- const daemon = spawn('ipfs', ['daemon'], { stdio: 'ignore', detached: true });
39
- daemon.unref();
40
-
41
- let attempts = 0;
42
- while (!(await isPortOpen(host, port))) {
43
- if (attempts++ > 20) throw new Error('Failed to start IPFS daemon.');
44
- await new Promise(r => setTimeout(r, 500));
14
+ // ------------------- IPFS Initialization -------------------
15
+ async function ensureIpfs() {
16
+ if (!ipfs) {
17
+ console.log('Starting embedded IPFS node...');
18
+ ipfs = await create({ repo: path.join(process.cwd(), '.ipfs') });
19
+ console.log('IPFS node started.');
45
20
  }
46
- console.log('IPFS daemon started.');
47
21
  }
48
22
 
49
23
  // ------------------- Utility Functions -------------------
@@ -59,30 +33,10 @@ function renderProgress(completed, total, label = '') {
59
33
  process.stderr.write(`\r${label} [${bar}] ${(ratio*100).toFixed(1)}% (${completed}/${total})`);
60
34
  }
61
35
 
62
- // ------------------- IPFS Block Operations -------------------
63
- async function ipfsBlockPut(data) {
64
- const form = new FormData();
65
- form.append('file', data, { filename: 'chunk' });
66
-
67
- const res = await request(`${IPFS_API}/block/put?format=raw&mhtype=sha2-256`, {
68
- method: 'POST',
69
- body: form,
70
- headers: form.getHeaders()
71
- });
72
-
73
- const body = await res.body.text();
74
- const json = JSON.parse(body);
75
- return json.Key;
76
- }
77
-
78
- async function ipfsBlockGet(cid) {
79
- const res = await request(`${IPFS_API}/block/get?arg=${cid}`);
80
- const buffer = await res.body.arrayBuffer();
81
- return Buffer.from(buffer);
82
- }
83
-
84
- // ------------------- Pack Function -------------------
36
+ // ------------------- Pack -------------------
85
37
  async function pack(filePath, output, chunkSize = 4096, pin = false) {
38
+ await ensureIpfs();
39
+
86
40
  if (!output) {
87
41
  const base = path.basename(filePath, path.extname(filePath));
88
42
  output = `${base}.cbyte`;
@@ -90,6 +44,7 @@ async function pack(filePath, output, chunkSize = 4096, pin = false) {
90
44
 
91
45
  const data = fs.readFileSync(filePath);
92
46
  const chunksMap = {};
47
+
93
48
  for (let offset = 0; offset < data.length; offset += chunkSize) {
94
49
  const chunk = data.slice(offset, offset + chunkSize);
95
50
  const hash = sha256(chunk);
@@ -102,24 +57,24 @@ async function pack(filePath, output, chunkSize = 4096, pin = false) {
102
57
  version: 1,
103
58
  chunk_size: chunkSize,
104
59
  original_size: data.length,
105
- original_ext: path.extname(filePath), // store original file extension
60
+ original_ext: path.extname(filePath),
106
61
  chunks: []
107
62
  };
108
63
 
109
64
  const hashes = Object.keys(chunksMap);
110
65
  const total = hashes.length;
111
- let uploaded = 0;
66
+ let completed = 0;
112
67
 
113
68
  const concurrency = Math.max(1, Math.min(8, os.cpus().length));
114
69
  for (let i = 0; i < hashes.length; i += concurrency) {
115
70
  const batch = hashes.slice(i, i + concurrency);
116
71
  await Promise.all(batch.map(async (hash) => {
117
72
  const info = chunksMap[hash];
118
- const cid = await ipfsBlockPut(info.chunk);
119
- if (pin) await request(`${IPFS_API}/pin/add?arg=${cid}`);
120
- manifest.chunks.push({ cid, length: info.chunk.length, count: info.count });
121
- uploaded++;
122
- renderProgress(uploaded, total, 'Uploading chunks');
73
+ const { cid } = await ipfs.block.put(info.chunk);
74
+ if (pin) await ipfs.pin.add(cid);
75
+ manifest.chunks.push({ cid: cid.toString(), length: info.chunk.length, count: info.count });
76
+ completed++;
77
+ renderProgress(completed, total, 'Packing');
123
78
  }));
124
79
  }
125
80
  process.stderr.write('\n');
@@ -128,8 +83,10 @@ async function pack(filePath, output, chunkSize = 4096, pin = false) {
128
83
  console.log(`Packed ${filePath} -> ${output}`);
129
84
  }
130
85
 
131
- // ------------------- Unpack Function -------------------
86
+ // ------------------- Unpack -------------------
132
87
  async function unpack(manifestPath, output) {
88
+ await ensureIpfs();
89
+
133
90
  const manifest = JSON.parse(fs.readFileSync(manifestPath));
134
91
  if (!output) {
135
92
  const base = path.basename(manifestPath, path.extname(manifestPath));
@@ -145,7 +102,7 @@ async function unpack(manifestPath, output) {
145
102
  for (let i = 0; i < manifest.chunks.length; i += concurrency) {
146
103
  const batch = manifest.chunks.slice(i, i + concurrency);
147
104
  await Promise.all(batch.map(async (entry) => {
148
- const data = await ipfsBlockGet(entry.cid);
105
+ const data = Buffer.from(await ipfs.block.get(entry.cid));
149
106
  for (let j = 0; j < entry.count; j++) {
150
107
  fs.writeSync(outFd, data.slice(0, entry.length));
151
108
  completed++;
@@ -160,16 +117,19 @@ async function unpack(manifestPath, output) {
160
117
  console.log(`Unpacked -> ${output}`);
161
118
  }
162
119
 
163
- // ------------------- Verify Function -------------------
120
+ // ------------------- Verify -------------------
164
121
  async function verify(manifestPath) {
122
+ await ensureIpfs();
123
+
165
124
  const manifest = JSON.parse(fs.readFileSync(manifestPath));
166
125
  let verified = 0;
126
+
167
127
  for (const { cid } of manifest.chunks) {
168
128
  try {
169
- await ipfsBlockGet(cid);
129
+ await ipfs.block.get(cid);
170
130
  verified++;
171
131
  renderProgress(verified, manifest.chunks.length, 'Verifying');
172
- } catch (e) {
132
+ } catch {
173
133
  console.error(`Missing block ${cid}`);
174
134
  process.exit(1);
175
135
  }
@@ -178,12 +138,15 @@ async function verify(manifestPath) {
178
138
  console.log('All blocks verified');
179
139
  }
180
140
 
181
- // ------------------- Pin Function -------------------
141
+ // ------------------- Pin -------------------
182
142
  async function pin(manifestPath) {
143
+ await ensureIpfs();
144
+
183
145
  const manifest = JSON.parse(fs.readFileSync(manifestPath));
184
146
  let pinned = 0;
147
+
185
148
  for (const { cid } of manifest.chunks) {
186
- await request(`${IPFS_API}/pin/add?arg=${cid}`);
149
+ await ipfs.pin.add(cid);
187
150
  pinned++;
188
151
  renderProgress(pinned, manifest.chunks.length, 'Pinning');
189
152
  }
@@ -200,29 +163,25 @@ program
200
163
  .option('--chunk-size <bytes>', 'Chunk size', '4096')
201
164
  .option('--pin', 'Pin blocks after upload', false)
202
165
  .action(async (file, options) => {
203
- await ensureIpfsDaemon();
204
- await pack(file, options.output, parseInt(options['chunkSize']), options.pin);
166
+ await pack(file, options.output, parseInt(options.chunkSize), options.pin);
205
167
  });
206
168
 
207
169
  program
208
170
  .command('unpack <manifest>')
209
171
  .option('-o, --output <file>', 'Output file')
210
172
  .action(async (manifest, options) => {
211
- await ensureIpfsDaemon();
212
173
  await unpack(manifest, options.output);
213
174
  });
214
175
 
215
176
  program
216
177
  .command('verify <manifest>')
217
178
  .action(async (manifest) => {
218
- await ensureIpfsDaemon();
219
179
  await verify(manifest);
220
180
  });
221
181
 
222
182
  program
223
183
  .command('pin <manifest>')
224
184
  .action(async (manifest) => {
225
- await ensureIpfsDaemon();
226
185
  await pin(manifest);
227
186
  });
228
187
 
package/package.json CHANGED
@@ -1,18 +1,29 @@
1
1
  {
2
2
  "name": "cbyte",
3
- "version": "1.0.0",
3
+ "version": "1.3.0",
4
4
  "description": "Content-addressed deduplicated storage CLI using IPFS with automatic unpack extension retention",
5
5
  "main": "cbyte.js",
6
- "bin": { "cbyte": "./cbyte.js" },
7
- "scripts": { "test": "node test.js" },
8
- "keywords": ["ipfs","cli","deduplication","storage","content-addressed"],
6
+ "bin": {
7
+ "cbyte": "./cbyte.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node test.js"
11
+ },
12
+ "keywords": [
13
+ "ipfs",
14
+ "cli",
15
+ "deduplication",
16
+ "storage",
17
+ "content-addressed"
18
+ ],
9
19
  "author": "Your Name <you@example.com>",
10
20
  "license": "MIT",
11
21
  "dependencies": {
12
22
  "commander": "^11.0.0",
13
- "undici": "^6.0.0",
14
- "form-data": "^4.0.0"
23
+ "form-data": "^4.0.0",
24
+ "undici": "^6.0.0"
15
25
  },
16
- "engines": { "node": ">=20" }
26
+ "engines": {
27
+ "node": ">=20"
28
+ }
17
29
  }
18
-