diff --git a/codecs/test/__snapshots__/object.test.ts.snap b/codecs/test/__snapshots__/object.test.ts.snap index de39e02..cd65e37 100644 --- a/codecs/test/__snapshots__/object.test.ts.snap +++ b/codecs/test/__snapshots__/object.test.ts.snap @@ -71,7 +71,10 @@ snapshot[`\$person invalid { name: "", nickName: "", superPower: "", unluckyNumb snapshot[`\$.object( \$.taggedUnion( "_tag", - [ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ] + [ + Variant { tag: "a", codec: \$.object(Array) }, + Variant { tag: "b", codec: \$.object(Array) } + ] ), \$.field("bar", \$.u8) ) { _tag: "a", bar: 123 } 1`] = ` @@ -82,7 +85,10 @@ snapshot[`\$.object( snapshot[`\$.object( \$.taggedUnion( "_tag", - [ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ] + [ + Variant { tag: "a", codec: \$.object(Array) }, + Variant { tag: "b", codec: \$.object(Array) } + ] ), \$.field("bar", \$.u8) ) { _tag: "b", x: 0, bar: 123 } 1`] = ` @@ -94,7 +100,10 @@ snapshot[`\$.object( snapshot[`\$.object( \$.taggedUnion( "_tag", - [ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ] + [ + Variant { tag: "a", codec: \$.object(Array) }, + Variant { tag: "b", codec: \$.object(Array) } + ] ), \$.field("bar", \$.u8) ) invalid null 1`] = `ScaleAssertError: value == null`; @@ -102,7 +111,10 @@ snapshot[`\$.object( snapshot[`\$.object( \$.taggedUnion( "_tag", - [ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ] + [ + Variant { tag: "a", codec: \$.object(Array) }, + Variant { tag: "b", codec: \$.object(Array) } + ] ), \$.field("bar", \$.u8) ) invalid { _tag: null, bar: 1 } 1`] = `ScaleAssertError: typeof value._tag !== "string"`; @@ -110,7 +122,10 @@ snapshot[`\$.object( snapshot[`\$.object( \$.taggedUnion( "_tag", - [ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ] + [ + Variant { tag: "a", codec: \$.object(Array) }, + Variant { tag: "b", codec: \$.object(Array) } + ] ), \$.field("bar", \$.u8) ) invalid { _tag: "", bar: 1 } 1`] = `ScaleAssertError: value._tag: invalid tag`; @@ -118,7 +133,10 @@ snapshot[`\$.object( snapshot[`\$.object( \$.taggedUnion( "_tag", - [ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ] + [ + Variant { tag: "a", codec: \$.object(Array) }, + Variant { tag: "b", codec: \$.object(Array) } + ] ), \$.field("bar", \$.u8) ) invalid { _tag: "b", bar: 1 } 1`] = `ScaleAssertError: !("x" in value)`; @@ -126,7 +144,10 @@ snapshot[`\$.object( snapshot[`\$.object( \$.taggedUnion( "_tag", - [ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ] + [ + Variant { tag: "a", codec: \$.object(Array) }, + Variant { tag: "b", codec: \$.object(Array) } + ] ), \$.field("bar", \$.u8) ) invalid { _tag: "b", x: -1, bar: 1 } 1`] = `ScaleAssertError: value.x < 0`; @@ -134,7 +155,10 @@ snapshot[`\$.object( snapshot[`\$.object( \$.taggedUnion( "_tag", - [ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ] + [ + Variant { tag: "a", codec: \$.object(Array) }, + Variant { tag: "b", codec: \$.object(Array) } + ] ), \$.field("bar", \$.u8) ) invalid { _tag: "a", bar: -1 } 1`] = `ScaleAssertError: value.bar < 0`; diff --git a/codecs/union.ts b/codecs/union.ts index 12bc080..58e0b77 100644 --- a/codecs/union.ts +++ b/codecs/union.ts @@ -3,16 +3,15 @@ import { Codec, createCodec, Expand, metadata, Narrow, ScaleAssertError, ScaleDe import { constant } from "./constant.ts" import { field, NativeObject, object, ObjectMembers } from "./object.ts" -export interface Variant { - tag: T - codec: Codec +export class Variant { + constructor(readonly tag: T, readonly codec: Codec) {} } export function variant( tag: T, ...members: ObjectMembers ): Variant> { - return { tag, codec: object(...members) } + return new Variant(tag, object(...members)) } export type NativeTaggedUnion< diff --git a/common/codec.ts b/common/codec.ts index 2fdce82..e7973ea 100644 --- a/common/codec.ts +++ b/common/codec.ts @@ -3,7 +3,6 @@ import { DecodeBuffer, EncodeBuffer } from "./buffer.ts" import { Metadata } from "./metadata.ts" import { ScaleAssertError, ScaleEncodeError } from "./util.ts" -export type AnyCodec = Codec | Codec export type Native = T extends Codec ? U : never export function createCodec( @@ -39,39 +38,8 @@ const codecInspectCtx = new Map, number | null>() let codecInspectIdN = 0 const nodeCustomInspect = Symbol.for("nodejs.util.inspect.custom") const denoCustomInspect = Symbol.for("Deno.customInspect") -export abstract class Codec { - /** A static estimation of the size, which may be an under- or over-estimate */ - abstract _staticSize: number - /** Encodes the value into the supplied buffer, which should have at least `_staticSize` free byte. */ - abstract _encode: (buffer: EncodeBuffer, value: T) => void - /** Decodes the value from the supplied buffer */ - abstract _decode: (buffer: DecodeBuffer) => T - /** Asserts that the value is valid for this codec */ - abstract _assert: (state: AssertState) => void - /** An array with metadata representing the construction of this codec */ - abstract _metadata: Metadata - - /** Encodes the value into a new Uint8Array (throws if async) */ - encode(value: T) { - const buf = new EncodeBuffer(this._staticSize) - this._encode(buf, value) - if (buf.asyncCount) throw new ScaleEncodeError(this, value, "Attempted to synchronously encode an async codec") - return buf.finish() - } - - /** Asynchronously encodes the value into a new Uint8Array */ - async encodeAsync(value: T) { - const buf = new EncodeBuffer(this._staticSize) - this._encode(buf, value) - return buf.finishAsync() - } - - /** Decodes a value from the supplied Uint8Array */ - decode(array: Uint8Array) { - const buf = new DecodeBuffer(array) - return this._decode(buf) - } +abstract class _Codec { private [nodeCustomInspect](_0: unknown, _1: unknown, inspect: (value: unknown) => string) { return this._inspect(inspect) } @@ -82,7 +50,7 @@ export abstract class Codec { // Properly handles circular codecs in the case of $.deferred private _inspect(inspect: (value: unknown) => string): string - private _inspect(this: Codec, inspect: (value: unknown) => string): string { + private _inspect(this: Codec, inspect: (value: unknown) => string): string { let id = codecInspectCtx.get(this) if (id !== undefined) { if (id === null) { @@ -115,6 +83,52 @@ export abstract class Codec { } } +export interface AnyCodec extends _Codec { + _staticSize: number + _encode(buffer: EncodeBuffer, value: any): void + _decode: (buffer: DecodeBuffer) => any + _assert: (state: AssertState) => void + _metadata: any + + encode(value: any): Uint8Array + encodeAsync(value: any): Promise + decode(array: Uint8Array): any +} + +export abstract class Codec extends _Codec implements AnyCodec { + /** A static estimation of the size, which may be an under- or over-estimate */ + abstract _staticSize: number + /** Encodes the value into the supplied buffer, which should have at least `_staticSize` free byte. */ + abstract _encode: (buffer: EncodeBuffer, value: T) => void + /** Decodes the value from the supplied buffer */ + abstract _decode: (buffer: DecodeBuffer) => T + /** Asserts that the value is valid for this codec */ + abstract _assert: (state: AssertState) => void + /** An array with metadata representing the construction of this codec */ + abstract _metadata: Metadata + + /** Encodes the value into a new Uint8Array (throws if async) */ + encode(value: T) { + const buf = new EncodeBuffer(this._staticSize) + this._encode(buf, value) + if (buf.asyncCount) throw new ScaleEncodeError(this, value, "Attempted to synchronously encode an async codec") + return buf.finish() + } + + /** Asynchronously encodes the value into a new Uint8Array */ + async encodeAsync(value: T) { + const buf = new EncodeBuffer(this._staticSize) + this._encode(buf, value) + return buf.finishAsync() + } + + /** Decodes a value from the supplied Uint8Array */ + decode(array: Uint8Array) { + const buf = new DecodeBuffer(array) + return this._decode(buf) + } +} + export function assert(codec: Codec, value: unknown): asserts value is T { codec._assert(new AssertState(value)) }