chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { describe, expect, test, vi } from 'vitest';
import { LexiconResolver } from '../src/resolver.js';
import { knownRecords } from '../src/schemas/known-records.js';
/** @type {import('@bigmoves/lexicon').Lexicon} */
const postSchema = {
lexicon: 1,
id: 'app.bsky.feed.post',
defs: {
main: {
type: 'record',
key: 'tid',
record: {
type: 'object',
required: ['text', 'createdAt'],
properties: {
text: { type: 'string', maxLength: 3000 },
createdAt: { type: 'string', format: 'datetime' },
},
},
},
},
};
describe('LexiconResolver', () => {
describe('with static schemas', () => {
test('validates record against pre-loaded schema', async () => {
const resolver = new LexiconResolver({
schemas: [postSchema],
});
const status = await resolver.assertValid('app.bsky.feed.post', {
$type: 'app.bsky.feed.post',
text: 'Hello world',
createdAt: new Date().toISOString(),
});
expect(status).toBe('valid');
});
test('throws on invalid record', async () => {
const resolver = new LexiconResolver({
schemas: [postSchema],
});
await expect(
resolver.assertValid('app.bsky.feed.post', {
$type: 'app.bsky.feed.post',
// missing required fields
}),
).rejects.toThrow();
});
test('returns unknown for unloaded schema', async () => {
const resolver = new LexiconResolver({
schemas: [postSchema],
});
const status = await resolver.assertValid('com.example.unknown', {
$type: 'com.example.unknown',
});
expect(status).toBe('unknown');
});
});
describe('with an unresolvable ref', () => {
/** @type {import('@bigmoves/lexicon').Lexicon} */
const photoSchema = {
lexicon: 1,
id: 'com.example.photo',
defs: {
main: {
type: 'record',
key: 'tid',
record: {
type: 'object',
required: ['alt'],
properties: {
alt: { type: 'string' },
aspectRatio: { type: 'ref', ref: 'com.example.defs#aspectRatio' },
},
},
},
},
};
/** A resolver holding the record schema but unable to fetch its ref. */
const offlineResolver = () =>
new LexiconResolver({
schemas: [photoSchema],
fetch: vi.fn().mockRejectedValue(new Error('Network error')),
});
test('returns unknown instead of rejecting the record', async () => {
const status = await offlineResolver().assertValid('com.example.photo', {
$type: 'com.example.photo',
alt: 'a photo',
aspectRatio: { width: 3000, height: 2000 },
});
expect(status).toBe('unknown');
});
test('still rejects a record broken independently of the ref', async () => {
await expect(
offlineResolver().assertValid('com.example.photo', {
$type: 'com.example.photo',
// missing required 'alt', and no ref field to excuse it
}),
).rejects.toThrow(/alt/);
});
test('reports valid when the record omits the unresolvable field', async () => {
const status = await offlineResolver().assertValid('com.example.photo', {
$type: 'com.example.photo',
alt: 'a photo',
});
expect(status).toBe('valid');
});
});
describe('with a network-resolved schema', () => {
/** @type {import('@bigmoves/lexicon').Lexicon} */
const gallerySchema = {
lexicon: 1,
id: 'com.example.gallery',
defs: {
main: {
type: 'record',
key: 'tid',
record: {
type: 'object',
required: ['title', 'alt'],
properties: {
title: { type: 'string' },
alt: { type: 'string' },
},
},
},
},
};
/** Mocks the DNS -> DID -> getRecord chain so the schema arrives fetched. */
const fetchedResolver = () => {
const fetch = vi.fn().mockImplementation((url) => {
const u = String(url);
if (u.includes('dns-query')) {
return Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
Answer: [{ type: 16, data: '"did=did:plc:testdid"' }],
}),
});
}
if (u.includes('plc.directory')) {
return Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
service: [
{
id: '#atproto_pds',
type: 'AtprotoPersonalDataServer',
serviceEndpoint: 'https://pds.example.com',
},
],
}),
});
}
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ value: gallerySchema }),
});
});
return new LexiconResolver({ fetch });
};
// The record omits 'alt', which the fetched lexicon requires — the shape of
// a published lexicon lagging the client that writes the records.
const drifted = { $type: 'com.example.gallery', title: 'hi' };
test('reports unknown rather than rejecting the write', async () => {
expect(
await fetchedResolver().assertValid('com.example.gallery', drifted),
).toBe('unknown');
});
test('reports unknown without resolving, even when the record conforms', async () => {
const resolver = fetchedResolver();
expect(
await resolver.assertValid('com.example.gallery', {
...drifted,
alt: 'a photo',
}),
).toBe('unknown');
// The reference PDS answers 'unknown' for a lexicon it does not hold.
// Resolving one cannot reject the write either way, so the chain never
// runs on the write path.
expect(resolver.fetch).not.toHaveBeenCalled();
});
test('enforces it when the caller asks for strict validation', async () => {
await expect(
fetchedResolver().assertValid('com.example.gallery', drifted, {
strict: true,
}),
).rejects.toThrow(/alt/);
});
test('enforces a collection on the known-records list', async () => {
// Same drifted-record shape, but for a collection the reference PDS
// validates strictly — so it must be rejected, not waved through.
const resolver = fetchedResolver();
resolver.enforced.add('com.example.gallery');
await expect(
resolver.assertValid('com.example.gallery', drifted),
).rejects.toThrow(/alt/);
});
test('honours an operator-supplied enforce list', async () => {
const bare = new LexiconResolver({ enforce: [] });
expect(bare.enforced.has('app.bsky.feed.post')).toBe(false);
// Pre-loaded schemas are enforced regardless of the list.
const withStatic = new LexiconResolver({
enforce: [],
schemas: [postSchema],
});
expect(withStatic.enforced.has('app.bsky.feed.post')).toBe(true);
});
test('strict validation fails when nothing resolves', async () => {
const resolver = new LexiconResolver({
fetch: vi.fn().mockRejectedValue(new Error('Network error')),
});
await expect(
resolver.assertValid(
'com.example.unknown',
{ $type: 'com.example.unknown' },
{ strict: true },
),
).rejects.toThrow(/no published lexicon/);
});
});
describe('ensureSchemaWithRefs', () => {
test('collects refs it could not resolve', async () => {
/** @type {import('@bigmoves/lexicon').Lexicon} */
const schema = {
lexicon: 1,
id: 'com.example.photo',
defs: {
main: {
type: 'record',
key: 'tid',
record: {
type: 'object',
properties: {
aspectRatio: {
type: 'ref',
ref: 'com.example.defs#aspectRatio',
},
},
},
},
},
};
const resolver = new LexiconResolver({
schemas: [schema],
fetch: vi.fn().mockRejectedValue(new Error('Network error')),
});
/** @type {Set<string>} */
const unresolved = new Set();
expect(
await resolver.ensureSchemaWithRefs('com.example.photo', unresolved),
).toBe(true);
expect([...unresolved]).toEqual(['com.example.defs']);
});
test('terminates on a ref cycle', async () => {
/** @type {import('@bigmoves/lexicon').Lexicon} */
const a = {
lexicon: 1,
id: 'com.example.a',
defs: {
main: {
type: 'object',
properties: { b: { type: 'ref', ref: 'com.example.b#main' } },
},
},
};
/** @type {import('@bigmoves/lexicon').Lexicon} */
const b = {
lexicon: 1,
id: 'com.example.b',
defs: {
main: {
type: 'object',
properties: { a: { type: 'ref', ref: 'com.example.a#main' } },
},
},
};
const resolver = new LexiconResolver({ schemas: [a, b] });
/** @type {Set<string>} */
const unresolved = new Set();
expect(
await resolver.ensureSchemaWithRefs('com.example.a', unresolved),
).toBe(true);
expect([...unresolved]).toEqual([]);
});
});
describe('ensureSchema', () => {
test('returns true for pre-loaded schema', async () => {
const resolver = new LexiconResolver({
schemas: [postSchema],
});
const result = await resolver.ensureSchema('app.bsky.feed.post');
expect(result).toBe(true);
});
test('returns false for unknown schema when resolution fails', async () => {
const mockFetch = vi.fn().mockRejectedValue(new Error('Network error'));
const resolver = new LexiconResolver({ fetch: mockFetch });
const result = await resolver.ensureSchema('com.example.unknown');
expect(result).toBe(false);
});
test('concurrent callers share one resolution and all get its answer', async () => {
/** @type {(value: unknown) => void} */
let releaseDns = () => {};
const mockFetch = vi
.fn()
// DNS lookup, held open until released so callers pile up mid-flight
.mockImplementationOnce(
() =>
new Promise((resolve) => {
releaseDns = resolve;
}),
)
// DID resolution
.mockResolvedValueOnce({
ok: true,
json: () =>
Promise.resolve({
service: [
{
id: '#atproto_pds',
type: 'AtprotoPersonalDataServer',
serviceEndpoint: 'https://pds.example.com',
},
],
}),
})
// Lexicon fetch
.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ value: postSchema }),
});
// A collection outside the preloaded set, so the chain actually runs.
const resolver = new LexiconResolver({ fetch: mockFetch });
const first = resolver.ensureSchema('com.example.thing');
const second = resolver.ensureSchema('com.example.thing');
const third = resolver.ensureSchema('com.example.thing');
releaseDns({
ok: true,
json: () =>
Promise.resolve({
Answer: [{ type: 16, data: '"did=did:plc:testdid"' }],
}),
});
expect(await Promise.all([first, second, third])).toEqual([
true,
true,
true,
]);
// One resolution chain (DNS, DID, record), not one per caller.
expect(mockFetch).toHaveBeenCalledTimes(3);
});
});
describe('isPublished', () => {
test('returns true for a pre-loaded schema', async () => {
const resolver = new LexiconResolver({ schemas: [postSchema] });
expect(await resolver.isPublished('app.bsky.feed.post')).toBe(true);
});
test('returns false for an unpublished NSID', async () => {
const mockFetch = vi.fn().mockRejectedValue(new Error('Network error'));
const resolver = new LexiconResolver({ fetch: mockFetch });
expect(await resolver.isPublished('com.example.unknown')).toBe(false);
});
test('caches the negative so a repeat check does not re-hit DNS', async () => {
const mockFetch = vi.fn().mockRejectedValue(new Error('Network error'));
const resolver = new LexiconResolver({ fetch: mockFetch });
expect(await resolver.isPublished('com.example.unknown')).toBe(false);
const callsAfterFirst = mockFetch.mock.calls.length;
expect(await resolver.isPublished('com.example.unknown')).toBe(false);
expect(mockFetch.mock.calls.length).toBe(callsAfterFirst);
});
test('re-resolves a negative once negativeTtl elapses', async () => {
const mockFetch = vi.fn().mockRejectedValue(new Error('Network error'));
const resolver = new LexiconResolver({
fetch: mockFetch,
negativeTtl: 0,
});
expect(await resolver.isPublished('com.example.unknown')).toBe(false);
const callsAfterFirst = mockFetch.mock.calls.length;
expect(await resolver.isPublished('com.example.unknown')).toBe(false);
expect(mockFetch.mock.calls.length).toBeGreaterThan(callsAfterFirst);
});
});
describe('getLexicon', () => {
test('returns the resolved document for a pre-loaded schema', async () => {
const resolver = new LexiconResolver({ schemas: [postSchema] });
expect(await resolver.getLexicon('app.bsky.feed.post')).toEqual(
postSchema,
);
});
test('returns null for an unresolvable NSID', async () => {
const mockFetch = vi.fn().mockRejectedValue(new Error('Network error'));
const resolver = new LexiconResolver({ fetch: mockFetch });
expect(await resolver.getLexicon('com.example.unknown')).toBeNull();
});
});
describe('fetchLexicon', () => {
test('resolves lexicon via DNS -> DID -> PDS chain', async () => {
const mockFetch = vi
.fn()
// DNS lookup
.mockResolvedValueOnce({
ok: true,
json: () =>
Promise.resolve({
Answer: [{ type: 16, data: '"did=did:plc:testdid"' }],
}),
})
// DID resolution
.mockResolvedValueOnce({
ok: true,
json: () =>
Promise.resolve({
service: [
{
id: '#atproto_pds',
type: 'AtprotoPersonalDataServer',
serviceEndpoint: 'https://pds.example.com',
},
],
}),
})
// Lexicon fetch
.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ value: postSchema }),
});
const resolver = new LexiconResolver({ fetch: mockFetch });
const lexicon = await resolver.fetchLexicon('app.bsky.feed.post');
expect(lexicon).toEqual(postSchema);
expect(mockFetch).toHaveBeenCalledTimes(3);
});
});
describe('preloaded lexicons', () => {
test('validates an enforced record type without any network call', async () => {
const fetch = vi.fn().mockRejectedValue(new Error('Network error'));
const resolver = new LexiconResolver({ fetch });
const status = await resolver.assertValid('app.bsky.feed.post', {
$type: 'app.bsky.feed.post',
text: 'Hello world',
createdAt: new Date().toISOString(),
facets: [
{
index: { byteStart: 0, byteEnd: 5 },
features: [
{
$type: 'app.bsky.richtext.facet#link',
uri: 'https://a.example',
},
],
},
],
});
expect(status).toBe('valid');
expect(fetch).not.toHaveBeenCalled();
});
test('rejects a broken record from the preloaded lexicon', async () => {
const fetch = vi.fn().mockRejectedValue(new Error('Network error'));
const resolver = new LexiconResolver({ fetch });
await expect(
resolver.assertValid('app.bsky.feed.post', {
$type: 'app.bsky.feed.post',
text: 'no createdAt',
}),
).rejects.toThrow(/createdAt/);
expect(fetch).not.toHaveBeenCalled();
});
test('every enforced record type is preloaded', async () => {
const fetch = vi.fn().mockRejectedValue(new Error('Network error'));
const resolver = new LexiconResolver({ fetch });
for (const nsid of knownRecords) {
expect(resolver.schemas.has(nsid)).toBe(true);
}
expect(fetch).not.toHaveBeenCalled();
});
test('a caller-supplied schema wins over the preloaded one', async () => {
const stub = { ...postSchema, id: 'app.bsky.feed.post' };
const resolver = new LexiconResolver({ schemas: [stub] });
expect(await resolver.getLexicon('app.bsky.feed.post')).toEqual(stub);
});
});
describe('resolution caching', () => {
/** Answers the DNS -> DID -> getRecord chain, counting each leg. */
const chainFetch = () =>
vi.fn().mockImplementation((url) => {
const u = String(url);
if (u.includes('dns-query')) {
return Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
Answer: [{ type: 16, data: '"did=did:plc:testdid"' }],
}),
});
}
if (u.includes('plc.directory')) {
return Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
service: [
{
id: '#atproto_pds',
type: 'AtprotoPersonalDataServer',
serviceEndpoint: 'https://pds.example.com',
},
],
}),
});
}
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
});
const legs = (/** @type {ReturnType<typeof chainFetch>} */ fetch) => {
const urls = fetch.mock.calls.map((/** @type {unknown[]} */ c) =>
String(c[0]),
);
return {
dns: urls.filter((u) => u.includes('dns-query')).length,
plc: urls.filter((u) => u.includes('plc.directory')).length,
};
};
test('two NSIDs under one authority share the DNS and DID lookups', async () => {
const fetch = chainFetch();
const resolver = new LexiconResolver({ fetch });
await resolver.ensureSchema('com.example.one');
await resolver.ensureSchema('com.example.two');
// com.example.one and com.example.two both live at example.com.
expect(legs(fetch)).toEqual({ dns: 1, plc: 1 });
});
test('concurrent NSIDs under one authority share them too', async () => {
const fetch = chainFetch();
const resolver = new LexiconResolver({ fetch });
await Promise.all([
resolver.ensureSchema('com.example.one'),
resolver.ensureSchema('com.example.two'),
resolver.ensureSchema('com.example.three'),
]);
expect(legs(fetch)).toEqual({ dns: 1, plc: 1 });
});
test('an unresolvable NSID does not repeat the chain', async () => {
const fetch = vi.fn().mockRejectedValue(new Error('Network error'));
const resolver = new LexiconResolver({ fetch });
expect(await resolver.ensureSchema('com.example.unknown')).toBe(false);
const afterFirst = fetch.mock.calls.length;
expect(afterFirst).toBeGreaterThan(0);
expect(await resolver.ensureSchema('com.example.unknown')).toBe(false);
expect(fetch.mock.calls.length).toBe(afterFirst);
});
test('a preloaded lexicon whose refresh fails does not retry every call', async () => {
const fetch = vi.fn().mockRejectedValue(new Error('Network error'));
// ttl 0 ages the preloaded schemas out immediately.
const resolver = new LexiconResolver({ fetch, ttl: 0 });
expect(await resolver.ensureSchema('app.bsky.feed.post')).toBe(true);
const afterFirst = fetch.mock.calls.length;
expect(afterFirst).toBeGreaterThan(0);
expect(await resolver.ensureSchema('app.bsky.feed.post')).toBe(true);
expect(fetch.mock.calls.length).toBe(afterFirst);
});
});
});