cbyte 1.4.0 → 1.6.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 (2) hide show
  1. package/cbyte.js +83 -81
  2. package/package.json +7 -4
package/cbyte.js CHANGED
@@ -1,45 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- require('events').defaultMaxListeners = 6000;
4
-
5
- const fs = require('fs');
6
- const crypto = require('crypto');
7
- const { program } = require('commander');
8
- const { create } = require('ipfs-core');
9
- const os = require('os');
10
- const path = require('path');
11
-
12
- let ipfs = null;
13
-
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.');
3
+ // Increase listener limit to avoid warnings
4
+ // require('events').defaultMaxListeners = 6000;
5
+ import evt from 'events';
6
+ import fs from 'fs';
7
+ import crypto from 'crypto';
8
+ import { Command } from 'commander';
9
+ import { createHelia } from 'helia';
10
+ import * as Block from 'multiformats/block';
11
+ import * as rawCodec from 'multiformats/codecs/raw';
12
+ import * as sha256 from 'multiformats/hashes/sha2';
13
+ import os from 'os';
14
+ import path from 'path';
15
+
16
+ evt.defaultMaxListeners = 6000;
17
+
18
+ const program = new Command();
19
+ let helia = null;
20
+
21
+ // Initialize Helia node once
22
+ async function ensureHelia() {
23
+ if (!helia) {
24
+ helia = await createHelia();
20
25
  }
21
26
  }
22
27
 
23
- // ------------------- Utility Functions -------------------
24
- function sha256(data) {
25
- return crypto.createHash('sha256').update(data).digest('hex');
26
- }
27
-
28
+ // Simple SHA‑256 progress bar
28
29
  function renderProgress(completed, total, label = '') {
29
30
  const width = 40;
30
31
  const ratio = completed / total;
31
32
  const filled = Math.floor(ratio * width);
32
33
  const bar = '='.repeat(filled) + ' '.repeat(width - filled);
33
- process.stderr.write(`\r${label} [${bar}] ${(ratio*100).toFixed(1)}% (${completed}/${total})`);
34
+ process.stderr.write(`\r${label} [${bar}] ${(ratio * 100).toFixed(1)}% (${completed}/${total})`);
34
35
  }
35
36
 
