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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
72 lines · 2.6 KB Raw History
/**
 * The archive's on-disk block format, written by hand so a test can serve
 * the bytes `getBlock` returns.
 */

import { zstdCompressSync } from 'node:zlib';
import { cborEncodeDagCbor } from '@pdsjs/core/repo';

/**
 * The columnar block body exactly as the archive writes it: an event
 * count, per-event fixed columns, then the concatenated variable columns.
 * @param {Array<{seq: number, kind: number, did: string, collection: string, rkey: string, rev: string, payload: Uint8Array}>} rows
 * @returns {Uint8Array}
 */
export function encodeBlockColumns(rows) {
  /** @param {number} value @param {1|2|4|8} bytes */
  const le = (value, bytes) => {
    const out = new Uint8Array(bytes);
    const view = new DataView(out.buffer);
    if (bytes === 1) view.setUint8(0, value);
    if (bytes === 2) view.setUint16(0, value, true);
    if (bytes === 4) view.setUint32(0, value, true);
    if (bytes === 8) view.setBigUint64(0, BigInt(value), true);
    return out;
  };
  /** @param {string} value @returns {Uint8Array} */
  const text = (value) => new TextEncoder().encode(value);
  /** @param {Uint8Array[]} parts @returns {Uint8Array} */
  const join = (parts) => {
    const total = parts.reduce((sum, part) => sum + part.length, 0);
    const out = new Uint8Array(total);
    let offset = 0;
    for (const part of parts) {
      out.set(part, offset);
      offset += part.length;
    }
    return out;
  };
  return join([
    le(rows.length, 4),
    ...rows.map((row) => le(row.seq, 8)),
    ...rows.map(() => le(0, 8)),
    ...rows.map(() => le(0, 8)),
    ...rows.map((row) => le(row.kind, 1)),
    ...rows.map((row) => le(row.collection.length, 1)),
    ...rows.map((row) => le(row.did.length, 2)),
    ...rows.map((row) => le(row.rkey.length, 1)),
    ...rows.map((row) => le(row.rev.length, 1)),
    ...rows.map((row) => le(row.payload.length, 4)),
    ...rows.map((row) => text(row.collection)),
    ...rows.map((row) => text(row.did)),
    ...rows.map((row) => text(row.rkey)),
    ...rows.map((row) => text(row.rev)),
    ...rows.map((row) => row.payload),
  ]);
}

/**
 * One row, encoded and zstd-compressed into the frame `getBlock` serves.
 * @param {Array<{seq: number, kind: number, did: string, collection: string, rkey: string, rev: string, payload?: Record<string, unknown>|Uint8Array}>} rows
 * @returns {Uint8Array}
 */
export function blockFrame(rows) {
  const encoded = rows.map((row) => ({
    ...row,
    payload:
      row.payload instanceof Uint8Array
        ? row.payload
        : cborEncodeDagCbor(row.payload ?? {}),
  }));
  return zstdCompressSync(Buffer.from(encodeBlockColumns(encoded)));
}