import {
isCid,
isDatetime,
isDid,
isNsid,
isRecordKey,
isTid,
parseCanonicalResourceUri
} from "@atcute/lexicons/syntax";
import type {
CanonicalResourceUri,
Cid as RawCid,
Datetime as RawDatetime,
Did as RawDid,
Nsid as RawNsid,
RecordKey as RawRecordKey,
ResourceUri as RawResourceUri,
Tid as RawTid
} from "@atcute/lexicons/syntax";
export type { RawCid, RawDatetime, RawResourceUri };
// asserting wrappers over atcute's syntax guards for xrpc input boundaries.
// untrusted strings (user input, route params, storage) get rejected here
// instead of traveling to the server and failing as a 400.
declare const tag: unique symbol;
type Lewitype<T, N extends string> = T & { readonly [tag]: N };
export type Did = Lewitype<RawDid, "Did">;
export type Nsid = Lewitype<RawNsid, "Nsid">;
export type Rkey = Lewitype<RawRecordKey, "Rkey">;
export type Tid = Lewitype<RawTid, "Tid">;
export type Cid = Lewitype<RawCid, "Cid">;
export type AtUri = Lewitype<CanonicalResourceUri, "AtUri">;
export type Datetime = Lewitype<RawDatetime, "Datetime">;
export type KnotOrigin = Lewitype<string, "KnotOrigin">;
export type GitOid = Lewitype<string, "GitOid">;
export type LabelKey = Lewitype<string, "LabelKey">;
export type Capability = Lewitype<string, "Capability">;
export type ProxyPath = Lewitype<string, "ProxyPath">;
const invalid = (kind: string, value: string): Error =>
new Error(`"${value}" is not a valid ${kind}.`);
export const didOf = (value: string): Did => {
if (!isDid(value)) throw invalid("DID", value);
return value as Did;
};
export const nsidOf = (value: string): Nsid => {
if (!isNsid(value)) throw invalid("NSID", value);
return value as Nsid;
};
export const recordKeyOf = (value: string): Rkey => {
if (!isRecordKey(value)) throw invalid("rkey", value);
return value as Rkey;
};
export const tidOf = (value: string): Tid => {
if (!isTid(value)) throw invalid("TID", value);
return value as Tid;
};
export const cidOf = (value: string): Cid => {
if (!isCid(value)) throw invalid("CID", value);
return value as Cid;
};
export const atUriOf = (value: string): AtUri => {
parseCanonicalResourceUri(value);
return value as AtUri;
};
export const datetimeOf = (value: string): Datetime => {
if (!isDatetime(value)) throw invalid("datetime", value);
return value as Datetime;
};
export const knotOf = (value: string): KnotOrigin => {
const parsed = URL.parse(`https://${value}`);
if (parsed === null || parsed.host !== value || parsed.pathname !== "/")
throw invalid("knot origin", value);
return value as KnotOrigin;
};
const GIT_OID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
export const gitOidOf = (value: string): GitOid => {
if (!GIT_OID.test(value)) throw invalid("git OID", value);
return value as GitOid;
};
const LABEL_KEY = /^(?!did:)[A-Za-z0-9._~:-]{1,512}$/;
export const labelKeyOf = (value: string): LabelKey => {
if (!LABEL_KEY.test(value)) throw invalid("label key", value);
return value as LabelKey;
};
const CAPABILITY = /^\S+$/;
export const capabilityOf = (value: string): Capability => {
if (value.length === 0 || value.length > 512 || !CAPABILITY.test(value))
throw invalid("cap", value);
return value as Capability;
};
const PROXY_PATH = /^_[A-Za-z0-9.-]+$/;
export const proxyPathOf = (value: string): ProxyPath => {
if (!PROXY_PATH.test(value)) throw invalid("proxy path", value);
return value as ProxyPath;
};
export type XrpcPath = Nsid | ProxyPath;
export const xrpcPathOf = (value: string): XrpcPath =>
value.startsWith("_") ? proxyPathOf(value) : nsidOf(value);