36
- // ------------------- Pack -------------------
37
- async function pack(filePath, output, chunkSize = 4096, pin = false) {
38
- await ensureIpfs();
37
+ // Pack operation with Helia
38
+ async function pack(filePath, outFile, chunkSize = 4096, pin = false) {
39
+ await ensureHelia();
39
40
 
40
- if (!output) {
41
+ if (!outFile) {
41
42
  const base = path.basename(filePath, path.extname(filePath));
42
- output = `${base}.cbyte`;
43
+ outFile = `${base}.cbyte`;
43
44
  }
44
45
 
45
46
  const data = fs.readFileSync(filePath);
@@ -47,7 +48,8 @@ async function pack(filePath, output, chunkSize = 4096, pin = false) {
47
48
 
48
49
  for (let offset = 0; offset < data.length; offset += chunkSize) {
49
50
  const chunk = data.slice(offset, offset + chunkSize);
50
- const hash = sha256(chunk);
51
+ const hashVal = crypto.createHash('sha256').update(chunk).digest();
52
+ const hash = Buffer.from(hashVal).toString('hex');
51
53
  if (!chunksMap[hash]) chunksMap[hash] = { chunk, count: 0 };
52
54
  chunksMap[hash].count++;
53
55
  }
@@ -62,73 +64,83 @@ async function pack(filePath, output, chunkSize = 4096, pin = false) {
62
64
  };
63
65
 
64
66
  const hashes = Object.keys(chunksMap);
65
- const total = hashes.length;
66
- let completed = 0;
67
+ let uploaded = 0;
67
68
 
68
69
  const concurrency = Math.max(1, Math.min(8, os.cpus().length));
69
70
  for (let i = 0; i < hashes.length; i += concurrency) {
70
71
  const batch = hashes.slice(i, i + concurrency);
71
- await Promise.all(batch.map(async (hash) => {
72
- const info = chunksMap[hash];
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');
72
+ await Promise.all(batch.map(async (h) => {
73
+ const info = chunksMap[h];
74
+
75
+ // Create a raw block; multiformats block API
76
+ const block = await Block.encode({
77
+ value: info.chunk,
78
+ codec: rawCodec,
79
+ hasher: sha256.sha256
80
+ });
81
+
82
+ await helia.blockstore.put(block.cid, block.bytes);
83
+
84
+ manifest.chunks.push({
85
+ cid: block.cid.toString(),
86
+ length: info.chunk.length,
87
+ count: info.count
88
+ });
89
+
90
+ uploaded++;
91
+ renderProgress(uploaded, hashes.length, 'Packing');
78
92
  }));
79
93
  }
80
94
  process.stderr.write('\n');
81
95
 
82
- fs.writeFileSync(output, JSON.stringify(manifest, null, 2));
83
- console.log(`Packed ${filePath} -> ${output}`);
96
+ fs.writeFileSync(outFile, JSON.stringify(manifest, null, 2));
97
+ console.log(`Packed ${filePath} -> ${outFile}`);
84
98
  }
85
99
 
86
- // ------------------- Unpack -------------------
87
- async function unpack(manifestPath, output) {
88
- await ensureIpfs();
100
+ // Unpack using Helia
101
+ async function unpack(manifestPath, outFile) {
102
+ await ensureHelia();
89
103
 
90
104
  const manifest = JSON.parse(fs.readFileSync(manifestPath));
91
- if (!output) {
105
+ if (!outFile) {
92
106
  const base = path.basename(manifestPath, path.extname(manifestPath));
93
107
  const ext = manifest.original_ext || '.out';
94
- output = `${base}${ext}`;
108
+ outFile = `${base}${ext}`;
95
109
  }
96
110
 
97
- const outFd = fs.openSync(output, 'w');
98
- const totalChunks = manifest.chunks.reduce((a, c) => a + c.count, 0);
111
+ const fd = fs.openSync(outFile, 'w');
112
+ const total = manifest.chunks.reduce((sum, e) => sum + e.count, 0);
99
113
  let completed = 0;
100
114
 
101
115
  const concurrency = Math.max(1, Math.min(8, os.cpus().length));
102
116
  for (let i = 0; i < manifest.chunks.length; i += concurrency) {
103
117
  const batch = manifest.chunks.slice(i, i + concurrency);
104
118
  await Promise.all(batch.map(async (entry) => {
105
- const data = Buffer.from(await ipfs.block.get(entry.cid));
119
+ const blockBytes = await helia.blockstore.get(entry.cid);
106
120
  for (let j = 0; j < entry.count; j++) {
107
- fs.writeSync(outFd, data.slice(0, entry.length));
121
+ fs.writeSync(fd, blockBytes.slice(0, entry.length));
108
122
  completed++;
109
- renderProgress(completed, totalChunks, 'Unpacking');
123
+ renderProgress(completed, total, 'Unpacking');
110
124
  }
111
125
  }));
112
126
  }
113
-
114
127
  process.stderr.write('\n');
115
- fs.ftruncateSync(outFd, manifest.original_size);
116
- fs.closeSync(outFd);
117
- console.log(`Unpacked -> ${output}`);
128
+
129
+ fs.ftruncateSync(fd, manifest.original_size);
130
+ fs.closeSync(fd);
131
+ console.log(`Unpacked -> ${outFile}`);
118
132
  }
119
133
 
120
- // ------------------- Verify -------------------
134
+ // Verify blocks exist in Helia
121
135
  async function verify(manifestPath) {
122
- await ensureIpfs();
123
-
136
+ await ensureHelia();
124
137
  const manifest = JSON.parse(fs.readFileSync(manifestPath));
125
- let verified = 0;
126
-
138
+ let checked = 0;
127
139
  for (const { cid } of manifest.chunks) {
128
140
  try {
129
- await ipfs.block.get(cid);
130
- verified++;
131
- renderProgress(verified, manifest.chunks.length, 'Verifying');
141
+ await helia.blockstore.get(cid);
142
+ checked++;
143
+ renderProgress(checked, manifest.chunks.length, 'Verifying');
132
144
  } catch {
133
145
  console.error(`Missing block ${cid}`);
134
146
  process.exit(1);
@@ -138,15 +150,13 @@ async function verify(manifestPath) {
138
150
  console.log('All blocks verified');
139
151
  }
140
152
 
141
- // ------------------- Pin -------------------
153
+ // Pin via Helia’s pin API
142
154
  async function pin(manifestPath) {
143
- await ensureIpfs();
144
-
155
+ await ensureHelia();
145
156
  const manifest = JSON.parse(fs.readFileSync(manifestPath));
146
157
  let pinned = 0;
147
-
148
158
  for (const { cid } of manifest.chunks) {
149
- await ipfs.pin.add(cid);
159
+ await helia.pin.add(cid);
150
160
  pinned++;
151
161
  renderProgress(pinned, manifest.chunks.length, 'Pinning');
152
162
  }
@@ -154,36 +164,28 @@ async function pin(manifestPath) {
154
164
  console.log('All blocks pinned');
155
165
  }
156
166
 
157
- // ------------------- CLI -------------------
167
+ // CLI definition
158
168
  program.version('1.0.0');
159
169
 
160
170
  program
161
171
  .command('pack <file>')
162
172
  .option('-o, --output <file>', 'Output manifest')
163
173
  .option('--chunk-size <bytes>', 'Chunk size', '4096')
164
- .option('--pin', 'Pin blocks after upload', false)
165
- .action(async (file, options) => {
166
- await pack(file, options.output, parseInt(options.chunkSize), options.pin);
167
- });
174
+ .option('--pin', 'Pin blocks after pack', false)
175
+ .action((file, opts) => pack(file, opts.output, parseInt(opts['chunkSize']), opts.pin));
168
176
 
169
177
  program
170
178
  .command('unpack <manifest>')
171
179
  .option('-o, --output <file>', 'Output file')
172
- .action(async (manifest, options) => {
173
- await unpack(manifest, options.output);
174
- });
180
+ .action((m, opts) => unpack(m, opts.output));
175
181
 
176
182
  program
177
183
  .command('verify <manifest>')
178
- .action(async (manifest) => {
179
- await verify(manifest);
180
- });
184
+ .action((m) => verify(m));
181
185
 
182
186
  program
183
187
  .command('pin <manifest>')
184
- .action(async (manifest) => {
185
- await pin(manifest);
186
- });
188
+ .action((m) => pin(m));
187
189
 
188
190
  program.parse(process.argv);
189
191
 
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "cbyte",
3
- "version": "1.4.0",
3
+ "type": "module",
4
+ "version": "1.6.0",
4
5
  "description": "Content-addressed deduplicated storage CLI using IPFS with automatic unpack extension retention",
5
6
  "main": "cbyte.js",
6
7
  "bin": {
@@ -16,13 +17,15 @@
16
17
  "storage",
17
18
  "content-addressed"
18
19
  ],
19
- "author": "Your Name <you@example.com>",
20
+ "author": "Coperbyte <coperbyte@coperbyte.com>",
20
21
  "license": "MIT",
21
22
  "dependencies": {
23
+ "@helia/unixfs": "^7.0.1",
22
24
  "commander": "^11.0.0",
23
25
  "form-data": "^4.0.0",
24
- "undici": "^6.0.0",
25
- "ipfs-core": "^0.18.1"
26
+ "helia": "^6.0.16",
27
+ "multiformats": "^13.4.2",
28
+ "undici": "^6.0.0"
26
29
  },
27
30
  "engines": {
28
31
  "node": ">=20"