this repo has no description

feat: add CAR file builder

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Changed files
+105 -2
src
test
+69 -1
src/pds.js
··· 376 } 377 } 378 379 export class PersonalDataServer { 380 constructor(state, env) { 381 this.state = state ··· 635 export { 636 cborEncode, cborDecode, createCid, cidToString, base32Encode, createTid, 637 generateKeyPair, importPrivateKey, sign, bytesToHex, hexToBytes, 638 - getKeyDepth 639 }
··· 376 } 377 } 378 379 + // === CAR FILE BUILDER === 380 + 381 + function varint(n) { 382 + const bytes = [] 383 + while (n >= 0x80) { 384 + bytes.push((n & 0x7f) | 0x80) 385 + n >>>= 7 386 + } 387 + bytes.push(n) 388 + return new Uint8Array(bytes) 389 + } 390 + 391 + function cidToBytes(cidStr) { 392 + // Decode base32lower CID string to bytes 393 + if (!cidStr.startsWith('b')) throw new Error('expected base32lower CID') 394 + return base32Decode(cidStr.slice(1)) 395 + } 396 + 397 + function base32Decode(str) { 398 + const alphabet = 'abcdefghijklmnopqrstuvwxyz234567' 399 + let bits = 0 400 + let value = 0 401 + const output = [] 402 + 403 + for (const char of str) { 404 + const idx = alphabet.indexOf(char) 405 + if (idx === -1) continue 406 + value = (value << 5) | idx 407 + bits += 5 408 + if (bits >= 8) { 409 + bits -= 8 410 + output.push((value >> bits) & 0xff) 411 + } 412 + } 413 + 414 + return new Uint8Array(output) 415 + } 416 + 417 + function buildCarFile(rootCid, blocks) { 418 + const parts = [] 419 + 420 + // Header: { version: 1, roots: [rootCid] } 421 + const rootCidBytes = cidToBytes(rootCid) 422 + const header = cborEncode({ version: 1, roots: [rootCidBytes] }) 423 + parts.push(varint(header.length)) 424 + parts.push(header) 425 + 426 + // Blocks: varint(len) + cid + data 427 + for (const block of blocks) { 428 + const cidBytes = cidToBytes(block.cid) 429 + const blockLen = cidBytes.length + block.data.length 430 + parts.push(varint(blockLen)) 431 + parts.push(cidBytes) 432 + parts.push(block.data) 433 + } 434 + 435 + // Concatenate all parts 436 + const totalLen = parts.reduce((sum, p) => sum + p.length, 0) 437 + const car = new Uint8Array(totalLen) 438 + let offset = 0 439 + for (const part of parts) { 440 + car.set(part, offset) 441 + offset += part.length 442 + } 443 + 444 + return car 445 + } 446 + 447 export class PersonalDataServer { 448 constructor(state, env) { 449 this.state = state ··· 703 export { 704 cborEncode, cborDecode, createCid, cidToString, base32Encode, createTid, 705 generateKeyPair, importPrivateKey, sign, bytesToHex, hexToBytes, 706 + getKeyDepth, varint, base32Decode, buildCarFile 707 }
+36 -1
test/pds.test.js
··· 3 import { 4 cborEncode, cborDecode, createCid, cidToString, base32Encode, createTid, 5 generateKeyPair, importPrivateKey, sign, bytesToHex, hexToBytes, 6 - getKeyDepth 7 } from '../src/pds.js' 8 9 describe('CBOR Encoding', () => { ··· 265 assert.deepStrictEqual(decoded, original) 266 }) 267 })
··· 3 import { 4 cborEncode, cborDecode, createCid, cidToString, base32Encode, createTid, 5 generateKeyPair, importPrivateKey, sign, bytesToHex, hexToBytes, 6 + getKeyDepth, varint, base32Decode, buildCarFile 7 } from '../src/pds.js' 8 9 describe('CBOR Encoding', () => { ··· 265 assert.deepStrictEqual(decoded, original) 266 }) 267 }) 268 + 269 + describe('CAR File Builder', () => { 270 + test('varint encodes small numbers', () => { 271 + assert.deepStrictEqual(varint(0), new Uint8Array([0])) 272 + assert.deepStrictEqual(varint(1), new Uint8Array([1])) 273 + assert.deepStrictEqual(varint(127), new Uint8Array([127])) 274 + }) 275 + 276 + test('varint encodes multi-byte numbers', () => { 277 + // 128 = 0x80 -> [0x80 | 0x00, 0x01] = [0x80, 0x01] 278 + assert.deepStrictEqual(varint(128), new Uint8Array([0x80, 0x01])) 279 + // 300 = 0x12c -> [0xac, 0x02] 280 + assert.deepStrictEqual(varint(300), new Uint8Array([0xac, 0x02])) 281 + }) 282 + 283 + test('base32 encode/decode roundtrip', () => { 284 + const original = new Uint8Array([0x01, 0x71, 0x12, 0x20, 0xab, 0xcd]) 285 + const encoded = base32Encode(original) 286 + const decoded = base32Decode(encoded) 287 + assert.deepStrictEqual(decoded, original) 288 + }) 289 + 290 + test('buildCarFile produces valid structure', async () => { 291 + const data = cborEncode({ test: 'data' }) 292 + const cid = await createCid(data) 293 + const cidStr = cidToString(cid) 294 + 295 + const car = buildCarFile(cidStr, [{ cid: cidStr, data }]) 296 + 297 + assert.ok(car instanceof Uint8Array) 298 + assert.ok(car.length > 0) 299 + // First byte should be varint of header length 300 + assert.ok(car[0] > 0) 301 + }) 302 + })