chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import {
buildCarFile,
cborDecode,
cborEncode,
cborEncodeDagCbor,
cidToString,
createBlobCid,
createCid,
createTid,
findBlobRefs,
recordTimeOf,
sniffMimeType,
varint,
} from '@pdsjs/core/repo';
import { describe, expect, test } from 'vitest';
describe('CBOR Encoding', () => {
test('encodes simple map', () => {
const encoded = cborEncode({ hello: 'world', num: 42 });
// Expected: a2 65 68 65 6c 6c 6f 65 77 6f 72 6c 64 63 6e 75 6d 18 2a
const expected = new Uint8Array([
0xa2, 0x65, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x65, 0x77, 0x6f, 0x72, 0x6c,
0x64, 0x63, 0x6e, 0x75, 0x6d, 0x18, 0x2a,
]);
expect(encoded).toEqual(expected);
});
test('encodes null', () => {
const encoded = cborEncode(null);
expect(encoded).toEqual(new Uint8Array([0xf6]));
});
test('encodes byte strings past the engine argument limit', () => {
// A commit event carries a whole CAR file as one byte string. Spreading
// it into push() throws RangeError past about 125k arguments, so a large
// record used to crash createCommit.
const big = new Uint8Array(300_000).fill(7);
const encoded = cborEncode({ blocks: big });
const decoded = /** @type {{blocks: Uint8Array}} */ (cborDecode(encoded));
expect(decoded.blocks.length).toBe(300_000);
expect(decoded.blocks[299_999]).toBe(7);
});
test('encodes booleans', () => {
expect(cborEncode(true)).toEqual(new Uint8Array([0xf5]));
expect(cborEncode(false)).toEqual(new Uint8Array([0xf4]));
});
test('encodes small integers', () => {
expect(cborEncode(0)).toEqual(new Uint8Array([0x00]));
expect(cborEncode(1)).toEqual(new Uint8Array([0x01]));
expect(cborEncode(23)).toEqual(new Uint8Array([0x17]));
});
test('encodes integers >= 24', () => {
expect(cborEncode(24)).toEqual(new Uint8Array([0x18, 0x18]));
expect(cborEncode(255)).toEqual(new Uint8Array([0x18, 0xff]));
});
test('encodes negative integers', () => {
expect(cborEncode(-1)).toEqual(new Uint8Array([0x20]));
expect(cborEncode(-10)).toEqual(new Uint8Array([0x29]));
});
test('encodes strings', () => {
const encoded = cborEncode('hello');
// 0x65 = text string of length 5
expect(encoded).toEqual(
new Uint8Array([0x65, 0x68, 0x65, 0x6c, 0x6c, 0x6f]),
);
});
test('encodes byte strings', () => {
const bytes = new Uint8Array([1, 2, 3]);
const encoded = cborEncode(bytes);
// 0x43 = byte string of length 3
expect(encoded).toEqual(new Uint8Array([0x43, 1, 2, 3]));
});
test('encodes arrays', () => {
const encoded = cborEncode([1, 2, 3]);
// 0x83 = array of length 3
expect(encoded).toEqual(new Uint8Array([0x83, 0x01, 0x02, 0x03]));
});
test('sorts map keys deterministically', () => {
const encoded1 = cborEncode({ z: 1, a: 2 });
const encoded2 = cborEncode({ a: 2, z: 1 });
expect(encoded1).toEqual(encoded2);
// First key should be 'a' (0x61)
expect(encoded1[1]).toBe(0x61);
});
test('encodes large integers >= 2^31 without overflow', () => {
// 2^31 would overflow with bitshift operators (treated as signed 32-bit)
const twoTo31 = 2147483648;
const encoded = cborEncode(twoTo31);
const decoded = cborDecode(encoded);
expect(decoded).toBe(twoTo31);
// 2^32 - 1 (max unsigned 32-bit)
const maxU32 = 4294967295;
const encoded2 = cborEncode(maxU32);
const decoded2 = cborDecode(encoded2);
expect(decoded2).toBe(maxU32);
});
test('encodes 2^31 with correct byte format', () => {
// 2147483648 = 0x80000000
// CBOR: major type 0 (unsigned int), additional info 26 (4-byte follows)
const encoded = cborEncode(2147483648);
expect(encoded[0]).toBe(0x1a); // type 0 | info 26
expect(encoded[1]).toBe(0x80);
expect(encoded[2]).toBe(0x00);
expect(encoded[3]).toBe(0x00);
expect(encoded[4]).toBe(0x00);
});
});
describe('CBOR Decoding', () => {
test('decodes what encode produces (roundtrip)', () => {
const original = { hello: 'world', num: 42 };
const encoded = cborEncode(original);
const decoded = cborDecode(encoded);
expect(decoded).toEqual(original);
});
test('decodes null', () => {
const encoded = cborEncode(null);
const decoded = cborDecode(encoded);
expect(decoded).toBe(null);
});
test('decodes booleans', () => {
expect(cborDecode(cborEncode(true))).toBe(true);
expect(cborDecode(cborEncode(false))).toBe(false);
});
test('decodes integers', () => {
expect(cborDecode(cborEncode(0))).toBe(0);
expect(cborDecode(cborEncode(42))).toBe(42);
expect(cborDecode(cborEncode(255))).toBe(255);
expect(cborDecode(cborEncode(-1))).toBe(-1);
expect(cborDecode(cborEncode(-10))).toBe(-10);
});
test('decodes strings', () => {
expect(cborDecode(cborEncode('hello'))).toBe('hello');
expect(cborDecode(cborEncode(''))).toBe('');
});
test('decodes arrays', () => {
expect(cborDecode(cborEncode([1, 2, 3]))).toEqual([1, 2, 3]);
expect(cborDecode(cborEncode([]))).toEqual([]);
});
test('decodes nested structures', () => {
const original = { arr: [1, { nested: true }], str: 'test' };
const decoded = cborDecode(cborEncode(original));
expect(decoded).toEqual(original);
});
});
describe('CID Generation', () => {
test('createCid uses dag-cbor codec', async () => {
const data = cborEncode({ test: 'data' });
const cid = await createCid(data);
expect(cid.length).toBe(36); // 2 prefix + 2 multihash header + 32 hash
expect(cid[0]).toBe(0x01); // CIDv1
expect(cid[1]).toBe(0x71); // dag-cbor
expect(cid[2]).toBe(0x12); // sha-256
expect(cid[3]).toBe(0x20); // 32 bytes
});
test('createBlobCid uses raw codec', async () => {
const data = new Uint8Array([0xff, 0xd8, 0xff, 0xe0]); // JPEG magic bytes
const cid = await createBlobCid(data);
expect(cid.length).toBe(36);
expect(cid[0]).toBe(0x01); // CIDv1
expect(cid[1]).toBe(0x55); // raw codec
expect(cid[2]).toBe(0x12); // sha-256
expect(cid[3]).toBe(0x20); // 32 bytes
});
test('same bytes produce different CIDs with different codecs', async () => {
const data = new Uint8Array([1, 2, 3, 4]);
const dagCborCid = cidToString(await createCid(data));
const rawCid = cidToString(await createBlobCid(data));
expect(dagCborCid).not.toBe(rawCid);
});
test('cidToString returns base32lower with b prefix', async () => {
const data = cborEncode({ test: 'data' });
const cid = await createCid(data);
const cidStr = cidToString(cid);
expect(cidStr[0]).toBe('b');
expect(cidStr).toMatch(/^b[a-z2-7]+$/);
});
test('same input produces same CID', async () => {
const data1 = cborEncode({ test: 'data' });
const data2 = cborEncode({ test: 'data' });
const cid1 = cidToString(await createCid(data1));
const cid2 = cidToString(await createCid(data2));
expect(cid1).toBe(cid2);
});
test('different input produces different CID', async () => {
const cid1 = cidToString(await createCid(cborEncode({ a: 1 })));
const cid2 = cidToString(await createCid(cborEncode({ a: 2 })));
expect(cid1).not.toBe(cid2);
});
});
describe('TID Generation', () => {
test('creates 13-character TIDs', () => {
const tid = createTid();
expect(tid.length).toBe(13);
});
test('uses valid base32-sort characters', () => {
const tid = createTid();
expect(tid).toMatch(/^[234567abcdefghijklmnopqrstuvwxyz]+$/);
});
test('generates monotonically increasing TIDs', () => {
const tid1 = createTid();
const tid2 = createTid();
const tid3 = createTid();
expect(tid1 < tid2).toBe(true);
expect(tid2 < tid3).toBe(true);
});
test('generates unique TIDs', () => {
const tids = new Set();
for (let i = 0; i < 100; i++) {
tids.add(createTid());
}
expect(tids.size).toBe(100);
});
});
describe('CAR File Builder', () => {
test('varint encodes small numbers', () => {
expect(varint(0)).toEqual(new Uint8Array([0]));
expect(varint(1)).toEqual(new Uint8Array([1]));
expect(varint(127)).toEqual(new Uint8Array([127]));
});
test('varint encodes multi-byte numbers', () => {
// 128 = 0x80 -> [0x80 | 0x00, 0x01] = [0x80, 0x01]
expect(varint(128)).toEqual(new Uint8Array([0x80, 0x01]));
// 300 = 0x12c -> [0xac, 0x02]
expect(varint(300)).toEqual(new Uint8Array([0xac, 0x02]));
});
test('buildCarFile produces valid structure', async () => {
const data = cborEncode({ test: 'data' });
const cid = await createCid(data);
const cidStr = cidToString(cid);
const car = buildCarFile(cidStr, [{ cid: cidStr, data }]);
expect(car instanceof Uint8Array).toBe(true);
expect(car.length > 0).toBe(true);
// First byte should be varint of header length
expect(car[0] > 0).toBe(true);
});
});
describe('MIME Type Sniffing', () => {
test('detects JPEG', () => {
const bytes = new Uint8Array([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10]);
expect(sniffMimeType(bytes)).toBe('image/jpeg');
});
test('detects PNG', () => {
const bytes = new Uint8Array([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
]);
expect(sniffMimeType(bytes)).toBe('image/png');
});
test('detects GIF', () => {
const bytes = new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61]);
expect(sniffMimeType(bytes)).toBe('image/gif');
});
test('detects WebP', () => {
const bytes = new Uint8Array([
0x52,
0x49,
0x46,
0x46, // RIFF
0x00,
0x00,
0x00,
0x00, // size (ignored)
0x57,
0x45,
0x42,
0x50, // WEBP
]);
expect(sniffMimeType(bytes)).toBe('image/webp');
});
test('detects MP4', () => {
const bytes = new Uint8Array([
0x00,
0x00,
0x00,
0x18, // size
0x66,
0x74,
0x79,
0x70, // ftyp
0x69,
0x73,
0x6f,
0x6d, // isom brand
]);
expect(sniffMimeType(bytes)).toBe('video/mp4');
});
test('detects AVIF', () => {
const bytes = new Uint8Array([
0x00,
0x00,
0x00,
0x1c, // size
0x66,
0x74,
0x79,
0x70, // ftyp
0x61,
0x76,
0x69,
0x66, // avif brand
]);
expect(sniffMimeType(bytes)).toBe('image/avif');
});
test('detects HEIC', () => {
const bytes = new Uint8Array([
0x00,
0x00,
0x00,
0x18, // size
0x66,
0x74,
0x79,
0x70, // ftyp
0x68,
0x65,
0x69,
0x63, // heic brand
]);
expect(sniffMimeType(bytes)).toBe('image/heic');
});
test('returns null for unknown', () => {
const bytes = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
expect(sniffMimeType(bytes)).toBe(null);
});
});
describe('Blob Ref Detection', () => {
test('finds blob ref in simple object', () => {
const record = {
$type: 'app.bsky.feed.post',
text: 'Hello',
embed: {
$type: 'app.bsky.embed.images',
images: [
{
image: {
$type: 'blob',
ref: { $link: 'bafkreiabc123' },
mimeType: 'image/jpeg',
size: 1234,
},
alt: 'test image',
},
],
},
};
const refs = findBlobRefs(record);
expect(refs).toEqual(['bafkreiabc123']);
});
test('finds a blob ref decoded from a CAR (raw CID bytes)', async () => {
// An imported record carries its blob ref as the raw CID bytes cborDecode
// hands back for a tag-42 link, not `{$link}`. Both must be detected, or an
// imported account's blobs never transfer.
const cidBytes = await createBlobCid(new Uint8Array([1, 2, 3]));
const record = {
$type: 'app.bsky.actor.profile',
avatar: {
$type: 'blob',
ref: cidBytes,
mimeType: 'image/jpeg',
size: 3,
},
};
expect(findBlobRefs(record)).toEqual([cidToString(cidBytes)]);
});
test('finds multiple blob refs', () => {
const record = {
images: [
{
image: {
$type: 'blob',
ref: { $link: 'cid1' },
mimeType: 'image/png',
size: 100,
},
},
{
image: {
$type: 'blob',
ref: { $link: 'cid2' },
mimeType: 'image/png',
size: 200,
},
},
],
};
const refs = findBlobRefs(record);
expect(refs).toEqual(['cid1', 'cid2']);
});
test('returns empty array when no blobs', () => {
const record = { text: 'Hello world', count: 42 };
const refs = findBlobRefs(record);
expect(refs).toEqual([]);
});
test('handles null and primitives', () => {
expect(findBlobRefs(null)).toEqual([]);
expect(findBlobRefs('string')).toEqual([]);
expect(findBlobRefs(42)).toEqual([]);
});
test('finds legacy blob refs (pre-$type format)', () => {
const record = {
embed: {
image: { cid: 'bafkreilegacyblob', mimeType: 'image/jpeg' },
},
};
expect(findBlobRefs(record)).toEqual(['bafkreilegacyblob']);
});
test('does not treat objects with extra keys as legacy blob refs', () => {
const record = {
subject: {
cid: 'bafyreirecordcid',
mimeType: 'image/jpeg',
uri: 'at://x/y/z',
},
strongRef: { cid: 'bafyreirecordcid', uri: 'at://x/y/z' },
};
expect(findBlobRefs(record)).toEqual([]);
});
});
describe('Record Time Detection', () => {
test('reads the conventional createdAt', () => {
expect(recordTimeOf({ createdAt: '2026-05-07T14:32:34.621Z' })).toBe(
Date.parse('2026-05-07T14:32:34.621Z'),
);
});
test('accepts timestamp fields under other names', () => {
expect(recordTimeOf({ playedTime: '2025-03-01T10:00:00Z' })).toBe(
Date.parse('2025-03-01T10:00:00Z'),
);
expect(recordTimeOf({ publishDate: '2024-06-05T00:00:00Z' })).toBe(
Date.parse('2024-06-05T00:00:00Z'),
);
});
test('the earliest moment wins, so updates never mask creation', () => {
expect(
recordTimeOf({
createdAt: '2026-05-07T14:32:34Z',
updatedAt: '2026-05-07T18:44:29Z',
}),
).toBe(Date.parse('2026-05-07T14:32:34Z'));
});
test('ignores moments outside the plausible window', () => {
// A birthday must not date an avatar; a scheduled future event must not
// pin its poster to that day.
expect(recordTimeOf({ birthDate: '1985-03-12T00:00:00Z' })).toBe(null);
expect(recordTimeOf({ startsAt: '2999-01-01T00:00:00Z' })).toBe(null);
expect(
recordTimeOf({
birthDate: '1985-03-12T00:00:00Z',
createdAt: '2026-05-07T14:32:34Z',
}),
).toBe(Date.parse('2026-05-07T14:32:34Z'));
});
test('ignores non-time fields and malformed values', () => {
expect(recordTimeOf({ format: 'flat', text: 'hello' })).toBe(null);
expect(recordTimeOf({ createdAt: 'yesterday' })).toBe(null);
expect(recordTimeOf({ createdAt: 1746628354621 })).toBe(null);
expect(recordTimeOf(null)).toBe(null);
expect(recordTimeOf(['2026-05-07T14:32:34Z'])).toBe(null);
});
});
describe('CBOR integers past 32 bits', () => {
// knotless starts a ref's generation at the time in milliseconds.
const ref = {
$type: 'dev.knotless.ref',
name: 'refs/heads/main',
generation: 1793736300000,
};
test('encode with an 8-byte head and read back', () => {
for (const encode of [cborEncode, cborEncodeDagCbor]) {
expect(encode(1793736300000)).toEqual(
new Uint8Array([0x1b, 0x00, 0x00, 0x01, 0xa1, 0xa3, 0x03, 0xd5, 0xe0]),
);
expect(encode(2 ** 32)).toEqual(
new Uint8Array([0x1b, 0, 0, 0, 1, 0, 0, 0, 0]),
);
expect(cborDecode(encode(-(2 ** 40)))).toBe(-(2 ** 40));
expect(cborDecode(encode(Number.MAX_SAFE_INTEGER))).toBe(
Number.MAX_SAFE_INTEGER,
);
}
expect(cborDecode(cborEncodeDagCbor(ref))).toEqual(ref);
});
test('a value an encoder cannot write is refused, not left out', () => {
for (const encode of [cborEncode, cborEncodeDagCbor]) {
for (const bad of [1.5, Number.NaN, 2 ** 53, 10n, [undefined]]) {
expect(() => encode({ a: bad })).toThrow(/CBOR cannot encode/);
}
}
});
test('a record stored cut short by an earlier encoder still reads', () => {
// Before 8-byte heads, the encoders wrote nothing for such an integer.
// A read gives 0 for the missing value. Refusing the read made every
// listing of a collection holding one fail.
const whole = cborEncodeDagCbor({ ...ref, generation: 0 });
const stored = whole.subarray(0, whole.length - 1);
expect(cborDecode(stored)).toEqual({ ...ref, generation: 0 });
});
});