Skip to content

Instantly share code, notes, and snippets.

@dckc
Last active May 19, 2023 06:21
Show Gist options
  • Save dckc/e31223c60a8cb13e7b9e901f19165a5f to your computer and use it in GitHub Desktop.
Save dckc/e31223c60a8cb13e7b9e901f19165a5f to your computer and use it in GitHub Desktop.
// TypeScript requires a little gymnastics to handle recursive types.
export type Atom =
| undefined
| null
| boolean
| number
| bigint
| string
| ByteString
| symbol;
export type ByteString = Uint8Array;
const tagSymbol: unique symbol = Symbol();
export type Tagged<Cap, Err> = { [tagSymbol]: string; value: Value<Cap, Err> };
export type Value<Cap, Err> =
| Atom
| ValueArray<Cap, Err>
| ValueRecord<Cap, Err>
| Tagged<Cap, Err>
| Cap
| Err;
interface ValueArray<Cap, Err> extends Array<Value<Cap, Err>> {}
interface ValueRecord<Cap, Err> extends Record<string, Value<Cap, Err>> {}
{
"devDependencies": {
"typescript": "^5.0.4"
},
"dependencies": {
"cbor": "^9.0.0"
}
}
// @ts-check
import cbor from "cbor";
import { MT } from "cbor/types/lib/constants";
import { tagSymbol } from "./ocapn-value";
// https://www.rfc-editor.org/rfc/rfc8949.html#section-3.4.6
const SELF_DESCRIBED = 55799;
/**
* TODO: Cap
* TODO: check return value from encoder methods
*
* @param {cbor.Encoder} encoder
* @param {import('./ocapn-value').Value<never, Error>} value
*/
export const toCBOR = (encoder, value) => {
switch (typeof value) {
case "undefined":
case "boolean":
case "number":
case "string":
case "bigint":
encoder.pushAny(value);
break;
case "symbol":
encoder._pushTag(SELF_DESCRIBED);
encoder.pushAny("S");
encoder.pushAny(value.toString());
break;
case "object":
if (value === null) {
encoder.push(null);
} else if (value instanceof Error) {
encoder._pushTag(SELF_DESCRIBED);
encoder.pushAny("E");
encoder._pushInt(2, MT.ARRAY);
encoder.pushAny(value.name);
encoder.pushAny(value.message);
} else if (tagSymbol in value) {
encoder._pushTag(SELF_DESCRIBED);
encoder._pushInt(3, MT.ARRAY);
encoder.pushAny("T");
const { [tagSymbol]: tag } = value;
encoder.pushAny(tag);
toCBOR(encoder, value.value);
} else if (value instanceof Uint8Array) {
encoder.pushAny(value);
} else if (Array.isArray(value)) {
encoder._pushInt(value.length, MT.ARRAY);
for (const v of value) {
toCBOR(encoder, v);
}
} else {
const length = Object.keys(value).length;
encoder._pushInt(length, MT.MAP);
for (const [k, v] of Object.entries(value)) {
encoder.pushAny(k);
toCBOR(encoder, v);
}
}
break;
default:
throw TypeError(`Unknown type: ${typeof value}`);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment