import { Nsid } from "@atcute/lexicons";
import { useLocation, useNavigate } from "@solidjs/router";
import { createEffect, For, Show } from "solid-js";
import { resolveLexiconAuthority } from "../utils/api.js";
interface LexiconSchema {
lexicon: number;
id: string;
description?: string;
defs: {
[key: string]: LexiconDef;
};
}
interface LexiconDef {
type: string;
description?: string;
key?: string;
record?: LexiconObject;
parameters?: LexiconObject;
input?: { encoding: string; schema?: LexiconObject };
output?: { encoding: string; schema?: LexiconObject };
errors?: Array<{ name: string; description?: string }>;
properties?: { [key: string]: LexiconProperty };
required?: string[];
nullable?: string[];
maxLength?: number;
minLength?: number;
maxGraphemes?: number;
minGraphemes?: number;
items?: LexiconProperty;
refs?: string[];
closed?: boolean;
enum?: string[];
const?: string;
default?: any;
minimum?: number;
maximum?: number;
accept?: string[];
maxSize?: number;
knownValues?: string[];
format?: string;
}
interface LexiconObject {
type: string;
description?: string;
ref?: string;
refs?: string[];
closed?: boolean;
properties?: { [key: string]: LexiconProperty };
required?: string[];
nullable?: string[];
}
interface LexiconProperty {
type: string;
description?: string;
ref?: string;
refs?: string[];
closed?: boolean;
format?: string;
items?: LexiconProperty;
minLength?: number;
maxLength?: number;
maxGraphemes?: number;
minGraphemes?: number;
minimum?: number;
maximum?: number;
enum?: string[];
const?: string | boolean | number;
default?: any;
knownValues?: string[];
accept?: string[];
maxSize?: number;
}
const TypeBadge = (props: { type: string; format?: string; refType?: string }) => {
const navigate = useNavigate();
const displayType =
props.refType ? props.refType.replace(/^#/, "")
: props.format ? `${props.type}:${props.format}`
: props.type;
const isLocalRef = () => props.refType?.startsWith("#");
const isExternalRef = () => props.refType && !props.refType.startsWith("#");
const handleClick = async () => {
if (isLocalRef()) {
const defName = props.refType!.slice(1);
window.history.replaceState(null, "", `#schema:${defName}`);
const element = document.getElementById(`def-${defName}`);
if (element) {
element.scrollIntoView({ behavior: "instant", block: "start" });
}
} else if (isExternalRef()) {
try {
const [nsid, anchor] = props.refType!.split("#");
const authority = await resolveLexiconAuthority(nsid as Nsid);
const hash = anchor ? `#schema:${anchor}` : "#schema";
navigate(`/at://${authority}/com.atproto.lexicon.schema/${nsid}${hash}`);
} catch (err) {
console.error("Failed to resolve lexicon authority:", err);
}
}
};
return (
<>
{displayType}
>
);
};
const UnionBadges = (props: { refs: string[] }) => (
{(refType) => }
);
const ConstraintsList = (props: { property: LexiconProperty }) => (
minLength: {props.property.minLength}
maxLength: {props.property.maxLength}
maxGraphemes: {props.property.maxGraphemes}
minGraphemes: {props.property.minGraphemes}
min: {props.property.minimum}
max: {props.property.maximum}
maxSize: {props.property.maxSize}
accept: [{props.property.accept!.join(", ")}]
enum: [{props.property.enum!.join(", ")}]
const: {props.property.const?.toString()}
default: {JSON.stringify(props.property.default)}
knownValues: [{props.property.knownValues!.join(", ")}]
closed: true
);
const PropertyRow = (props: {
name: string;
property: LexiconProperty;
required?: boolean;
hideNameType?: boolean;
}) => {
const hasConstraints = (property: LexiconProperty) =>
property.minLength !== undefined ||
property.maxLength !== undefined ||
property.maxGraphemes !== undefined ||
property.minGraphemes !== undefined ||
property.minimum !== undefined ||
property.maximum !== undefined ||
property.maxSize !== undefined ||
property.accept ||
property.enum ||
property.const ||
property.default !== undefined ||
property.knownValues ||
property.closed;
return (
{props.name}
union
required
{props.property.description}
);
};
const DefSection = (props: { name: string; def: LexiconDef }) => {
const defTypeColor = () => {
switch (props.def.type) {
case "record":
return "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300";
case "query":
return "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300";
case "procedure":
return "bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300";
case "subscription":
return "bg-pink-100 text-pink-800 dark:bg-pink-900/30 dark:text-pink-300";
case "object":
return "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300";
case "token":
return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300";
default:
return "bg-neutral-200 text-neutral-800 dark:bg-neutral-700 dark:text-neutral-300";
}
};
const hasDefContent = () =>
props.def.refs ||
props.def.minLength !== undefined ||
props.def.maxLength !== undefined ||
props.def.maxGraphemes !== undefined ||
props.def.minGraphemes !== undefined ||
props.def.minimum !== undefined ||
props.def.maximum !== undefined ||
props.def.maxSize !== undefined ||
props.def.accept ||
props.def.enum ||
props.def.const ||
props.def.default !== undefined ||
props.def.closed ||
props.def.items ||
props.def.knownValues;
return (
{props.def.description}
{/* Record key */}
Record Key:
{props.def.key}
{/* Properties (for record/object types) */}
0}
>
Properties
{([name, property]) => (
)}
{/* Parameters (for query/procedure) */}
0
}
>
Parameters
{([name, property]) => (
)}
{/* Input */}
Input
Encoding:
{props.def.input!.encoding}
Schema:
0
}
>
{([name, property]) => (
)}
{/* Output */}
Output
Encoding:
{props.def.output!.encoding}
Schema:
0
}
>
{([name, property]) => (
)}
{/* Errors */}
0}>
Errors
{(error) => (
{error.name}
{error.description}
)}
{/* Other Definitions */}
);
};
export const LexiconSchemaView = (props: { schema: LexiconSchema }) => {
const location = useLocation();
// Handle scrolling to a definition when hash is like #schema:definitionName
createEffect(() => {
const hash = location.hash;
if (hash.startsWith("#schema:")) {
const defName = hash.slice(8);
requestAnimationFrame(() => {
const element = document.getElementById(`def-${defName}`);
if (element) element.scrollIntoView({ behavior: "instant", block: "start" });
});
}
});
return (
{/* Header */}
{props.schema.id}
Lexicon version:
{props.schema.lexicon}
{props.schema.description}
{/* Definitions */}
{([name, def]) => }
);
};