cbyte 1.11.0 → 1.13.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/99367107.cbyte CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "format": "cbyte",
3
- "version": 1.1,
3
+ "version": 1.11,
4
4
  "chunk_size": 4096,
5
5
  "original_size": 16552,
6
6
  "original_ext": ".pdf",
package/README.md CHANGED
@@ -1,208 +1,16 @@
1
- # cbyte
1
+ # cbyte v1.13
2
2
 
3
- **cbyte** is a Node.js CLI utility for content-addressed, deduplicated storage using IPFS. It allows you to pack files into manifests (`.cbyte`), reconstruct them, verify integrity, and manage IPFS pinning.
4
-
5
- ---
3
+ cbyte is an offline, content-addressed, deduplicated file packer built on Helia.
6
4
 
7
5
  ## Features
6
+ - Streaming chunking (large-file safe)
7
+ - Deterministic manifests
8
+ - Persistent deduplicated blockstore
9
+ - No IPFS daemon, no networking
10
+ - Clean CLI exit
8
11
 
9
- * Deduplicated storage using SHA-256 hashes
10
- * Fixed-size chunking for large files
11
- * IPFS raw block backend (no UnixFS overhead)
12
- * Deterministic `.cbyte` manifests
13
- * Parallel unpacking with safe ordered writes
14
- * ASCII progress bar during unpack
15
- * CLI commands: `pack`, `unpack`, `verify`, `pin`
16
-
17
- ---
18
-
19
- ## Requirements
20
-
21
- * Node.js >= 20
22
- * npm
23
- * IPFS (Kubo) >= 0.21
24
- * Linux environment (POSIX-compliant filesystem)
25
-
26
- ---
27
-
28
- ## Installation
29
-
30
- ### Using npm
31
-
32
- ```bash
33
- npm install -g cbyte
34
- ```
35
-
36
- ### Manual (from GitHub or tarball)
37
-
38
- ```bash
39
- git clone https://github.com/yourusername/cbyte.git
40
- cd cbyte
41
- npm install
42
- chmod +x cbyte.js
43
- sudo ln -sf $(pwd)/cbyte.js /usr/local/bin/cbyte
44
- ```
45
-
46
- Ensure IPFS daemon is running:
47
-
48
- ```bash
49
- ipfs daemon &
50
- ```
51
-
52
- ---
53
-
54
- ## CLI Usage
55
-
56
- ```text
57
- cbyte <command> [options]
58
- ```
59
-
60
- ### Commands
61
-
62
- | Command | Description |
63
- | ----------------- | ------------------------------------------- |
64
- | pack <file> | Pack a file into a `.cbyte` manifest |
65
- | unpack <manifest> | Reconstruct the original file from manifest |
66
- | verify <manifest> | Verify availability & integrity of blocks |
67
- | pin <manifest> | Pin all blocks referenced by manifest |
68
-
69
- ### Options
70
-
71
- * `-o, --output <file>`: specify output file (manifest or restored file)
72
- * `--chunk-size <bytes>`: size of chunks in bytes (default 4096)
73
- * `--pin`: automatically pin blocks during pack
74
- * `-h, --help`: show help for command
75
-
76
- ---
77
-
78
- ## Examples
79
-
80
- ```bash
81
- # Pack a file
82
- cbyte pack bigfile.bin -o bigfile.cbyte --chunk-size 4096 --pin
83
-
84
- # Unpack with ASCII progress
85
- cbyte unpack bigfile.cbyte -o bigfile_restored.bin
86
-
87
- # Verify blocks
88
- cbyte verify bigfile.cbyte
89
-
90
- # Pin all blocks
91
- cbyte pin bigfile.cbyte
92
- ```
93
-
94
- ASCII progress bar example during unpack:
95
-
96
- ```
97
- [====================== ] 62.5% (500/800)
98
- ```
99
-
100
- ---
101
-
102
- ## Manifest Format
103
-
104
- ```json
105
- {
106
- "format": "cbyte",
107
- "version": 1,
108
- "chunk_size": 4096,
109
- "original_size": 5242880,
110
- "chunks": [
111
- { "cid": "bafkreigh2akiscaildcg...", "length": 4096, "count": 2 }
112
- ]
113
- }
114
- ```
115
-
116
- * `cid`: IPFS content identifier
117
- * `length`: bytes in the chunk
118
- * `count`: repeated occurrences
119
- * `chunk_size`: chunk size used during packing
120
-
121
- ---
122
-
123
- ## Testing
124
-
125
- ```bash
126
- # Small file
127
- echo "test data" > testfile.txt
128
- cbyte pack testfile.txt -o testfile.cbyte --chunk-size 32 --pin
129
- cbyte unpack testfile.cbyte -o restored.txt
130
- diff testfile.txt restored.txt
131
-
132
- # Large file
133
- dd if=/dev/urandom of=bigfile.bin bs=1M count=5
134
- cbyte pack bigfile.bin -o bigfile.cbyte --chunk-size 4096 --pin
135
- cbyte unpack bigfile.cbyte -o bigfile_restored.bin
136
- diff bigfile.bin bigfile_restored.bin
137
- ```
138
-
139
- ---
140
-
141
- ## Safety Notes
142
-
143
- * Fetching or unpacking fails if any block is missing
144
- * Parallel fetching does not reorder writes; output is deterministic
145
- * CID ensures cryptographic integrity
146
- * Use `--pin` to persist blocks on your IPFS node
147
-
148
- ---
149
-
150
- ## License
151
-
152
- MIT
153
-
154
- ---
155
-
156
- ## Repository and Issues
157
-
158
- * GitHub: [https://github.com/yourusername/cbyte](https://github.com/yourusername/cbyte)
159
- * Issues: [https://github.com/yourusername/cbyte/issues](https://github.com/yourusername/cbyte/issues)
160
-
161
-
162
- **cbyte** is a Node.js CLI utility for content-addressed, deduplicated storage using IPFS. It allows you to pack files into manifests (`.cbyte`), reconstruct them, verify integrity, and manage IPFS pinning.
163
-
164
- ## Features
165
- - Deduplicated storage using SHA-256 hashes
166
- - Fixed-size chunking for large files
167
- - IPFS raw block backend (no UnixFS overhead)
168
- - Deterministic `.cbyte` manifests
169
- - Parallel unpacking with safe ordered writes
170
- - ASCII progress bar during unpack
171
- - CLI commands: `pack`, `unpack`, `verify`, `pin`
172
-
173
- ## Installation
174
-
175
- ### Using npm
176
- ```
177
- npm install -g cbyte
178
- ```
179
-
180
- ### Manual
181
- ```
182
- git clone https://github.com/yourusername/cbyte.git
183
- cd cbyte
184
- npm install
185
- chmod +x cbyte.js
186
- sudo ln -sf $(pwd)/cbyte.js /usr/local/bin/cbyte
187
- ```
188
-
189
- Ensure IPFS daemon is running:
190
- ```
191
- ipfs daemon &
192
- ```
193
-
194
- ## CLI Usage
195
- ```
196
- cbyte <command> [options]
197
- ```
198
-
199
- Commands: `pack`, `unpack`, `verify`, `pin`
200
-
201
- ## Examples
202
- ```
203
- cbyte pack bigfile.bin -o bigfile.cbyte --chunk-size 4096 --pin
204
- cbyte unpack bigfile.cbyte -o bigfile_restored.bin
205
- cbyte verify bigfile.cbyte
206
- cbyte pin bigfile.cbyte
207
- ```
12
+ ## Usage
13
+ cbyte pack file.bin
14
+ cbyte unpack file.bin.cbyte
208
15
 
16
+ Requires Node.js >= 20.8.0
Binary file
package/cbyte.js CHANGED
@@ -1,23 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // Increase listener limit to avoid warnings
4
- import evt from 'events';
5
- import fs from 'fs';
6
- import crypto from 'crypto';
7
- import { Command } from 'commander';
8
- import { createHelia } from 'helia';
9
- import * as Block from 'multiformats/block';
10
- import * as rawCodec from 'multiformats/codecs/raw';
11
- import * as sha256 from 'multiformats/hashes/sha2';
12
- import os from 'os';
13
- import path from 'path';
14
-
15
- evt.defaultMaxListeners = 6000;
16
-
17
- const program = new Command();
18
- let helia = null;
19
-
20
- // ---- Promise.withResolvers polyfill (Node < 22) ----
3
+ const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number)
4
+ if (NODE_MAJOR < 20 || (NODE_MAJOR === 20 && NODE_MINOR < 8)) {
5
+ console.error('cbyte requires Node.js >= 20.8.0')
6
+ process.exit(1)
7
+ }
8
+
21
9
  if (typeof Promise.withResolvers !== 'function') {
22
10
  Promise.withResolvers = function () {
23
11
  let resolve, reject
@@ -29,174 +17,115 @@ if (typeof Promise.withResolvers !== 'function') {
29
17
  }
30
18
  }
31
19
 
32
- // Initialize Helia node once
33
- async function ensureHelia() {
34
- if (!helia) {
35
- helia = await createHelia();
36
- }
37
- }
20
+ const fs = require('fs')
21
+ const path = require('path')
22
+ const os = require('os')
23
+ const crypto = require('crypto')
24
+ const { program } = require('commander')
25
+ const { EventEmitter } = require('events')
26
+
27
+ EventEmitter.defaultMaxListeners = 6000
28
+
29
+ async function createHeliaNode(storePath) {
30
+ const { createHelia } = await import('helia')
31
+ const { FsBlockstore } = await import('blockstore-fs')
38
32
 
39
- // Simple SHA‑256 progress bar
40
- function renderProgress(completed, total, label = '') {
41
- const width = 40;
42
- const ratio = completed / total;
43
- const filled = Math.floor(ratio * width);
44
- const bar = '='.repeat(filled) + ' '.repeat(width - filled);
45
- process.stderr.write(`\r${label} [${bar}] ${(ratio * 100).toFixed(1)}% (${completed}/${total})`);
33
+ const blockstore = new FsBlockstore(storePath)
34
+ const helia = await createHelia({ blockstore, libp2p: false })
35
+ return { helia, blockstore }
46
36
  }
47
37
 
48
- // Pack operation with Helia
49
- async function pack(filePath, outFile, chunkSize = 4096, pin = false) {
50
- await ensureHelia();
38
+ function sha256(buf) {
39
+ return crypto.createHash('sha256').update(buf).digest('hex')
40
+ }
51
41
 
52
- if (!outFile) {
53
- const base = path.basename(filePath, path.extname(filePath));
54
- outFile = `${base}.cbyte`;
55
- }
42
+ function renderProgress(label, done, total) {
43
+ const width = 40
44
+ const ratio = total === 0 ? 1 : done / total
45
+ const filled = Math.floor(ratio * width)
46
+ const bar = '='.repeat(filled) + ' '.repeat(width - filled)
47
+ process.stderr.write(`\r${label} [${bar}] ${(ratio * 100).toFixed(1)}%`)
48
+ }
56
49
 
57
- const data = fs.readFileSync(filePath);
58
- const chunksMap = {};
50
+ async function pack(inputFile, outputFile, chunkSize) {
51
+ const storeDir = path.join(os.homedir(), '.cbyte', 'blocks')
52
+ fs.mkdirSync(storeDir, { recursive: true })
59
53
 
60
- for (let offset = 0; offset < data.length; offset += chunkSize) {
61
- const chunk = data.slice(offset, offset + chunkSize);
62
- const hashVal = crypto.createHash('sha256').update(chunk).digest();
63
- const hash = Buffer.from(hashVal).toString('hex');
64
- if (!chunksMap[hash]) chunksMap[hash] = { chunk, count: 0 };
65
- chunksMap[hash].count++;
66
- }
54
+ const { helia, blockstore } = await createHeliaNode(storeDir)
55
+ const stat = fs.statSync(inputFile)
67
56
 
68
57
  const manifest = {
69
58
  format: 'cbyte',
70
- version: 1.11,
59
+ version: '1.13',
60
+ original_size: stat.size,
71
61
  chunk_size: chunkSize,
72
- original_size: data.length,
73
- original_ext: path.extname(filePath),
62
+ extension: path.extname(inputFile),
74
63
  chunks: []
75
- };
76
-
77
- const hashes = Object.keys(chunksMap);
78
- let uploaded = 0;
79
-
80
- const concurrency = Math.max(1, Math.min(8, os.cpus().length));
81
- for (let i = 0; i < hashes.length; i += concurrency) {
82
- const batch = hashes.slice(i, i + concurrency);
83
- await Promise.all(batch.map(async (h) => {
84
- const info = chunksMap[h];
85
-
86
- // Create a raw block; multiformats block API
87
- const block = await Block.encode({
88
- value: info.chunk,
89
- codec: rawCodec,
90
- hasher: sha256.sha256
91
- });
92
-
93
- await helia.blockstore.put(block.cid, block.bytes);
94
-
95
- manifest.chunks.push({
96
- cid: block.cid.toString(),
97
- length: info.chunk.length,
98
- count: info.count
99
- });
100
-
101
- uploaded++;
102
- renderProgress(uploaded, hashes.length, 'Packing ...');
103
- }));
104
64
  }
105
- process.stderr.write('\n');
106
65
 
107
- fs.writeFileSync(outFile, JSON.stringify(manifest, null, 2));
108
- console.log(`Packed ${filePath} -> ${outFile}`);
109
- }
66
+ const seen = new Map()
67
+ let processed = 0
68
+
69
+ const stream = fs.createReadStream(inputFile, { highWaterMark: chunkSize })
70
+ for await (const chunk of stream) {
71
+ const hash = sha256(chunk)
72
+ let entry = seen.get(hash)
73
+ if (!entry) {
74
+ const cid = await blockstore.put(chunk)
75
+ entry = { cid: cid.toString(), length: chunk.length, count: 0 }
76
+ seen.set(hash, entry)
77
+ manifest.chunks.push(entry)
78
+ }
79
+ entry.count++
80
+ processed += chunk.length
81
+ renderProgress('Packing', processed, stat.size)
82
+ }
110
83
 
111
- // Unpack using Helia
112
- async function unpack(manifestPath, outFile) {
113
- await ensureHelia();
84
+ process.stderr.write('\n')
85
+ manifest.manifest_hash = sha256(Buffer.from(JSON.stringify(manifest)))
86
+ fs.writeFileSync(outputFile, JSON.stringify(manifest, null, 2))
87
+ console.log(`Packed -> ${outputFile}`)
88
+ await helia.stop()
89
+ process.exit(0)
90
+ }
114
91
 
115
- const manifest = JSON.parse(fs.readFileSync(manifestPath));
116
- if (!outFile) {
117
- const base = path.basename(manifestPath, path.extname(manifestPath));
118
- const ext = manifest.original_ext || '.bin';
119
- outFile = `${base}${ext}`;
120
- }
92
+ async function unpack(manifestPath, outputFile) {
93
+ const manifest = JSON.parse(fs.readFileSync(manifestPath))
94
+ if (!outputFile) outputFile = `output${manifest.extension || ''}`
121
95
 
122
- const fd = fs.openSync(outFile, 'w');
123
- const total = manifest.chunks.reduce((sum, e) => sum + e.count, 0);
124
- let completed = 0;
125
-
126
- const concurrency = Math.max(1, Math.min(8, os.cpus().length));
127
- for (let i = 0; i < manifest.chunks.length; i += concurrency) {
128
- const batch = manifest.chunks.slice(i, i + concurrency);
129
- await Promise.all(batch.map(async (entry) => {
130
- const blockBytes = await helia.blockstore.get(entry.cid);
131
- for (let j = 0; j < entry.count; j++) {
132
- fs.writeSync(fd, blockBytes.slice(0, entry.length));
133
- completed++;
134
- renderProgress(completed, total, 'Unpacking... ');
135
- }
136
- }));
137
- }
138
- process.stderr.write('\n');
96
+ const storeDir = path.join(os.homedir(), '.cbyte', 'blocks')
97
+ const { helia, blockstore } = await createHeliaNode(storeDir)
139
98
 
140
- fs.ftruncateSync(fd, manifest.original_size);
141
- fs.closeSync(fd);
142
- console.log(`Unpacked -> ${outFile}`);
143
- }
99
+ const fd = fs.openSync(outputFile, 'w')
100
+ let written = 0
144
101
 
145
- // Verify blocks exist in Helia
146
- async function verify(manifestPath) {
147
- await ensureHelia();
148
- const manifest = JSON.parse(fs.readFileSync(manifestPath));
149
- let checked = 0;
150
- for (const { cid } of manifest.chunks) {
151
- try {
152
- await helia.blockstore.get(cid);
153
- checked++;
154
- renderProgress(checked, manifest.chunks.length, 'Verifying');
155
- } catch {
156
- console.error(`Missing block ${cid}`);
157
- process.exit(1);
102
+ for (const entry of manifest.chunks) {
103
+ const data = await blockstore.get(entry.cid)
104
+ for (let i = 0; i < entry.count; i++) {
105
+ fs.writeSync(fd, data.slice(0, entry.length))
106
+ written += entry.length
107
+ renderProgress('Unpacking', written, manifest.original_size)
158
108
  }
159
109
  }
160
- process.stderr.write('\n');
161
- console.log('All blocks verified');
162
- }
163
110
 
164
- // Pin via Helia’s pin API
165
- async function pin(manifestPath) {
166
- await ensureHelia();
167
- const manifest = JSON.parse(fs.readFileSync(manifestPath));
168
- let pinned = 0;
169
- for (const { cid } of manifest.chunks) {
170
- await helia.pin.add(cid);
171
- pinned++;
172
- renderProgress(pinned, manifest.chunks.length, 'Pinning...');
173
- }
174
- process.stderr.write('\n');
175
- console.log('All blocks pinned');
111
+ fs.ftruncateSync(fd, manifest.original_size)
112
+ fs.closeSync(fd)
113
+ process.stderr.write('\n')
114
+ console.log(`Unpacked -> ${outputFile}`)
115
+ await helia.stop()
116
+ process.exit(0)
176
117
  }
177
118
 
178
- // CLI definition
179
- program.version('1.10.0');
180
-
181
- program
182
- .command('pack <file>')
183
- .option('-o, --output <file>', 'Output manifest')
184
- .option('--chunk-size <bytes>', 'Chunk size', '4096')
185
- .option('--pin', 'Pin blocks after pack', false)
186
- .action((file, opts) => pack(file, opts.output, parseInt(opts['chunkSize']), opts.pin));
187
-
188
- program
189
- .command('unpack <manifest>')
190
- .option('-o, --output <file>', 'Output file')
191
- .action((m, opts) => unpack(m, opts.output));
192
-
193
- program
194
- .command('verify <manifest>')
195
- .action((m) => verify(m));
196
-
197
- program
198
- .command('pin <manifest>')
199
- .action((m) => pin(m));
119
+ program.version('1.13')
120
+ program.command('pack <file>')
121
+ .option('-o, --output <file>')
122
+ .option('-c, --chunk-size <bytes>', 'Chunk size', '4096')
123
+ .action((file, opts) => {
124
+ pack(file, opts.output || path.basename(file) + '.cbyte', parseInt(opts.chunkSize))
125
+ })
200
126
 
201
- program.parse(process.argv);
127
+ program.command('unpack <manifest>')
128
+ .option('-o, --output <file>')
129
+ .action((m, opts) => unpack(m, opts.output))
202
130
 
131
+ program.parse(process.argv)
package/package.json CHANGED
@@ -1,33 +1,14 @@
1
1
  {
2
2
  "name": "cbyte",
3
- "type": "module",
4
- "version": "1.11.0",
5
- "description": "Content-addressed deduplicated storage CLI using IPFS with automatic unpack extension retention",
6
- "main": "cbyte.js",
7
- "bin": {
8
- "cbyte": "cbyte.js"
9
- },
10
- "scripts": {
11
- "test": "node test.js"
12
- },
13
- "keywords": [
14
- "ipfs",
15
- "cli",
16
- "deduplication",
17
- "storage",
18
- "content-addressed"
19
- ],
20
- "author": "Coperbyte <coperbyte@coperbyte.com>",
21
- "license": "MIT",
3
+ "version": "1.13.0",
4
+ "description": "Content-addressed deduplicated packer using embedded Helia (offline)",
5
+ "bin": { "cbyte": "./cbyte.js" },
6
+ "type": "commonjs",
7
+ "engines": { "node": ">=20.8.0" },
22
8
  "dependencies": {
23
- "@helia/unixfs": "^7.0.1",
24
9
  "commander": "^11.0.0",
25
- "form-data": "^4.0.0",
26
- "helia": "^6.0.16",
27
- "multiformats": "^13.4.2",
28
- "undici": "^6.0.0"
10
+ "helia": "^4.0.0",
11
+ "blockstore-fs": "^2.0.0"
29
12
  },
30
- "engines": {
31
- "node": ">=20.8.0"
32
- }
13
+ "license": "MIT"
33
14
  }
Binary file