-
Notifications
You must be signed in to change notification settings - Fork 4
/
codec.ts
138 lines (123 loc) · 4.72 KB
/
codec.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import { AssertState } from "./assert.ts"
import { DecodeBuffer, EncodeBuffer } from "./buffer.ts"
import { Metadata } from "./metadata.ts"
import { ScaleAssertError, ScaleEncodeError } from "./util.ts"
export type Input<T extends AnyCodec> = T extends Codec<infer I, unknown> ? I : never
export type Output<T extends AnyCodec> = T extends Codec<never, infer O> ? O : never
export function createCodec<I, O = I>(
_codec:
& ThisType<Codec<I, O>>
& Pick<Codec<I, O>, "_encode" | "_decode" | "_assert" | "_staticSize" | "_metadata">,
): Codec<I, O> {
const { _staticSize, _encode, _assert, _decode, _metadata } = _codec
const codec: Codec<I, O> = {
// @ts-ignore https://gist.github.com/tjjfvi/ea194c4fce76dacdd60a0943256332aa
__proto__: Codec.prototype,
_staticSize,
_encode,
_decode,
_assert,
_metadata,
}
return codec
}
type NoInfer<T> = T extends infer U ? U : never
export function withMetadata<I, O>(metadata: Metadata<NoInfer<I>, NoInfer<O>>, codec: Codec<I, O>): Codec<I, O> {
const result: Codec<I, O> = {
// @ts-ignore https://gist.github.com/tjjfvi/ea194c4fce76dacdd60a0943256332aa
__proto__: Codec.prototype,
...codec,
_metadata: [...metadata as Metadata<I, O>, ...codec._metadata],
}
return result
}
const codecInspectCtx = new Map<AnyCodec, number | null>()
let codecInspectIdN = 0
const nodeCustomInspect = Symbol.for("nodejs.util.inspect.custom")
const denoCustomInspect = Symbol.for("Deno.customInspect")
abstract class _Codec {
private [nodeCustomInspect](_0: unknown, _1: unknown, inspect: (value: unknown) => string) {
return this._inspect(inspect)
}
private [denoCustomInspect](inspect: (value: unknown, opts: unknown) => string, opts: unknown) {
return this._inspect((x) => inspect(x, opts))
}
// Properly handles circular codecs in the case of $.deferred
private _inspect(inspect: (value: unknown) => string): string
private _inspect(this: AnyCodec, inspect: (value: unknown) => string): string {
let id = codecInspectCtx.get(this)
if (id !== undefined) {
if (id === null) {
codecInspectCtx.set(this, id = codecInspectIdN++)
}
return `$${id}`
}
try {
codecInspectCtx.set(this, null)
const metadata = this._metadata[0]
const content = metadata
? metadata.type === "atomic"
? metadata.name
: `${metadata.name}(${inspect(metadata.args).replace(/^\[(?: (.+) |(.+))\]$/s, "$1$2")})`
: "?"
id = codecInspectCtx.get(this)
return id !== null ? `$${id} = ${content}` : content
} finally {
codecInspectCtx.delete(this)
if (codecInspectCtx.size === 0) codecInspectIdN = 0
}
}
}
export type AnyCodec = Codec<never, unknown>
export type Encodec<I> = Codec<I, unknown>
export type Decodec<O> = Codec<never, O>
export abstract class Codec<in I, out O = I> 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: I) => void
/** Decodes the value from the supplied buffer */
abstract _decode: (buffer: DecodeBuffer) => O
/** 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<I, O>
/** Encodes the value into a new Uint8Array (throws if async) */
encode(value: I) {
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: I) {
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)
}
/** Requires the codec to have an explicit type annotation; if it doesn't, use `$.assert` instead. */
assert(value: unknown): asserts value is I {
assert(this, value)
}
}
/** Asserts that the value is valid for the specified codec */
export function assert<I>(codec: Codec<I, unknown>, value: unknown): asserts value is I {
codec._assert(new AssertState(value))
}
export function is<T>(codec: Codec<T>, value: unknown): value is T {
try {
codec._assert(new AssertState(value))
return true
} catch (e) {
if (e instanceof ScaleAssertError) {
return false
} else {
throw e
}
}
}