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