chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { describe, expect, it } from 'vitest';
import { runSpaceStorageConformance } from '../../../test/helpers/space-storage-conformance.js';
import { createSpaceStorage } from '../src/space.js';
runSpaceStorageConformance('storage-sqlite', async () => {
const Database = (await import('better-sqlite3')).default;
return createSpaceStorage(new Database(':memory:'));
});
describe('opening a database from before space revisions', () => {
it('sequences each recorded writer at its repo rev', async () => {
const Database = (await import('better-sqlite3')).default;
const db = new Database(':memory:');
db.exec(`CREATE TABLE space_writer (
space TEXT NOT NULL,
did TEXT NOT NULL,
rev TEXT NOT NULL,
hash BLOB NOT NULL,
PRIMARY KEY (space, did)
)`);
db.prepare(
'INSERT INTO space_writer (space, did, rev, hash) VALUES (?, ?, ?, ?)',
).run(
'at://did:plc:a/space/x.y.z/s',
'did:plc:w',
'3lkaaaaaaaa22',
Buffer.alloc(32),
);
const store = createSpaceStorage(db);
const { writers, cursor } = await store.listSpaceWriters(
'at://did:plc:a/space/x.y.z/s',
null,
10,
);
expect(writers).toEqual([
expect.objectContaining({
did: 'did:plc:w',
repoRev: '3lkaaaaaaaa22',
spaceRev: '3lkaaaaaaaa22',
}),
]);
expect(cursor).toBe('3lkaaaaaaaa22');
expect(await store.getSpaceRevHead('at://did:plc:a/space/x.y.z/s')).toBe(
'3lkaaaaaaaa22',
);
});
});
describe('opening a database from before space policies', () => {
const A = 'at://did:plc:a/space/x.y.z/members';
const B = 'at://did:plc:a/space/x.y.z/public';
const C = 'at://did:plc:a/space/x.y.z/managed';
const D = 'at://did:plc:b/space/x.y.z/joined';
async function openLegacy() {
const Database = (await import('better-sqlite3')).default;
const db = new Database(':memory:');
db.exec(`CREATE TABLE space (
uri TEXT PRIMARY KEY,
space_did TEXT NOT NULL,
space_type TEXT NOT NULL,
is_owner INTEGER NOT NULL,
read_policy TEXT NOT NULL DEFAULT 'member-list',
read_managing_app TEXT,
write_policy TEXT NOT NULL DEFAULT 'member-list',
write_managing_app TEXT,
app_access_type TEXT NOT NULL DEFAULT 'open',
app_allowed TEXT NOT NULL DEFAULT '[]',
created_at TEXT NOT NULL,
deleted_at TEXT
);
CREATE TABLE space_member (
space TEXT NOT NULL,
did TEXT NOT NULL,
can_read INTEGER NOT NULL DEFAULT 1,
can_write INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (space, did)
);`);
const insert = db.prepare(
`INSERT INTO space (uri, space_did, space_type, is_owner, read_policy,
read_managing_app, write_policy, write_managing_app, app_access_type,
app_allowed, created_at) VALUES (?, ?, 'x.y.z', ?, ?, ?, ?, ?, ?, ?, '2026-10-01T00:00:00.000Z')`,
);
insert.run(
A,
'did:plc:a',
1,
'member-list',
null,
'member-list',
null,
'open',
'[]',
);
insert.run(
B,
'did:plc:a',
1,
'public',
null,
'member-list',
null,
'allowList',
'["https://app.example/c.json"]',
);
insert.run(
C,
'did:plc:a',
1,
'member-list',
null,
'managing-app',
'did:web:app.example#svc',
'allowList',
'["https://app.example/c.json"]',
);
insert.run(
D,
'did:plc:b',
0,
'member-list',
null,
'member-list',
null,
'open',
'[]',
);
const member = db.prepare(
'INSERT INTO space_member (space, did, can_read, can_write) VALUES (?, ?, ?, ?)',
);
member.run(A, 'did:plc:m', 1, 0);
member.run(C, 'did:plc:m', 1, 1);
return { db, store: createSpaceStorage(db) };
}
it('makes a memberspace of a member-list space, keeping its members', async () => {
const { store } = await openLegacy();
expect((await store.getSpace(A))?.policy).toEqual({
$type: 'com.atproto.memberspace.policy',
publicRead: false,
publicWrite: false,
publishPolicy: false,
publishMembers: false,
});
expect(await store.getMember(A, 'did:plc:m')).toEqual({
read: true,
write: false,
});
});
it('carries public access and an app allow list into the memberspace', async () => {
const { store } = await openLegacy();
expect((await store.getSpace(B))?.policy).toEqual({
$type: 'com.atproto.memberspace.policy',
publicRead: true,
publicWrite: false,
allowedApps: ['https://app.example/c.json'],
publishPolicy: false,
publishMembers: false,
});
});
it('makes a managedspace of a managing app on either side, dropping its members', async () => {
const { store } = await openLegacy();
expect((await store.getSpace(C))?.policy).toEqual({
$type: 'com.atproto.managedspace.policy',
managingApp: 'did:web:app.example#svc',
publishPolicy: false,
});
expect(await store.getMember(C, 'did:plc:m')).toBeNull();
});
it('gives a space another authority governs no policy', async () => {
const { store } = await openLegacy();
expect((await store.getSpace(D))?.policy).toBeNull();
});
it('converts once, so a second open changes nothing', async () => {
const { db, store } = await openLegacy();
await store.putSpace({
.../** @type {import('@pdsjs/core/ports').SpaceRow} */ (
await store.getSpace(A)
),
policy: { $type: 'com.atproto.personalspace.policy' },
});
const again = createSpaceStorage(db);
expect((await again.getSpace(A))?.policy).toEqual({
$type: 'com.atproto.personalspace.policy',
});
expect(await again.getMember(A, 'did:plc:m')).toEqual({
read: true,
write: false,
});
});
});