- deprecatedLatest
- v0.13.0
- v0.12.2
- v0.12.1
- v0.12.0
- v0.11.2
- v0.11.1
- v0.11.0
- v0.11.0-beta.2
- v0.11.0-beta.1
- v0.11.0-beta.0
- v0.10.2
- v0.10.1
- v0.10.0
- v0.9.1
- v0.9.0
- v0.8.2
- v0.8.1
- v0.8.0
- v0.7.1
- v0.7.0
- v0.6.2
- v0.6.1
- v0.6.0
- v0.5.3
- v0.5.2
- v0.5.1
- v0.5.0
- v0.4.2
- v0.4.1
- v0.4.0
- v0.3.3
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.1
- v0.2.0
- v0.1.4
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
- v0.1.0-beta.7
- v0.1.0-beta.6
- v0.1.0-beta.5
- v0.1.0-beta.4
- v0.1.0-beta.3
- v0.1.0-beta.2
- v0.1.0-beta.1
SCALE Codecs for JavaScript and TypeScript
A TypeScript implementation of SCALE (Simple Concatenated Aggregate Little-Endian) transcoding (see Rust implementation here), which emphasizes JS-land representations and e2e type-safety.
Setup
If youâre using Deno, simply import via the deno.land/x
specifier.
import * as $ from "https://deno.land/x/scale/mod.ts";
If youâre using Node, install as follows.
npm install scale-codec
Then import as follows.
import * as $ from "scale-codec";
Usage
- Import the library
- Define a codec via the libraryâs functions, whose names correspond to types
- Utilize the codec youâve defined
Example
import * as $ from "https://deno.land/x/scale/mod.ts";
const $superhero = $.object(
["pseudonym", $.str],
["secretIdentity", $.option($.str)],
["superpowers", $.array($.str)],
);
const valueToEncode = {
pseudonym: "Spider-Man",
secretIdentity: "Peter Parker",
superpowers: ["does whatever a spider can"],
};
const encodedBytes: Uint8Array = $superhero.encode(valueToEncode);
const decodedValue: Superhero = $superhero.decode(encodedBytes);
assertEquals(decodedValue, valueToEncode);
To extract the JS-native TypeScript type from a given codec, use the Native
utility type.
type Superhero = $.Native<typeof $superhero>;
// {
// pseudonym: string;
// secretIdentity: string | undefined;
// superpowers: string[];
// }
You can also explicitly type the codec, which will validate that the inferred type aligns with the expected.
interface Superhero {
pseudonym: string;
secretIdentity: string | undefined;
superpowers: string[];
}
const $superhero: Codec<Superhero> = $.object(
["pseudonym", $.str],
["secretIdentity", $.option($.str)],
["superpowers", $.array($.str)],
);
// @ts-expect-error
// Type 'Codec<{ pseudonym: string; secretIdentity: string | undefined; }>' is not assignable to type 'Codec<Superhero>'.
// The types returned by 'decode(...)' are incompatible between these types.
// Type '{ pseudonym: string; secretIdentity: string | undefined; }' is not assignable to type 'Superhero'.
const $plebeianHero: Codec<Superhero> = $.object(
["pseudonym", $.str],
["secretIdentity", $.option($.str)],
);
You can also validate a value against a codec using $.assert
or $.is
:
value; // unknown
if ($.is($superhero, value)) {
value; // Superhero
}
value; // unknown
$.assert($superhero, value);
value; // Superhero
If $.assert
fails, it will throw a ScaleAssertError
detailing why the value was invalid.
Further examples can be found in the examples
directory.
Codec Naming
This library adopts a convention of denoting codecs with a $
â $.foo
for built-in codecs, and $foo
for user-defined codecs. This makes codecs easily distinguishable from other values, and makes it easier to have codecs in scope with other variables:
interface Person { ... }
const $person = $.object(...)
const person = { ... }
Here, the type, codec, and a value can all coexist without clashing, without having to resort to wordy workarounds like personCodec
.
The main other library this could possibly clash with is jQuery, and its usage has waned enough that this is not a serious problem.
While we recommend following this convention for consistency, you can, of course, adopt an alternative convention if the $
is problematic â $.foo
can easily become s.foo
or scale.foo
with an alternate import name.
Asynchronous Encoding
Some codecs require asynchronous encoding. Calling .encode()
on a codec will throw if it or another codec it calls is asynchronous. In this case, you must call .encodeAsync()
instead, which returns a Promise<Uint8Array>
. You can call .encodeAsync()
on any codec; if it is a synchronous codec, it will simply resolve immediately.
Asynchronous decoding is not supported.
Custom Codecs
If your encoding/decoding logic is more complicated, you can create custom codecs with createCodec
:
const $foo = $.createCodec<Foo>({
_metadata: $.metadata("$foo"),
// A static estimation of the encoded size, in bytes.
// This can be either an under- or over- estimate.
_staticSize: 123,
_encode(buffer, value) {
// Encode `value` into `buffer.array`, starting at `buffer.index`.
// A `DataView` is also supplied as `buffer.view`.
// At first, you may only write at most as many bytes as `_staticSize`.
// After you write bytes, you must update `buffer.index` to be the first unwritten byte.
// If you need to write more bytes, call `buffer.pushAlloc(size)`.
// If you do this, you can then write at most `size` bytes,
// and then you must call `buffer.popAlloc()`.
// You can also call `buffer.insertArray()` to insert an array without consuming any bytes.
// You can delegate to another codec by calling `$bar._encode(buffer, bar)`.
// Before doing so, you must ensure that `$bar._staticSize` bytes are free,
// either by including it in `_staticSize` or by calling `buffer.pushAlloc()`.
// Note that you should use `_encode` and not `encode`.
// See the `EncodeBuffer` class for information on other methods.
// ...
},
_decode(buffer) {
// Decode `value` from `buffer.array`, starting at `buffer.index`.
// A `DataView` is also supplied as `buffer.view`.
// After you read bytes, you must update `buffer.index` to be the first unread byte.
// You can delegate to another codec by calling `$bar._decode(buffer)`.
// Note that you should use `_decode` and not `decode`.
// ...
return value;
},
_assert(assert: $.AssertState) {
// Validate that `assert.value` is valid for this codec.
// `assert` exposes various utility methods, such as `assert.instanceof`.
// See the `AssertState` class for information on other methods.
// You can delegate to another codec by calling `$bar._assert(assert)` or `$bar._assert(assert.access("key"))`.
// Any errors thrown should be an instance of `$.ScaleAssertError`, and should use `assert.path`.
// ...
},
});