atjamalpha

API reference

Reference. Every public export of @atjam/lexicons, with exact signatures. For the wire-level field reference (max lengths, formats), see the JSON schemas — this page documents the TypeScript surface.

Everything below is exported from the package root:

import {
  Jam, Round, Signup, Invitation, Submission,
  Queries, Validate, Eligibility,
  NSIDS, type StrongRef,
} from "@atjam/lexicons";

Jam, Round, Signup, Invitation, Submission, Queries, Validate, and Eligibility are namespaces (export * as …). StrongRef and NSIDS are re-exported directly.

The blocks below show each export's shape as accessed through its namespace — e.g. the Jam.Jam type, the Round.deriveState function. They are reference notation, not literal declarations you can paste (you can't declare interface Jam.Jam). For copy-ready imports, see Getting started.

NSIDS

A frozen map of the five collection NSIDs.

const NSIDS: {
  jam: "at.atjam.jam";
  round: "at.atjam.round";
  signup: "at.atjam.signup";
  invitation: "at.atjam.invitation";
  submission: "at.atjam.submission";
}

StrongRef

interface StrongRef {
  uri: string;
  cid: string;
}

A content-addressed reference to a record. Used wherever one record points at another (round.jam, signup.round, signup.invitation, invitation.round, submission.round, submission.payload).

Jam

Jam.NSID; // "at.atjam.jam"

interface Jam.JamLink {
  label?: string;
  url: string;
}

interface Jam.Jam {
  $type?: "at.atjam.jam";
  name: string;
  description?: string;
  kind?: string;
  links?: Jam.JamLink[];
  createdAt: string;
}

// Type guard: true if v has string `name` and `createdAt`.
function Jam.isJam(v: unknown): v is Jam.Jam;

Round

Round.NSID; // "at.atjam.round"

interface Round.Milestone {
  label: string;
  date: string;
}

type Round.JoinMode = "open" | "hosted" | "network";
type Round.NetworkGate = "signup" | "contributed";
type Round.RoundState = "open" | "in-progress" | "closed";

interface Round.Round {
  $type?: "at.atjam.round";
  jam: StrongRef;
  name?: string;
  assignment: string;
  subject?: unknown;
  acceptedSubmissionTypes: string[];
  milestones: Round.Milestone[];
  closingEvent?: unknown;
  joinMode?: Round.JoinMode | string;
  networkGate?: Round.NetworkGate | string;
  createdAt: string;
}

// Resolve joinMode for consumer logic; unknown/absent → "open".
function Round.getJoinMode(round: Round.Round): Round.JoinMode;

// Resolve networkGate (only meaningful for "network"); unknown/absent → "signup".
function Round.getNetworkGate(round: Round.Round): Round.NetworkGate;

// True for "hosted" and "network"; false for "open" and unknown values.
function Round.requiresInvitation(round: Round.Round): boolean;

// Type guard: checks `assignment`, `acceptedSubmissionTypes`, `milestones`, `createdAt`.
function Round.isRound(v: unknown): v is Round.Round;

// Derive state from milestones. "closed" if past submission-deadline; else
// "in-progress" if past signup-deadline; else "open". Missing deadlines bias
// toward "open". Boundary comparisons use >= (the deadline moment counts as past).
function Round.deriveState(round: Round.Round, now?: Date): Round.RoundState;

interface Round.RoundPhase {
  index: number;     // 0 before the first milestone; milestones.length after the last
  since?: Round.Milestone;  // milestone that opened the current phase
  until?: Round.Milestone;  // next upcoming milestone
}

// Where now falls among the (date-sorted) milestones — a fine-grained companion
// to deriveState for rounds using custom milestone labels as phases.
function Round.deriveCurrentPhase(round: Round.Round, now?: Date): Round.RoundPhase;

Signup

Signup.NSID; // "at.atjam.signup"

interface Signup.Signup {
  $type?: "at.atjam.signup";
  round: StrongRef;
  invitation?: StrongRef; // required by readers for hosted/network rounds
  note?: string;
  createdAt: string;
}

Invitation

Invitation.NSID; // "at.atjam.invitation"

interface Invitation.Invitation {
  $type?: "at.atjam.invitation";
  round: StrongRef;
  invitee: string; // a DID
  note?: string;
  createdAt: string;
}

// Type guard: checks `round` (object), `invitee` (string), `createdAt` (string).
function Invitation.isInvitation(v: unknown): v is Invitation.Invitation;

Submission

Submission.NSID; // "at.atjam.submission"

interface Submission.Submission {
  $type?: "at.atjam.submission";
  round: StrongRef;
  payload?: StrongRef; // preferred deliverable: a record on its native app
  url?: string;        // fallback deliverable: a plain link (prefer payload)
  note?: string;
  createdAt: string;
}

// The deliverable normalized to one form — the single gate for "exactly one of".
type Submission.Deliverable =
  | { kind: "record"; ref: StrongRef }
  | { kind: "url"; url: string };

// Returns the record (preferred), else the url, else null (malformed).
function Submission.getDeliverable(s: Submission.Submission): Submission.Deliverable | null;

// Whether a submission carries any deliverable (the "at least one" rule).
function Submission.hasDeliverable(s: Submission.Submission): boolean;

Queries

The read layer. Re-exports the contents of at-uri, did, pds, constellation, and fetchers.

AT URI utilities (no network)

interface Queries.ParsedAtUri {
  did: string;
  collection: string;
  rkey: string;
}

// Parse "at://<did>/<collection>/<rkey>". Throws on a non-at:// or malformed URI.
function Queries.parseAtUri(uri: string): Queries.ParsedAtUri;

// Reassemble an AT URI from its parts.
function Queries.buildAtUri(parts: Queries.ParsedAtUri): string;

DID resolution

