~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
132 lines · 4.2 KB Raw History
// The query gate in createServer: experimental.sites.queries composes the engine's route
// and commit tap in; absent, the endpoint does not exist. Driven through
// pds.fetch and the real write path, so the tap itself is covered too.

import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import { createServer } from '../src/index.js';

const DID = 'did:plc:querywiringtestaccountaa';
const PASSWORD = 'wiring-test-password';
const BASE = 'https://pds.example.com';
const RUN = `${BASE}/xrpc/dev.pdsjs.query.run`;

/** @type {Array<{close: () => Promise<void>}>} */
const servers = [];
/** @type {string[]} */
const dirs = [];

async function makeServer(opts = {}) {
  const dir = mkdtempSync(join(tmpdir(), 'pdsjs-query-wiring-'));
  dirs.push(dir);
  const server = await createServer({
    dbPath: join(dir, 'pds.db'),
    blobsDir: join(dir, 'blobs'),
    jwtSecret: 'wiring-test-secret',
    hostname: 'pds.example.com',
    password: PASSWORD,
    ...opts,
  });
  servers.push(server);
  return server;
}

/** @param {import('@pdsjs/core').PersonalDataServer} pds */
async function initAccount(pds) {
  const init = await pds.fetch(
    new Request(`${BASE}/init?did=${DID}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        did: DID,
        privateKey: Buffer.from(
          crypto.getRandomValues(new Uint8Array(32)),
        ).toString('hex'),
        handle: 'query.test',
        password: PASSWORD,
      }),
    }),
  );
  expect(init.ok).toBe(true);

  const session = await pds.fetch(
    new Request(`${BASE}/xrpc/com.atproto.server.createSession`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ identifier: DID, password: PASSWORD }),
    }),
  );
  const { accessJwt } = await session.json();
  return { Authorization: `Bearer ${accessJwt}` };
}

/**
 * @param {import('@pdsjs/core').PersonalDataServer} pds
 * @param {Record<string, string>} auth
 * @param {string} collection
 * @param {string} rkey
 * @param {Record<string, unknown>} record
 */
async function put(pds, auth, collection, rkey, record) {
  const response = await pds.fetch(
    new Request(`${BASE}/xrpc/com.atproto.repo.putRecord`, {
      method: 'POST',
      headers: { ...auth, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        repo: DID,
        collection,
        rkey,
        record: { $type: collection, ...record },
      }),
    }),
  );
  expect(response.ok).toBe(true);
}

afterEach(async () => {
  for (const server of servers.splice(0)) {
    await server.close().catch(() => {});
  }
  for (const dir of dirs.splice(0))
    rmSync(dir, { recursive: true, force: true });
});

describe('node query wiring', () => {
  it('serves no query endpoint without experimental.sites.queries', async () => {
    const { pds } = await makeServer();
    const response = await pds.fetch(new Request(`${RUN}?query=x`));
    expect(response.status).toBe(501);
  });

  it('answers queries and sees writes through the commit tap', async () => {
    const { pds } = await makeServer({
      experimental: { sites: { queries: true } },
    });
    const auth = await initAccount(pds);

    await put(pds, auth, 'dev.pdsjs.query.def', 'notes', {
      collection: 'com.example.note',
      sort: { field: 'title', direction: 'asc' },
      createdAt: new Date().toISOString(),
    });
    await put(pds, auth, 'com.example.note', '3aaa', { title: 'beta' });

    const first = await pds.fetch(new Request(`${RUN}?query=notes`));
    expect(first.status).toBe(200);
    expect(first.headers.get('access-control-allow-origin')).toBe('*');
    const body = await first.json();
    expect(body.records.map((/** @type {any} */ r) => r.value.title)).toEqual([
      'beta',
    ]);

    // A write after the collection is cached arrives via the tap
    await put(pds, auth, 'com.example.note', '3bbb', { title: 'alpha' });
    const second = await pds.fetch(new Request(`${RUN}?query=notes`));
    const updated = await second.json();
    expect(
      updated.records.map((/** @type {any} */ r) => r.value.title),
    ).toEqual(['alpha', 'beta']);
  });
});