Suite of AT Protocol TypeScript libraries built on web standards
1import {
2 decode as cborgDecode,
3 decodeFirst as cborgDecodeFirst,
4 type DecodeOptions,
5 encode as cborgEncode,
6 type EncodeOptions,
7 type TagDecoder,
8 Token,
9 Type,
10} from "cborg";
11import { asCid, type Cid, decodeCid } from "../data/cid.ts";
12import type { LexValue } from "../data/lex.ts";
13
14export type { Cid, LexValue };
15
16const CID_CBOR_TAG = 42;
17
18function cidEncoder(obj: object): Token[] | null {
19 const cid = asCid(obj);
20 if (!cid) return null;
21
22 const bytes = new Uint8Array(cid.bytes.byteLength + 1);
23 bytes.set(cid.bytes, 1);
24 return [new Token(Type.tag, CID_CBOR_TAG), new Token(Type.bytes, bytes)];
25}
26
27function undefinedEncoder(): null {
28 throw new Error("`undefined` is not allowed by the AT Data Model");
29}
30
31function numberEncoder(num: number): null {
32 if (Number.isInteger(num)) return null;
33 throw new Error("Non-integer numbers are not allowed by the AT Data Model");
34}
35
36function mapEncoder(map: Map<unknown, unknown>): null {
37 for (const key of map.keys()) {
38 if (typeof key !== "string") {
39 throw new Error(
40 'Only string keys are allowed in CBOR "map" by the AT Data Model',
41 );
42 }
43 }
44 return null;
45}
46
47const encodeOptions: EncodeOptions = {
48 typeEncoders: {
49 Map: mapEncoder,
50 Object: cidEncoder,
51 undefined: undefinedEncoder,
52 number: numberEncoder,
53 },
54};
55
56function cidDecoder(bytes: Uint8Array): Cid {
57 if (bytes[0] !== 0) {
58 throw new Error("Invalid CID for CBOR tag 42; expected leading 0x00");
59 }
60 return decodeCid(bytes.subarray(1));
61}
62
63const tagDecoders: TagDecoder[] = [];
64tagDecoders[CID_CBOR_TAG] = cidDecoder;
65
66const decodeOptions: DecodeOptions = {
67 allowIndefinite: false,
68 coerceUndefinedToNull: true,
69 allowNaN: false,
70 allowInfinity: false,
71 allowBigInt: true,
72 strict: true,
73 useMaps: false,
74 rejectDuplicateMapKeys: true,
75 tags: tagDecoders,
76};
77
78export function encode<T extends LexValue>(data: T): Uint8Array {
79 return cborgEncode(data, encodeOptions);
80}
81
82export function decode<T extends LexValue>(bytes: Uint8Array): T {
83 return cborgDecode(bytes, decodeOptions) as T;
84}
85
86export function* decodeAll<T extends LexValue = LexValue>(
87 data: Uint8Array,
88): Generator<T, void, unknown> {
89 do {
90 const [result, remainingBytes] = cborgDecodeFirst(data, decodeOptions);
91 yield result as T;
92 data = remainingBytes;
93 } while (data.byteLength > 0);
94}