interface Queries.ResolvedDid {
  pds: string;
  handle?: string; // best-effort, from alsoKnownAs[0]
}

type Queries.HandleMap = Record<string, string>;

// Configure the PLC directory URL (default "https://plc.directory"). No-op on empty.
function Queries.setPlcDirectoryUrl(url: string): void;
function Queries.getPlcDirectoryUrl(): string;

// Resolve a DID to its PDS endpoint (and best-effort handle). Supports
// did:plc and did:web. Per-process cached.
function Queries.resolveDid(did: string): Promise<Queries.ResolvedDid>;

// Resolve many DIDs to a HandleMap in parallel; per-DID failures are dropped.
function Queries.resolveHandles(dids: Iterable<string>): Promise<Queries.HandleMap>;

Raw PDS reads (unauthenticated)

interface Queries.PdsRecord<T = unknown> {
  uri: string;
  cid: string;
  value: T;
}

interface Queries.ListRecordsResponse<T = unknown> {
  records: Queries.PdsRecord<T>[];
  cursor?: string;
}

// com.atproto.repo.listRecords against the DID's PDS.
function Queries.listRecords<T = unknown>(args: {
  did: string;
  collection: string;
  limit?: number;
  cursor?: string;
  reverse?: boolean;
}): Promise<Queries.ListRecordsResponse<T>>;

// com.atproto.repo.getRecord against the DID's PDS.
function Queries.getRecord<T = unknown>(args: {
  did: string;
  collection: string;
  rkey: string;
}): Promise<Queries.PdsRecord<T>>;

Constellation backlinks

interface Queries.BacklinkRef {
  uri: string;
  cid?: string;
}

interface Queries.BacklinksResponse {
  links: Queries.BacklinkRef[];
  cursor?: string;
  total?: number;
}

// Configure the Constellation URL (default "https://constellation.microcosm.blue"). No-op on empty.
function Queries.setConstellationUrl(url: string): void;
function Queries.getConstellationUrl(): string;

// Given a target record, return records that link to it at the given JSON path.
function Queries.getBacklinks(args: {
  target: string;
  collection: string;
  path: string;
  limit?: number;
  cursor?: string;
}): Promise<Queries.BacklinksResponse>;

Composed atjam fetchers

// Direct reads.
function Queries.fetchRound(did: string, rkey: string): Promise<Queries.PdsRecord<Round.Round>>;
function Queries.fetchJam(did: string, rkey: string): Promise<Queries.PdsRecord<Jam.Jam>>;
function Queries.fetchJamByUri(jamUri: string): Promise<Queries.PdsRecord<Jam.Jam>>;

// Rounds under a jam (backlinks + fetch each body; unreadable rounds dropped).
function Queries.fetchRoundsForJam(jamUri: string): Promise<Queries.PdsRecord<Round.Round>[]>;

// A round's signups — raw backlinks (bodies not fetched).
function Queries.fetchSignupsForRound(roundUri: string): Promise<Queries.BacklinksResponse>;

// A round's submissions / invitations — bodies fetched (unreadable dropped).
function Queries.fetchSubmissionsForRound(roundUri: string): Promise<Queries.PdsRecord<Submission.Submission>[]>;
function Queries.fetchInvitationsForRound(roundUri: string): Promise<Queries.PdsRecord<Invitation.Invitation>[]>;

// Cross-organizer feed: list each DID's rounds, dedupe, sort by createdAt desc.
function Queries.fetchHomeFeed(dids: string[]): Promise<Queries.PdsRecord<Round.Round>[]>;

// A DID's own jams (newest first).
function Queries.fetchMyJams(did: string): Promise<Queries.PdsRecord<Jam.Jam>[]>;

Validate

Read-only signup validation. See the validation contract.

interface Validate.ValidationResult {
  valid: boolean;
  reason?: string;
}

// Fetchers the validator needs; defaults wrap Queries. Tests inject stubs.
interface Validate.ValidateFetchers {
  getInvitation(uri: string): Promise<Invitation.Invitation | null>;
  getSignupForRound(args: { did: string; roundUri: string }): Promise<{ uri: string; value: Signup.Signup } | null>;
  hasSubmissionForRound(args: { did: string; roundUri: string }): Promise<boolean>;
}

interface Validate.ValidateSignupArgs {
  signup: { uri: string; value: Signup.Signup };
  round: { uri: string; value: Round.Round };
  fetchers?: Partial<Validate.ValidateFetchers>;
}

function Validate.validateSignup(args: Validate.ValidateSignupArgs): Promise<Validate.ValidationResult>;

Eligibility

Pure predicates (no I/O) for prospective "may this DID invite?" questions.

interface Eligibility.InviterContext {
  round: Round.Round;
  organizerDid: string;
  validSignerDids: readonly string[];
  submitterDids: readonly string[];
}

// Is inviterDid a valid inviter for the round?
//   organizer → always; hosted → only organizer; network → valid signer
//   (and, under "contributed", must also be a submitter).
function Eligibility.isValidInviter(ctx: Eligibility.InviterContext, inviterDid: string): boolean;

// May viewerDid issue invitations? Open rounds → always false; else isValidInviter.
function Eligibility.canInvite(ctx: Eligibility.InviterContext, viewerDid: string): boolean;

// The invitation that would make viewerDid's signup valid: names the viewer
// as invitee AND comes from a valid inviter. Generic over the record shape.
function Eligibility.findUsableInvitation<
  I extends { uri: string; value: { invitee: string } },
>(
  ctx: Eligibility.InviterContext,
  viewerDid: string,
  invitations: readonly I[],
): I | undefined;

Not exported: writes

@atjam/lexicons provides no write helpers. Writing records is the application's job, via com.atproto.repo.createRecord with your own ATProto client. See Reading, writing, validating.