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.Jamtype, theRound.deriveStatefunction. They're reference notation, not literal declarations you can paste (you can't declareinterface 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;
Identity and people
A handle is only reported if it checks out both ways: the DID document
claims it and it resolves back to that DID. Otherwise it reads
handle.invalid (Queries.INVALID_HANDLE) — render that as a warning,
never as an address. Lookups go to Slingshot first and fall back to
checking locally; results are cached for an hour (five minutes for
failures).
interface Queries.Identity {
did: string;
handle: string; // verified, or "handle.invalid"
pds: string;
}
interface Queries.Person {
handle: string; // verified, or "handle.invalid"
displayName?: string;
avatar?: string; // small thumbnail URL
}
type Queries.PeopleMap = Record<string, Queries.Person>;
// A handle (with or without "@", any case) or a DID → verified identity.
function Queries.resolveIdentity(identifier: string): Promise<Queries.Identity>;
// Many DIDs → how to show each person: batched profiles (25 per call) for
// names and avatars, identity resolution for anyone without a profile.
// DIDs that can't be resolved at all are absent — fall back to the DID.
function Queries.resolvePeople(dids: Iterable<string>): Promise<Queries.PeopleMap>;
// DID → PDS endpoint (and verified handle, or undefined).
function Queries.resolveDid(did: string): Promise<{ pds: string; handle?: string }>;
function Queries.normalizeHandle(input: string): string; // trim, drop "@", lowercase
function Queries.isDid(s: string): boolean;
// Defaults: plc.directory and slingshot.microcosm.blue. No-op on empty.
function Queries.setPlcDirectoryUrl(url: string): void;
function Queries.setIdentityServiceUrl(url: string): void;
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>>;
// Every record in the collection, following cursors to the end.
function Queries.listAllRecords<T = unknown>(args: {
did: string;
collection: string;
reverse?: boolean;
}): Promise<Queries.PdsRecord<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>;
// Every backlink for the target, following cursors to the end.
function Queries.getAllBacklinks(args: {
target: string;
collection: string;
path: string;
}): Promise<Queries.BacklinkRef[]>;
A single page is a sample, not an answer. Use the All variants wherever a
short page would make a count or a membership check silently wrong.
Backlink paths
Every link between atjam records is a strong-ref inside the child record, so
Constellation indexes it by the child's collection and the JSON path to the
ref's uri. These are the collection:path pairs atjam itself queries — and
the ones anyone can use to ask what points at an atjam record:
| Collection | Path | Target | Answers |
|---|---|---|---|
at.atjam.round | .jam.uri | a jam | the rounds of a jam |
at.atjam.signup | .round.uri | a round | who signed up |
at.atjam.submission | .round.uri | a round | what was submitted |
at.atjam.invitation | .round.uri | a round | who was invited |
const signups = await Queries.getAllBacklinks({
target: roundUri, // at://did/at.atjam.round/rkey
collection: "at.atjam.signup",
path: ".round.uri",
});
// → [{ uri: "at://<signer>/at.atjam.signup/<rkey>", cid? }, …]
The composed fetchers below are these four queries plus a getRecord for
each result.
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 / submissions / invitations — every page, bodies fetched (unreadable dropped).
function Queries.fetchSignupsForRound(roundUri: string): Promise<Queries.PdsRecord<Signup.Signup>[]>;
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.