Suite of AT Protocol TypeScript libraries built on web standards
1import {
2 ensureValidAtUri,
3 ensureValidDid,
4 ensureValidHandle,
5 ensureValidNsid,
6 ensureValidRecordKey,
7 ensureValidTid,
8} from "@atp/syntax";
9import { ensureValidCidString } from "../data/cid.ts";
10import { isLanguage } from "../data/strings.ts";
11
12declare const __brand: unique symbol;
13type Brand<T, B> = T & { [__brand]: B };
14
15export type DidString = Brand<string, "did">;
16export type HandleString = Brand<string, "handle">;
17export type AtUriString = Brand<string, "at-uri">;
18export type AtIdentifierString = Brand<string, "at-identifier">;
19export type NsidString = `${string}.${string}.${string}`;
20export type CidString = Brand<string, "cid">;
21export type TidString = Brand<string, "tid">;
22export type RecordKeyString = Brand<string, "record-key">;
23export type DatetimeString = Brand<string, "datetime">;
24export type UriString = `${string}:${string}`;
25export type LanguageString = string;
26
27export const STRING_FORMATS: readonly [
28 "datetime",
29 "uri",
30 "at-uri",
31 "did",
32 "handle",
33 "at-identifier",
34 "nsid",
35 "cid",
36 "language",
37 "tid",
38 "record-key",
39] = Object.freeze(
40 [
41 "datetime",
42 "uri",
43 "at-uri",
44 "did",
45 "handle",
46 "at-identifier",
47 "nsid",
48 "cid",
49 "language",
50 "tid",
51 "record-key",
52 ] as const,
53);
54
55export type StringFormat = (typeof STRING_FORMATS)[number];
56
57export type InferStringFormat<F> = F extends "datetime" ? DatetimeString
58 : F extends "uri" ? UriString
59 : F extends "at-uri" ? AtUriString
60 : F extends "did" ? DidString
61 : F extends "handle" ? HandleString
62 : F extends "at-identifier" ? AtIdentifierString
63 : F extends "nsid" ? NsidString
64 : F extends "cid" ? CidString
65 : F extends "language" ? LanguageString
66 : F extends "tid" ? TidString
67 : F extends "record-key" ? RecordKeyString
68 : string;
69
70export function assertDid(input: string): asserts input is DidString {
71 ensureValidDid(input);
72}
73
74export function assertHandle(input: string): asserts input is HandleString {
75 ensureValidHandle(input);
76}
77
78export function assertAtUri(input: string): asserts input is AtUriString {
79 ensureValidAtUri(input);
80}
81
82export function assertAtIdentifier(
83 input: string,
84): asserts input is AtIdentifierString {
85 try {
86 ensureValidDid(input);
87 return;
88 } catch {
89 // did format failed
90 }
91 ensureValidHandle(input);
92}
93
94export function assertNsid(input: string): asserts input is NsidString {
95 ensureValidNsid(input);
96}
97
98export function assertTid(input: string): asserts input is TidString {
99 ensureValidTid(input);
100}
101
102export function assertRecordKey(
103 input: string,
104): asserts input is RecordKeyString {
105 ensureValidRecordKey(input);
106}
107
108export function assertDatetime(input: string): asserts input is DatetimeString {
109 if (
110 !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/.test(
111 input,
112 )
113 ) {
114 throw new Error(`Invalid datetime: ${input}`);
115 }
116}
117
118export function assertCidString(input: string): asserts input is CidString {
119 ensureValidCidString(input);
120}
121
122export function assertUri(input: string): asserts input is UriString {
123 if (!/^\w+:(?:\/\/)?[^\s/][^\s]*$/.test(input)) {
124 throw new Error("Invalid URI");
125 }
126}
127
128export function assertLanguage(
129 input: string,
130): asserts input is LanguageString {
131 if (!isLanguage(input)) {
132 throw new Error("Invalid BCP 47 string");
133 }
134}
135
136const formatters = new Map<StringFormat, (str: string) => void>(
137 [
138 ["datetime", assertDatetime],
139 ["uri", assertUri],
140 ["at-uri", assertAtUri],
141 ["did", assertDid],
142 ["handle", assertHandle],
143 ["at-identifier", assertAtIdentifier],
144 ["nsid", assertNsid],
145 ["cid", assertCidString],
146 ["language", assertLanguage],
147 ["tid", assertTid],
148 ["record-key", assertRecordKey],
149 ] as const,
150);
151
152export function assertStringFormat<F extends StringFormat>(
153 input: string,
154 format: F,
155): asserts input is InferStringFormat<F> {
156 const assertFn = formatters.get(format);
157 if (assertFn) assertFn(input);
158 else throw new Error(`Unknown string format: ${format}`);
159}