From 4692d7e63366953bfa1effa50e245eb3499a2bb5 Mon Sep 17 00:00:00 2001 From: T6 Date: Tue, 11 Jul 2023 10:44:52 -0400 Subject: [PATCH] refactor: rename to subShape (#168) --- Readme.md | 178 +++++++------ _tasks/build_npm_pkg.ts | 9 +- codecs/bench/array.bench.ts | 18 -- codecs/bench/bool.bench.ts | 4 - codecs/bench/compact.bench.ts | 15 -- codecs/bench/int.bench.ts | 14 -- codecs/bench/option.bench.ts | 8 - codecs/bench/str.bench.ts | 19 -- codecs/bench/tuple.bench.ts | 10 - codecs/bool.ts | 15 -- codecs/deferred.ts | 28 --- codecs/documented.ts | 8 - codecs/instance.ts | 23 -- codecs/lenPrefixed.ts | 30 --- codecs/never.ts | 15 -- codecs/option.ts | 37 --- codecs/promise.ts | 19 -- codecs/result.ts | 49 ---- codecs/test/__snapshots__/bool.test.ts.snap | 15 -- .../__snapshots__/collections.test.ts.snap | 90 ------- codecs/test/__snapshots__/never.test.ts.snap | 11 - codecs/test/__snapshots__/object.test.ts.snap | 164 ------------ codecs/test/optionBool.test.ts | 6 - codecs/transform.ts | 25 -- codecs/tuple.ts | 34 --- common/assert.ts | 46 ++-- common/codec.ts | 138 ----------- common/metadata.ts | 54 ++-- common/mod.ts | 2 +- common/shape.ts | 138 +++++++++++ .../test/__snapshots__/context.test.ts.snap | 2 +- .../test/__snapshots__/visitor.test.ts.snap | 2 +- common/test/context.test.ts | 34 +-- common/test/inspect.test.ts | 8 +- common/test/visitor.test.ts | 4 +- common/util.ts | 30 +-- dprint.json | 5 + examples/arrays.eg.ts | 14 +- examples/assertions.eg.ts | 6 +- examples/collections.eg.ts | 8 +- examples/metadata.eg.ts | 14 +- examples/objects.eg.ts | 6 +- examples/primitives.eg.ts | 48 ++-- examples/recursive.eg.ts | 4 +- examples/unions.eg.ts | 18 +- fixtures.rs | 38 +-- mod.ts | 2 +- scale.ts | 52 ++++ {codecs => shapes}/array.ts | 80 +++--- shapes/bench/array.bench.ts | 18 ++ shapes/bench/bool.bench.ts | 4 + shapes/bench/compact.bench.ts | 15 ++ {codecs => shapes}/bench/hex.bench.ts | 6 +- shapes/bench/int.bench.ts | 14 ++ {codecs => shapes}/bench/object.bench.ts | 16 +- shapes/bench/option.bench.ts | 8 + shapes/bench/str.bench.ts | 19 ++ shapes/bench/tuple.bench.ts | 10 + {codecs => shapes}/bitSequence.ts | 18 +- shapes/bool.ts | 15 ++ {codecs => shapes}/collections.ts | 34 +-- {codecs => shapes}/compact.ts | 86 +++---- {codecs => shapes}/constant.ts | 24 +- shapes/deferred.ts | 28 +++ shapes/documented.ts | 8 + {codecs => shapes}/fixtures/array.rs | 0 {codecs => shapes}/fixtures/bitSequence.rs | 0 {codecs => shapes}/fixtures/bool.rs | 0 {codecs => shapes}/fixtures/collections.rs | 0 {codecs => shapes}/fixtures/compact.rs | 0 {codecs => shapes}/fixtures/deferred.rs | 0 {codecs => shapes}/fixtures/float.rs | 0 {codecs => shapes}/fixtures/hex.rs | 0 {codecs => shapes}/fixtures/int.rs | 0 {codecs => shapes}/fixtures/iterable.rs | 0 {codecs => shapes}/fixtures/lenPrefixed.rs | 0 {codecs => shapes}/fixtures/never.rs | 0 {codecs => shapes}/fixtures/object.rs | 0 {codecs => shapes}/fixtures/option.rs | 0 {codecs => shapes}/fixtures/optionBool.rs | 0 {codecs => shapes}/fixtures/result.rs | 0 {codecs => shapes}/fixtures/str.rs | 0 {codecs => shapes}/fixtures/tuple.rs | 0 {codecs => shapes}/fixtures/union.rs | 0 {codecs => shapes}/float.ts | 14 +- {codecs => shapes}/hex.ts | 6 +- shapes/instance.ts | 23 ++ {codecs => shapes}/int.ts | 52 ++-- {codecs => shapes}/iterable.ts | 46 ++-- shapes/lenPrefixed.ts | 30 +++ {codecs => shapes}/mod.ts | 0 shapes/never.ts | 15 ++ {codecs => shapes}/object.ts | 104 ++++---- shapes/option.ts | 37 +++ {codecs => shapes}/optionBool.ts | 14 +- shapes/promise.ts | 19 ++ {codecs => shapes}/record.ts | 4 +- shapes/result.ts | 49 ++++ {codecs => shapes}/str.ts | 20 +- .../test/__snapshots__/array.test.ts.snap | 40 +-- .../__snapshots__/bitSequence.test.ts.snap | 12 +- shapes/test/__snapshots__/bool.test.ts.snap | 15 ++ .../__snapshots__/collections.test.ts.snap | 90 +++++++ .../test/__snapshots__/compact.test.ts.snap | 40 +-- .../test/__snapshots__/constant.test.ts.snap | 4 +- .../test/__snapshots__/deferred.test.ts.snap | 8 +- .../test/__snapshots__/float.test.ts.snap | 6 +- .../test/__snapshots__/hex.test.ts.snap | 30 +-- .../test/__snapshots__/instance.test.ts.snap | 12 +- .../test/__snapshots__/int.test.ts.snap | 234 +++++++++--------- .../test/__snapshots__/iterable.test.ts.snap | 0 .../__snapshots__/lenPrefixed.test.ts.snap | 6 +- shapes/test/__snapshots__/never.test.ts.snap | 11 + shapes/test/__snapshots__/object.test.ts.snap | 164 ++++++++++++ .../test/__snapshots__/option.test.ts.snap | 2 +- .../__snapshots__/optionBool.test.ts.snap | 2 +- .../test/__snapshots__/promise.test.ts.snap | 2 +- .../test/__snapshots__/record.test.ts.snap | 10 +- .../test/__snapshots__/result.test.ts.snap | 8 +- .../test/__snapshots__/str.test.ts.snap | 35 ++- .../test/__snapshots__/transform.test.ts.snap | 2 +- .../test/__snapshots__/tuple.test.ts.snap | 14 +- .../test/__snapshots__/union.test.ts.snap | 18 +- {codecs => shapes}/test/array.test.ts | 14 +- {codecs => shapes}/test/bitSequence.test.ts | 4 +- {codecs => shapes}/test/bool.test.ts | 4 +- {codecs => shapes}/test/collections.test.ts | 24 +- {codecs => shapes}/test/compact.test.ts | 12 +- {codecs => shapes}/test/constant.test.ts | 4 +- {codecs => shapes}/test/deferred.test.ts | 6 +- {codecs => shapes}/test/float.test.ts | 4 +- {codecs => shapes}/test/hex.test.ts | 8 +- {codecs => shapes}/test/instance.test.ts | 4 +- {codecs => shapes}/test/int.test.ts | 28 +-- {codecs => shapes}/test/iterable.test.ts | 4 +- {codecs => shapes}/test/lenPrefixed.test.ts | 8 +- {codecs => shapes}/test/never.test.ts | 4 +- {codecs => shapes}/test/object.test.ts | 6 +- {codecs => shapes}/test/option.test.ts | 14 +- shapes/test/optionBool.test.ts | 6 + {codecs => shapes}/test/promise.test.ts | 8 +- {codecs => shapes}/test/record.test.ts | 6 +- {codecs => shapes}/test/result.test.ts | 6 +- {codecs => shapes}/test/str.test.ts | 4 +- {codecs => shapes}/test/transform.test.ts | 4 +- {codecs => shapes}/test/tuple.test.ts | 6 +- {codecs => shapes}/test/union.test.ts | 8 +- shapes/transform.ts | 25 ++ shapes/tuple.ts | 34 +++ {codecs => shapes}/union.ts | 58 ++--- test-util.ts | 36 +-- words.txt | 3 +- 152 files changed, 1767 insertions(+), 1703 deletions(-) delete mode 100644 codecs/bench/array.bench.ts delete mode 100644 codecs/bench/bool.bench.ts delete mode 100644 codecs/bench/compact.bench.ts delete mode 100644 codecs/bench/int.bench.ts delete mode 100644 codecs/bench/option.bench.ts delete mode 100644 codecs/bench/str.bench.ts delete mode 100644 codecs/bench/tuple.bench.ts delete mode 100644 codecs/bool.ts delete mode 100644 codecs/deferred.ts delete mode 100644 codecs/documented.ts delete mode 100644 codecs/instance.ts delete mode 100644 codecs/lenPrefixed.ts delete mode 100644 codecs/never.ts delete mode 100644 codecs/option.ts delete mode 100644 codecs/promise.ts delete mode 100644 codecs/result.ts delete mode 100644 codecs/test/__snapshots__/bool.test.ts.snap delete mode 100644 codecs/test/__snapshots__/collections.test.ts.snap delete mode 100644 codecs/test/__snapshots__/never.test.ts.snap delete mode 100644 codecs/test/__snapshots__/object.test.ts.snap delete mode 100644 codecs/test/optionBool.test.ts delete mode 100644 codecs/transform.ts delete mode 100644 codecs/tuple.ts delete mode 100644 common/codec.ts create mode 100644 common/shape.ts create mode 100644 scale.ts rename {codecs => shapes}/array.ts (51%) create mode 100644 shapes/bench/array.bench.ts create mode 100644 shapes/bench/bool.bench.ts create mode 100644 shapes/bench/compact.bench.ts rename {codecs => shapes}/bench/hex.bench.ts (81%) create mode 100644 shapes/bench/int.bench.ts rename {codecs => shapes}/bench/object.bench.ts (73%) create mode 100644 shapes/bench/option.bench.ts create mode 100644 shapes/bench/str.bench.ts create mode 100644 shapes/bench/tuple.bench.ts rename {codecs => shapes}/bitSequence.ts (84%) create mode 100644 shapes/bool.ts rename {codecs => shapes}/collections.ts (81%) rename {codecs => shapes}/compact.ts (55%) rename {codecs => shapes}/constant.ts (54%) create mode 100644 shapes/deferred.ts create mode 100644 shapes/documented.ts rename {codecs => shapes}/fixtures/array.rs (100%) rename {codecs => shapes}/fixtures/bitSequence.rs (100%) rename {codecs => shapes}/fixtures/bool.rs (100%) rename {codecs => shapes}/fixtures/collections.rs (100%) rename {codecs => shapes}/fixtures/compact.rs (100%) rename {codecs => shapes}/fixtures/deferred.rs (100%) rename {codecs => shapes}/fixtures/float.rs (100%) rename {codecs => shapes}/fixtures/hex.rs (100%) rename {codecs => shapes}/fixtures/int.rs (100%) rename {codecs => shapes}/fixtures/iterable.rs (100%) rename {codecs => shapes}/fixtures/lenPrefixed.rs (100%) rename {codecs => shapes}/fixtures/never.rs (100%) rename {codecs => shapes}/fixtures/object.rs (100%) rename {codecs => shapes}/fixtures/option.rs (100%) rename {codecs => shapes}/fixtures/optionBool.rs (100%) rename {codecs => shapes}/fixtures/result.rs (100%) rename {codecs => shapes}/fixtures/str.rs (100%) rename {codecs => shapes}/fixtures/tuple.rs (100%) rename {codecs => shapes}/fixtures/union.rs (100%) rename {codecs => shapes}/float.ts (51%) rename {codecs => shapes}/hex.ts (87%) create mode 100644 shapes/instance.ts rename {codecs => shapes}/int.ts (72%) rename {codecs => shapes}/iterable.ts (54%) create mode 100644 shapes/lenPrefixed.ts rename {codecs => shapes}/mod.ts (100%) create mode 100644 shapes/never.ts rename {codecs => shapes}/object.ts (53%) create mode 100644 shapes/option.ts rename {codecs => shapes}/optionBool.ts (50%) create mode 100644 shapes/promise.ts rename {codecs => shapes}/record.ts (74%) create mode 100644 shapes/result.ts rename {codecs => shapes}/str.ts (57%) rename {codecs => shapes}/test/__snapshots__/array.test.ts.snap (60%) rename {codecs => shapes}/test/__snapshots__/bitSequence.test.ts.snap (53%) create mode 100644 shapes/test/__snapshots__/bool.test.ts.snap create mode 100644 shapes/test/__snapshots__/collections.test.ts.snap rename {codecs => shapes}/test/__snapshots__/compact.test.ts.snap (68%) rename {codecs => shapes}/test/__snapshots__/constant.test.ts.snap (66%) rename {codecs => shapes}/test/__snapshots__/deferred.test.ts.snap (83%) rename {codecs => shapes}/test/__snapshots__/float.test.ts.snap (82%) rename {codecs => shapes}/test/__snapshots__/hex.test.ts.snap (96%) rename {codecs => shapes}/test/__snapshots__/instance.test.ts.snap (63%) rename {codecs => shapes}/test/__snapshots__/int.test.ts.snap (52%) rename {codecs => shapes}/test/__snapshots__/iterable.test.ts.snap (100%) rename {codecs => shapes}/test/__snapshots__/lenPrefixed.test.ts.snap (98%) create mode 100644 shapes/test/__snapshots__/never.test.ts.snap create mode 100644 shapes/test/__snapshots__/object.test.ts.snap rename {codecs => shapes}/test/__snapshots__/option.test.ts.snap (90%) rename {codecs => shapes}/test/__snapshots__/optionBool.test.ts.snap (74%) rename {codecs => shapes}/test/__snapshots__/promise.test.ts.snap (98%) rename {codecs => shapes}/test/__snapshots__/record.test.ts.snap (86%) rename {codecs => shapes}/test/__snapshots__/result.test.ts.snap (80%) rename {codecs => shapes}/test/__snapshots__/str.test.ts.snap (98%) rename {codecs => shapes}/test/__snapshots__/transform.test.ts.snap (68%) rename {codecs => shapes}/test/__snapshots__/tuple.test.ts.snap (78%) rename {codecs => shapes}/test/__snapshots__/union.test.ts.snap (71%) rename {codecs => shapes}/test/array.test.ts (71%) rename {codecs => shapes}/test/bitSequence.test.ts (87%) rename {codecs => shapes}/test/bool.test.ts (52%) rename {codecs => shapes}/test/collections.test.ts (70%) rename {codecs => shapes}/test/compact.test.ts (61%) rename {codecs => shapes}/test/constant.test.ts (75%) rename {codecs => shapes}/test/deferred.test.ts (77%) rename {codecs => shapes}/test/float.test.ts (75%) rename {codecs => shapes}/test/hex.test.ts (82%) rename {codecs => shapes}/test/instance.test.ts (92%) rename {codecs => shapes}/test/int.test.ts (66%) rename {codecs => shapes}/test/iterable.test.ts (79%) rename {codecs => shapes}/test/lenPrefixed.test.ts (55%) rename {codecs => shapes}/test/never.test.ts (51%) rename {codecs => shapes}/test/object.test.ts (91%) rename {codecs => shapes}/test/option.test.ts (56%) create mode 100644 shapes/test/optionBool.test.ts rename {codecs => shapes}/test/promise.test.ts (73%) rename {codecs => shapes}/test/record.test.ts (73%) rename {codecs => shapes}/test/result.test.ts (86%) rename {codecs => shapes}/test/str.test.ts (77%) rename {codecs => shapes}/test/transform.test.ts (71%) rename {codecs => shapes}/test/tuple.test.ts (50%) rename {codecs => shapes}/test/union.test.ts (87%) create mode 100644 shapes/transform.ts create mode 100644 shapes/tuple.ts rename {codecs => shapes}/union.ts (63%) diff --git a/Readme.md b/Readme.md index 8c92899..c74413e 100644 --- a/Readme.md +++ b/Readme.md @@ -1,112 +1,119 @@ -# SCALE Codecs for JavaScript and TypeScript +# subShape  composable shapes for cohesive code -A TypeScript implementation of [SCALE (Simple Concatenated Aggregate Little-Endian) transcoding](https://docs.substrate.io/reference/scale-codec/) (see [Rust implementation here](https://github.com/paritytech/parity-scale-codec)), which emphasizes JS-land representations and e2e type-safety. **Faster than `JSON`, `avsc`, `jsbin` and `protobuf`** ([see benchmarks](https://github.com/paritytech/scale-ts-benchmark)). +> ### *one shape can do them all; one shape defined them* + +subShape provides primitives and patterns for crafting composable shapes +featuring cohesive typing, validation, serialization, and reflection. ## Setup -If you're using [Deno](https://deno.land/), simply import via the `deno.land/x` specifier. +### Deno ```ts -import * as $ from "https://deno.land/x/scale/mod.ts" +import * as $ from "https://deno.land/x/subshape/mod.ts" ``` -If you're using [Node](https://nodejs.org/), install as follows. +### Node ``` -npm install scale-codec +npm install subshape ``` -Then import as follows. - ```ts -import * as $ from "scale-codec" +import * as $ from "subshape" ``` -## Usage +## Demo -1. Import the library -2. Define a codec via the library's functions, whose names correspond to types -3. Utilize the codec you've defined - -## Example +### Craft a Composable Shape ```ts -import * as $ from "https://deno.land/x/scale/mod.ts" +import * as $ from "https://deno.land/x/subshape/mod.ts" const $superhero = $.object( $.field("pseudonym", $.str), $.optionalField("secretIdentity", $.str), $.field("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 type from a given codec, you can use the `Output` utility type. +### And Get... + +#### Typing ```ts type Superhero = $.Output -// { +// type 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. +#### Validation ```ts -interface Superhero { - pseudonym: string - secretIdentity?: string - superpowers: string[] +const spiderMan = { + pseudonym: "Spider-Man", + secretIdentity: "Peter Parker", + superpowers: ["does whatever a spider can"], } -const $superhero: Codec = $.object( - $.field("pseudonym", $.str), - $.optionalField("secretIdentity", $.str), - $.field("superpowers", $.array($.str)), -) +$.assert($superhero, spiderMan) // ok! -// @ts-expect-error -// Type 'Codec<{ pseudonym: string; secretIdentity?: string | undefined; }>' is not assignable to type 'Codec'. -// 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 = $.object( - $.field("pseudonym", $.str), - $.optionalField("secretIdentity", $.str), -) +const bob = { + pseudonym: "Bob", + secretIdentity: "Robert", + superpowers: null, +} + +$.assert($superhero, bob) // ShapeAssertError: !(value.superpowers instanceof Array) ``` -You can also validate a value against a codec using `$.assert` or `$.is`: +#### Serialization ```ts -value // unknown -if ($.is($superhero, value)) { - value // Superhero -} +const encoded = $superhero.encode(spiderMan) +// encoded: Uint8Array + +const decoded = $superhero.decode(spiderMan) +// decoded: Superhero + +console.log(decoded) +// Prints: +// { +// pseudonym: "Spider-Man", +// secretIdentity: "Peter Parker", +// superpowers: [ "does whatever a spider can" ] +// } +``` + +#### Reflection -value // unknown -$.assert($superhero, value) -value // Superhero +```ts +$superhero.metadata // Metadata + +console.log($superhero) +// Prints: +// $.object( +// $.field("pseudonym", $.str), +// $.optionalField("secretIdentity", $.str), +// $.field("superpowers", $.array($.str)) +// ) ``` -If `$.assert` fails, it will throw a `ScaleAssertError` detailing why the value was invalid. +## Examples -Further examples can be found in the [`examples`](https://github.com/paritytech/scale-ts/tree/main/examples) directory. +Further examples can be found in the +[`examples`](https://github.com/paritytech/scale-ts/tree/main/examples) +directory. -## Codec Naming +## Shape Naming Convention -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: +This library adopts a convention of denoting shapes with a `$` – `$.foo` for +built-in shapes, and `$foo` for user-defined shapes. This makes shapes easily +distinguishable from other values, and makes it easier to have shapes in scope +with other variables: ```ts interface Person { ... } @@ -114,33 +121,42 @@ 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`. +Here, the type, shape, and a value can all coexist without clashing, without +having to resort to wordy workarounds like `personShape`. -The main other library this could possibly clash with is jQuery, and its usage has waned enough that this is not a serious problem. +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. +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 `subshape.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`. You can call `.encodeAsync()` on any codec; if it is a synchronous codec, it will simply resolve immediately. +Some shapes require asynchronous encoding. Calling `.encode()` on a shape will +throw if it or another shape it calls is asynchronous. In this case, you must +call `.encodeAsync()` instead, which returns a `Promise`. You can +call `.encodeAsync()` on any shape; if it is a synchronous shape, it will simply +resolve immediately. Asynchronous decoding is not supported. -## Custom Codecs +## Custom Shapes -If your encoding/decoding logic is more complicated, you can create custom codecs with `createCodec`: +If your encoding/decoding logic is more complicated, you can create custom +shapes with `createShape`: ```ts -const $foo = $.createCodec({ - _metadata: $.metadata("$foo"), +const $foo = $.createShape({ + 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) { + staticSize: 123, + subEncode(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`. + // 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)`. @@ -149,35 +165,35 @@ const $foo = $.createCodec({ // 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`. + // You can delegate to another shape by calling `$bar.subEncode(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 `subEncode` and not `encode`. // See the `EncodeBuffer` class for information on other methods. // ... }, - _decode(buffer) { + subDecode(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`. + // You can delegate to another shape by calling `$bar.subDecode(buffer)`. + // Note that you should use `subDecode` and not `decode`. // ... return value }, - _assert(assert) { - // Validate that `assert.value` is valid for this codec. + subAssert(assert) { + // Validate that `assert.value` is valid for this shape. // `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`. + // You can delegate to another shape by calling `$bar.subAssert(assert)` or `$bar.subAssert(assert.access("key"))`. + // Any errors thrown should be an instance of `$.ShapeAssertError`, and should use `assert.path`. // ... }, diff --git a/_tasks/build_npm_pkg.ts b/_tasks/build_npm_pkg.ts index ef889dc..b757718 100644 --- a/_tasks/build_npm_pkg.ts +++ b/_tasks/build_npm_pkg.ts @@ -3,17 +3,18 @@ import { build } from "https://deno.land/x/dnt@0.33.0/mod.ts" await emptyDir("target/npm_pkg") -const DESCRIPTION = "A TypeScript Reference Implementation of SCALE Transcoding" +const description = + "subShape provides primitives and patterns for crafting composable shapes featuring cohesive typing, validation, serialization, and reflection." await build({ entryPoints: ["mod.ts"], outDir: "target/npm_pkg", package: { - name: "scale-codec", + name: "subshape", version: Deno.args[0]!, - description: DESCRIPTION, + description, sideEffects: false, - repository: "github:paritytech/scale-ts", + repository: "github:paritytech/subshape", }, shims: { deno: { diff --git a/codecs/bench/array.bench.ts b/codecs/bench/array.bench.ts deleted file mode 100644 index bed8822..0000000 --- a/codecs/bench/array.bench.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as $ from "../../mod.ts" -import { benchCodec } from "../../test-util.ts" - -function arr(length: number, el: (i: number) => T): T[] { - return Array.from({ length }, (_, i) => el(i)) -} - -benchCodec("bool[0]", $.array($.bool), []) -benchCodec("u128[0]", $.array($.u128), []) -benchCodec("compactU256[0]", $.array($.compact($.u256)), []) - -benchCodec("bool[128]", $.array($.bool), arr(128, (i) => i % 2 === 0)) -benchCodec("u128[128]", $.array($.u128), arr(128, (i) => 2n ** BigInt(i % 100) + BigInt(i))) -benchCodec("compactU256[128]", $.array($.compact($.u256)), arr(128, (i) => 2n ** BigInt(i % 100) + BigInt(i))) - -benchCodec("bool[16384]", $.array($.bool), arr(16384, (i) => i % 2 === 0)) -benchCodec("u128[16384]", $.array($.u128), arr(16384, (i) => 2n ** BigInt(i % 100) + BigInt(i))) -benchCodec("compactU256[16384]", $.array($.compact($.u256)), arr(16384, (i) => 2n ** BigInt(i % 100) + BigInt(i))) diff --git a/codecs/bench/bool.bench.ts b/codecs/bench/bool.bench.ts deleted file mode 100644 index 2fee942..0000000 --- a/codecs/bench/bool.bench.ts +++ /dev/null @@ -1,4 +0,0 @@ -import * as $ from "../../mod.ts" -import { benchCodec } from "../../test-util.ts" - -benchCodec("bool", $.bool, true) diff --git a/codecs/bench/compact.bench.ts b/codecs/bench/compact.bench.ts deleted file mode 100644 index 382d578..0000000 --- a/codecs/bench/compact.bench.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as $ from "../../mod.ts" -import { benchCodec } from "../../test-util.ts" - -benchCodec("compactU32 (6)", $.compact($.u32), 2 ** 6 - 1) -benchCodec("compactU32 (14)", $.compact($.u32), 2 ** 14 - 1) -benchCodec("compactU32 (30)", $.compact($.u32), 2 ** 30 - 1) -benchCodec("compactU32 (32)", $.compact($.u32), 2 ** 32 - 1) - -benchCodec("compactU256 (6)", $.compact($.u256), 2n ** 6n - 1n) -benchCodec("compactU256 (14)", $.compact($.u256), 2n ** 14n - 1n) -benchCodec("compactU256 (30)", $.compact($.u256), 2n ** 30n - 1n) -benchCodec("compactU256 (32)", $.compact($.u256), 2n ** 32n - 1n) -benchCodec("compactU256 (64)", $.compact($.u256), 2n ** 64n - 1n) -benchCodec("compactU256 (128)", $.compact($.u256), 2n ** 128n - 1n) -benchCodec("compactU256 (256)", $.compact($.u256), 2n ** 256n - 1n) diff --git a/codecs/bench/int.bench.ts b/codecs/bench/int.bench.ts deleted file mode 100644 index 40cbfaa..0000000 --- a/codecs/bench/int.bench.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as $ from "../../mod.ts" -import { benchCodec } from "../../test-util.ts" - -benchCodec("u8", $.u8, 123) -benchCodec("u16", $.u16, 123) -benchCodec("u32", $.u32, 123) -benchCodec("u64", $.u64, 123n) -benchCodec("u128", $.u128, 123n) - -benchCodec("i8", $.i8, 123) -benchCodec("i16", $.i16, 123) -benchCodec("i32", $.i32, 123) -benchCodec("i64", $.i64, 123n) -benchCodec("i128", $.i128, 123n) diff --git a/codecs/bench/option.bench.ts b/codecs/bench/option.bench.ts deleted file mode 100644 index 987d165..0000000 --- a/codecs/bench/option.bench.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as $ from "../../mod.ts" -import { benchCodec } from "../../test-util.ts" - -benchCodec("None", $.option($.bool), undefined) -benchCodec("None", $.option($.u128), undefined) - -benchCodec("Some", $.option($.bool), true) -benchCodec("Some", $.option($.u128), 12345678901234567890n) diff --git a/codecs/bench/str.bench.ts b/codecs/bench/str.bench.ts deleted file mode 100644 index 7141fa6..0000000 --- a/codecs/bench/str.bench.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as $ from "../../mod.ts" -import { benchCodec, files } from "../../test-util.ts" - -const trolleybus = "🚎" -const special = "œ∑鮆¥üîøπåß∂ƒ©˙∆˚¬Ω≈ç√∫ñµ" -const lipsum = await files.lipsum() -const cargoLock = await files.cargoLock() - -benchCodec(`""`, $.str, "") -benchCodec(`"abc"`, $.str, "abc") -benchCodec(`trolleybus`, $.str, trolleybus) -benchCodec(`special`, $.str, special) -benchCodec(`lipsum`, $.str, lipsum) -benchCodec(`cargoLock`, $.str, cargoLock) -benchCodec(`"abc" * 1000`, $.str, "abc".repeat(1000)) -benchCodec(`trolleybus * 1000`, $.str, trolleybus.repeat(1000)) -benchCodec(`special * 1000`, $.str, special.repeat(1000)) -benchCodec(`lipsum * 1000`, $.str, lipsum.repeat(1000)) -benchCodec(`cargoLock * 1000`, $.str, cargoLock.repeat(1000)) diff --git a/codecs/bench/tuple.bench.ts b/codecs/bench/tuple.bench.ts deleted file mode 100644 index 339b3fc..0000000 --- a/codecs/bench/tuple.bench.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as $ from "../../mod.ts" -import { benchCodec } from "../../test-util.ts" - -// for comparison -benchCodec("u128", $.u128, 123n) - -benchCodec("[]", $.tuple(), []) -benchCodec("[u128]", $.tuple($.u128), [123n]) -benchCodec("[u128, u128]", $.tuple($.u128, $.u128), [123n, 456n]) -benchCodec("[u128, u128, u128]", $.tuple($.u128, $.u128, $.u128), [123n, 456n, 789n]) diff --git a/codecs/bool.ts b/codecs/bool.ts deleted file mode 100644 index 6bcd98d..0000000 --- a/codecs/bool.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Codec, createCodec, metadata } from "../common/mod.ts" - -export const bool: Codec = createCodec({ - _metadata: metadata("$.bool"), - _staticSize: 1, - _encode(buffer, value) { - buffer.array[buffer.index++] = +value - }, - _decode(buffer) { - return !!buffer.array[buffer.index++]! - }, - _assert(assert) { - assert.typeof(this, "boolean") - }, -}) diff --git a/codecs/deferred.ts b/codecs/deferred.ts deleted file mode 100644 index 0434818..0000000 --- a/codecs/deferred.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Codec, createCodec, metadata } from "../common/mod.ts" - -export function deferred(getCodec: () => Codec): Codec { - let $codec: Codec - const codec = createCodec({ - _metadata: metadata("$.deferred", deferred, getCodec), - _staticSize: 0, - _encode(buffer, value) { - $codec ??= getCodec() - buffer.pushAlloc($codec._staticSize) - $codec._encode(buffer, value) - buffer.popAlloc() - }, - _decode(buffer) { - $codec ??= getCodec() - return $codec._decode(buffer) - }, - _assert(assert) { - $codec ??= getCodec() - $codec._assert(assert) - }, - }) - codec["_inspect"] = (inspect) => { - // Use ._inspect manually so that Deno doesn't detect the circularity - return `$.deferred(() => ${getCodec()["_inspect"]!(inspect)})` - } - return codec -} diff --git a/codecs/documented.ts b/codecs/documented.ts deleted file mode 100644 index 724a0b6..0000000 --- a/codecs/documented.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Codec, metadata, withMetadata } from "../common/mod.ts" - -export function documented(docs: string, inner: Codec): Codec { - return withMetadata( - metadata("$.documented", documented, docs, inner) as never, - inner, - ) -} diff --git a/codecs/instance.ts b/codecs/instance.ts deleted file mode 100644 index 6675ccc..0000000 --- a/codecs/instance.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AssertState } from "../common/assert.ts" -import { Codec, createCodec, metadata } from "../common/mod.ts" - -export function instance( - ctor: new(...args: AO) => O, - $args: Codec, - toArgs: (value: I) => [...AI], -): Codec { - return createCodec({ - _metadata: metadata("$.instance", instance, ctor, $args, toArgs), - _staticSize: $args._staticSize, - _encode(buffer, value) { - $args._encode(buffer, toArgs(value)) - }, - _decode(buffer) { - return new ctor(...$args._decode(buffer)) - }, - _assert(assert) { - assert.instanceof(this, ctor) - $args._assert(new AssertState(toArgs(assert.value as I), "#arguments", assert)) - }, - }) -} diff --git a/codecs/lenPrefixed.ts b/codecs/lenPrefixed.ts deleted file mode 100644 index 105c8bb..0000000 --- a/codecs/lenPrefixed.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Codec, createCodec, metadata } from "../common/mod.ts" -import { compact } from "./compact.ts" -import { u32 } from "./int.ts" - -const compactU32 = compact(u32) - -export function lenPrefixed($inner: Codec): Codec { - return createCodec({ - _metadata: metadata("$.lenPrefixed", lenPrefixed, $inner), - _staticSize: compactU32._staticSize + $inner._staticSize, - _encode(buffer, extrinsic) { - const lengthCursor = buffer.createCursor(compactU32._staticSize) - const contentCursor = buffer.createCursor($inner._staticSize) - $inner._encode(contentCursor, extrinsic) - buffer.waitForBuffer(contentCursor, () => { - const length = contentCursor.finishedSize + contentCursor.index - compactU32._encode(lengthCursor, length) - lengthCursor.close() - contentCursor.close() - }) - }, - _decode(buffer) { - const length = compactU32._decode(buffer) - return $inner.decode(buffer.array.subarray(buffer.index, buffer.index += length)) - }, - _assert(assert) { - $inner._assert(assert) - }, - }) -} diff --git a/codecs/never.ts b/codecs/never.ts deleted file mode 100644 index 7fb66f2..0000000 --- a/codecs/never.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Codec, createCodec, metadata, ScaleAssertError, ScaleDecodeError, ScaleEncodeError } from "../common/mod.ts" - -export const never: Codec = createCodec({ - _metadata: metadata("$.never"), - _staticSize: 0, - _encode(value) { - throw new ScaleEncodeError(this, value, "Cannot encode $.never") - }, - _decode(buffer) { - throw new ScaleDecodeError(this, buffer, "Cannot decode $.never") - }, - _assert(assert) { - throw new ScaleAssertError(this, assert.value, `${assert.path}: Cannot validate $.never`) - }, -}) diff --git a/codecs/option.ts b/codecs/option.ts deleted file mode 100644 index ac6b4b0..0000000 --- a/codecs/option.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Codec, createCodec, metadata, ScaleDecodeError } from "../common/mod.ts" - -export function option($some: Codec): Codec -export function option($some: Codec, none: N): Codec -export function option($some: Codec, none?: N): Codec { - if ($some._metadata.some((x) => x.factory === option && x.args[1] === none)) { - throw new Error("Nested option codec will not roundtrip correctly") - } - return createCodec({ - _metadata: metadata("$.option", option, $some, ...(none === undefined ? [] : [none!]) as [N]), - _staticSize: 1 + $some._staticSize, - _encode(buffer, value) { - if ((buffer.array[buffer.index++] = +(value !== none))) { - $some._encode(buffer, value as SI) - } - }, - _decode(buffer) { - switch (buffer.array[buffer.index++]) { - case 0: - return none as N - case 1: { - const value = $some._decode(buffer) - if (value === none) { - throw new ScaleDecodeError(this, buffer, "Some(None) will not roundtrip correctly") - } - return value - } - default: - throw new ScaleDecodeError(this, buffer, "Option discriminant neither 0 nor 1") - } - }, - _assert(assert) { - if (assert.value === none) return - $some._assert(assert) - }, - }) -} diff --git a/codecs/promise.ts b/codecs/promise.ts deleted file mode 100644 index 24b7a1e..0000000 --- a/codecs/promise.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Codec, createCodec, metadata } from "../common/mod.ts" - -export function promise($value: Codec): Codec, Promise> { - return createCodec({ - _metadata: metadata("$.promise", promise, $value), - _staticSize: $value._staticSize, - _encode(buffer, value) { - buffer.writeAsync($value._staticSize, async (buffer) => { - $value._encode(buffer, await value) - }) - }, - _decode(buffer) { - return Promise.resolve($value._decode(buffer)) - }, - _assert(assert) { - assert.instanceof(this, Promise) - }, - }) -} diff --git a/codecs/result.ts b/codecs/result.ts deleted file mode 100644 index cecf580..0000000 --- a/codecs/result.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Codec, createCodec, metadata, ScaleDecodeError } from "../common/mod.ts" - -export function result( - $ok: Codec, - $err: Codec, -): Codec { - if ($ok._metadata.some((x) => x.factory === result)) { - throw new Error("Nested result codec will not roundtrip correctly") - } - return createCodec({ - _metadata: metadata("$.result", result, $ok, $err), - _staticSize: 1 + Math.max($ok._staticSize, $err._staticSize), - _encode(buffer, value) { - if ((buffer.array[buffer.index++] = +(value instanceof Error))) { - $err._encode(buffer, value as UI) - } else { - $ok._encode(buffer, value as TI) - } - }, - _decode(buffer) { - switch (buffer.array[buffer.index++]) { - case 0: { - const value = $ok._decode(buffer) - if (value instanceof Error) { - throw new ScaleDecodeError( - this, - buffer, - "An ok value that is instanceof Error will not roundtrip correctly", - ) - } - return value - } - case 1: { - return $err._decode(buffer) - } - default: { - throw new ScaleDecodeError(this, buffer, "Result discriminant neither 0 nor 1") - } - } - }, - _assert(assert) { - if (assert.value instanceof Error) { - $err._assert(assert) - } else { - $ok._assert(assert) - } - }, - }) -} diff --git a/codecs/test/__snapshots__/bool.test.ts.snap b/codecs/test/__snapshots__/bool.test.ts.snap deleted file mode 100644 index 84bde66..0000000 --- a/codecs/test/__snapshots__/bool.test.ts.snap +++ /dev/null @@ -1,15 +0,0 @@ -export const snapshot = {}; - -snapshot[`\$.bool true 1`] = `01`; - -snapshot[`\$.bool false 1`] = `00`; - -snapshot[`\$.bool invalid null 1`] = `ScaleAssertError: typeof value !== "boolean"`; - -snapshot[`\$.bool invalid undefined 1`] = `ScaleAssertError: typeof value !== "boolean"`; - -snapshot[`\$.bool invalid 123 1`] = `ScaleAssertError: typeof value !== "boolean"`; - -snapshot[`\$.bool invalid [] 1`] = `ScaleAssertError: typeof value !== "boolean"`; - -snapshot[`\$.bool invalid {} 1`] = `ScaleAssertError: typeof value !== "boolean"`; diff --git a/codecs/test/__snapshots__/collections.test.ts.snap b/codecs/test/__snapshots__/collections.test.ts.snap deleted file mode 100644 index f828686..0000000 --- a/codecs/test/__snapshots__/collections.test.ts.snap +++ /dev/null @@ -1,90 +0,0 @@ -export const snapshot = {}; - -snapshot[`\$.set(\$.u8) ScaleSet { "\$value": \$.u8 } 1`] = `00`; - -snapshot[`\$.set(\$.u8) ScaleSet { "\$value": \$.u8 } 2`] = ` -10 -00 -02 -04 -08 -`; - -snapshot[`\$.set(\$.u8) ScaleSet { "\$value": \$.u8 } 3`] = ` -10 -02 -03 -05 -07 -`; - -snapshot[`\$.set(\$.u8) invalid null 1`] = `ScaleAssertError: !(value instanceof ScaleSet)`; - -snapshot[`\$.set(\$.u8) invalid undefined 1`] = `ScaleAssertError: !(value instanceof ScaleSet)`; - -snapshot[`\$.set(\$.u8) invalid 123 1`] = `ScaleAssertError: !(value instanceof ScaleSet)`; - -snapshot[`\$.set(\$.u8) invalid [ 123 ] 1`] = `ScaleAssertError: !(value instanceof ScaleSet)`; - -snapshot[`\$.set(\$.u8) invalid Set(1) { null } 1`] = `ScaleAssertError: !(value instanceof ScaleSet)`; - -snapshot[`\$.set(\$.u8) invalid Set(5) { 1, 2, 3, -1, 4 } 1`] = `ScaleAssertError: !(value instanceof ScaleSet)`; - -snapshot[`\$.set(\$.u8) invalid ScaleSet { "\$value": \$.i8 } 1`] = `ScaleAssertError: #iterator[3] < 0`; - -snapshot[`\$.map(\$.str, \$.u8) ScaleMap { "\$key": \$.str } 1`] = `00`; - -snapshot[`\$.map(\$.str, \$.u8) ScaleMap { "\$key": \$.str } 2`] = ` -08 -04 -30 -00 -04 -31 -01 -`; - -snapshot[`\$.map(\$.str, \$.u8) ScaleMap { "\$key": \$.str } 3`] = ` -14 -0c -32 -5e -30 -00 -0c -32 -5e -31 -02 -0c -32 -5e -32 -04 -0c -32 -5e -33 -08 -0c -32 -5e -34 -10 -`; - -snapshot[`\$.map(\$.str, \$.u8) invalid null 1`] = `ScaleAssertError: !(value instanceof ScaleMap)`; - -snapshot[`\$.map(\$.str, \$.u8) invalid undefined 1`] = `ScaleAssertError: !(value instanceof ScaleMap)`; - -snapshot[`\$.map(\$.str, \$.u8) invalid 123 1`] = `ScaleAssertError: !(value instanceof ScaleMap)`; - -snapshot[`\$.map(\$.str, \$.u8) invalid [ 123 ] 1`] = `ScaleAssertError: !(value instanceof ScaleMap)`; - -snapshot[`\$.map(\$.str, \$.u8) invalid [ [ "a", 1 ] ] 1`] = `ScaleAssertError: !(value instanceof ScaleMap)`; - -snapshot[`\$.map(\$.str, \$.u8) invalid Map(1) { "a" => null } 1`] = `ScaleAssertError: !(value instanceof ScaleMap)`; - -snapshot[`\$.map(\$.str, \$.u8) invalid Map(4) { "a" => 1, "b" => 2, "c" => -1, "d" => 0 } 1`] = `ScaleAssertError: !(value instanceof ScaleMap)`; - -snapshot[`\$.map(\$.str, \$.u8) invalid Map(4) { "a" => 1, "b" => 2, null => 3, "d" => 0 } 1`] = `ScaleAssertError: !(value instanceof ScaleMap)`; diff --git a/codecs/test/__snapshots__/never.test.ts.snap b/codecs/test/__snapshots__/never.test.ts.snap deleted file mode 100644 index af2e592..0000000 --- a/codecs/test/__snapshots__/never.test.ts.snap +++ /dev/null @@ -1,11 +0,0 @@ -export const snapshot = {}; - -snapshot[`\$.option(\$.never) undefined 1`] = `00`; - -snapshot[`\$.never invalid null 1`] = `ScaleAssertError: value: Cannot validate \$.never`; - -snapshot[`\$.never invalid 0 1`] = `ScaleAssertError: value: Cannot validate \$.never`; - -snapshot[`\$.option(\$.never) invalid null 1`] = `ScaleAssertError: value: Cannot validate \$.never`; - -snapshot[`\$.option(\$.never) invalid 0 1`] = `ScaleAssertError: value: Cannot validate \$.never`; diff --git a/codecs/test/__snapshots__/object.test.ts.snap b/codecs/test/__snapshots__/object.test.ts.snap deleted file mode 100644 index efca5f4..0000000 --- a/codecs/test/__snapshots__/object.test.ts.snap +++ /dev/null @@ -1,164 +0,0 @@ -export const snapshot = {}; - -snapshot[`\$person { name: "Darrel", nickName: "The Durst", superPower: "telekinesis", luckyNumber: 9 } 1`] = ` -18 -44 -61 -72 -72 -65 -6c -24 -54 -68 -65 -20 -44 -75 -72 -73 -74 -01 -2c -74 -65 -6c -65 -6b -69 -6e -65 -73 -69 -73 -09 -`; - -snapshot[`\$person { name: "Michael", nickName: "Mike", luckyNumber: 7 } 1`] = ` -1c -4d -69 -63 -68 -61 -65 -6c -10 -4d -69 -6b -65 -00 -07 -`; - -snapshot[`\$person invalid null 1`] = `ScaleAssertError: value == null`; - -snapshot[`\$person invalid undefined 1`] = `ScaleAssertError: typeof value !== "object"`; - -snapshot[`\$person invalid 123 1`] = `ScaleAssertError: typeof value !== "object"`; - -snapshot[`\$person invalid [Function (anonymous)] 1`] = `ScaleAssertError: typeof value !== "object"`; - -snapshot[`\$person invalid {} 1`] = `ScaleAssertError: !("name" in value)`; - -snapshot[`\$person invalid { name: "", nickName: "", superPower: 0, luckyNumber: 0 } 1`] = `ScaleAssertError: typeof value.superPower !== "string"`; - -snapshot[`\$person invalid { name: "", nickName: "", superPower: "", luckyNumber: -1 } 1`] = `ScaleAssertError: value.luckyNumber < 0`; - -snapshot[`\$person invalid { name: "", nickName: "", superPower: "", unluckyNumber: 0 } 1`] = `ScaleAssertError: !("luckyNumber" in value)`; - -snapshot[`\$.object( - \$.taggedUnion( - "_tag", - [ - Variant { tag: "a", codec: \$.object([]) }, - Variant { tag: "b", codec: \$.object(\$.field("x", \$.u8)) } - ] - ), - \$.field("bar", \$.u8) -) { _tag: "a", bar: 123 } 1`] = ` -00 -7b -`; - -snapshot[`\$.object( - \$.taggedUnion( - "_tag", - [ - Variant { tag: "a", codec: \$.object([]) }, - Variant { tag: "b", codec: \$.object(\$.field("x", \$.u8)) } - ] - ), - \$.field("bar", \$.u8) -) { _tag: "b", x: 0, bar: 123 } 1`] = ` -01 -00 -7b -`; - -snapshot[`\$.object( - \$.taggedUnion( - "_tag", - [ - Variant { tag: "a", codec: \$.object([]) }, - Variant { tag: "b", codec: \$.object(\$.field("x", \$.u8)) } - ] - ), - \$.field("bar", \$.u8) -) invalid null 1`] = `ScaleAssertError: value == null`; - -snapshot[`\$.object( - \$.taggedUnion( - "_tag", - [ - Variant { tag: "a", codec: \$.object([]) }, - Variant { tag: "b", codec: \$.object(\$.field("x", \$.u8)) } - ] - ), - \$.field("bar", \$.u8) -) invalid { _tag: null, bar: 1 } 1`] = `ScaleAssertError: typeof value._tag !== "string"`; - -snapshot[`\$.object( - \$.taggedUnion( - "_tag", - [ - Variant { tag: "a", codec: \$.object([]) }, - Variant { tag: "b", codec: \$.object(\$.field("x", \$.u8)) } - ] - ), - \$.field("bar", \$.u8) -) invalid { _tag: "", bar: 1 } 1`] = `ScaleAssertError: value._tag: invalid tag`; - -snapshot[`\$.object( - \$.taggedUnion( - "_tag", - [ - Variant { tag: "a", codec: \$.object([]) }, - Variant { tag: "b", codec: \$.object(\$.field("x", \$.u8)) } - ] - ), - \$.field("bar", \$.u8) -) invalid { _tag: "b", bar: 1 } 1`] = `ScaleAssertError: !("x" in value)`; - -snapshot[`\$.object( - \$.taggedUnion( - "_tag", - [ - Variant { tag: "a", codec: \$.object([]) }, - Variant { tag: "b", codec: \$.object(\$.field("x", \$.u8)) } - ] - ), - \$.field("bar", \$.u8) -) invalid { _tag: "b", x: -1, bar: 1 } 1`] = `ScaleAssertError: value.x < 0`; - -snapshot[`\$.object( - \$.taggedUnion( - "_tag", - [ - Variant { tag: "a", codec: \$.object([]) }, - Variant { tag: "b", codec: \$.object(\$.field("x", \$.u8)) } - ] - ), - \$.field("bar", \$.u8) -) invalid { _tag: "a", bar: -1 } 1`] = `ScaleAssertError: value.bar < 0`; diff --git a/codecs/test/optionBool.test.ts b/codecs/test/optionBool.test.ts deleted file mode 100644 index 6f44de1..0000000 --- a/codecs/test/optionBool.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" - -testCodec($.optionBool, [undefined, true, false]) - -testInvalid($.optionBool, [123]) diff --git a/codecs/transform.ts b/codecs/transform.ts deleted file mode 100644 index 37e9121..0000000 --- a/codecs/transform.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AssertState, Codec, createCodec, metadata } from "../common/mod.ts" - -export function transform( - props: { - $base: Codec - encode: (value: UI) => TI - decode: (value: TO) => UO - assert?: (this: Codec, assert: AssertState) => void - }, -): Codec { - return createCodec({ - _metadata: metadata("$.transform", transform, props), - _staticSize: props.$base._staticSize, - _encode(buffer, value) { - props.$base._encode(buffer, props.encode(value)) - }, - _decode(buffer) { - return props.decode(props.$base._decode(buffer)) - }, - _assert(assert) { - props.assert?.call(this, assert) - props.$base._assert(new AssertState(props.encode(assert.value as UI), "#encode", assert)) - }, - }) -} diff --git a/codecs/tuple.ts b/codecs/tuple.ts deleted file mode 100644 index f8be8e0..0000000 --- a/codecs/tuple.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { AnyCodec, Codec, createCodec, Input, metadata, Output } from "../common/mod.ts" - -export type InputTuple = { - readonly [K in keyof T]: Input -} -export type OutputTuple = { - [K in keyof T]: Output -} - -export function tuple(...codecs: [...T]): Codec, OutputTuple> { - return createCodec({ - _metadata: metadata("$.tuple", tuple, ...codecs), - _staticSize: codecs.map((x) => x._staticSize).reduce((a, b) => a + b, 0), - _encode(buffer, value) { - for (let i = 0; i < codecs.length; i++) { - codecs[i]._encode(buffer, value[i] as never) - } - }, - _decode(buffer) { - const value = Array(codecs.length) - for (let i = 0; i < codecs.length; i++) { - value[i] = codecs[i]._decode(buffer) - } - return value as any - }, - _assert(assert) { - assert.instanceof(this, Array) - assert.key(this, "length").equals(this, codecs.length) - for (let i = 0; i < codecs.length; i++) { - codecs[i]._assert(assert.key(this, i)) - } - }, - }) -} diff --git a/common/assert.ts b/common/assert.ts index 769f865..2a67119 100644 --- a/common/assert.ts +++ b/common/assert.ts @@ -1,5 +1,5 @@ -import { AnyCodec } from "./codec.ts" -import { ScaleAssertError } from "./util.ts" +import { AnyShape } from "./shape.ts" +import { ShapeAssertError } from "./util.ts" type TypeofMap = { string: string @@ -19,30 +19,30 @@ export class AssertState { return (this.parent?.path ?? "") + this.pathPart } - typeof(codec: AnyCodec, type: K) { + typeof(shape: AnyShape, type: K) { // deno-lint-ignore valid-typeof if (typeof this.value !== type) { - throw new ScaleAssertError(codec, this.value, `typeof ${this.path} !== "${type}"`) + throw new ShapeAssertError(shape, this.value, `typeof ${this.path} !== "${type}"`) } } - nonNull(codec: AnyCodec) { + nonNull(shape: AnyShape) { if (this.value == null) { - throw new ScaleAssertError(codec, this.value, `${this.path} == null`) + throw new ShapeAssertError(shape, this.value, `${this.path} == null`) } } - instanceof(codec: AnyCodec, ctor: abstract new(...args: any) => unknown) { + instanceof(shape: AnyShape, ctor: abstract new(...args: any) => unknown) { if (!(this.value instanceof ctor)) { - throw new ScaleAssertError(codec, this.value, `!(${this.path} instanceof ${ctor.name})`) + throw new ShapeAssertError(shape, this.value, `!(${this.path} instanceof ${ctor.name})`) } } - key(codec: AnyCodec, key: keyof any) { - this.typeof(codec, "object") - this.nonNull(codec) + key(shape: AnyShape, key: keyof any) { + this.typeof(shape, "object") + this.nonNull(shape) if (!(key in (this.value as object))) { - throw new ScaleAssertError(codec, this.value, `!(${JSON.stringify(key)} in ${this.path})`) + throw new ShapeAssertError(shape, this.value, `!(${JSON.stringify(key)} in ${this.path})`) } const pathPart = typeof key === "string" && /^[^\W\d]\w*$/u.test(key) ? `.${key}` @@ -50,34 +50,34 @@ export class AssertState { return new AssertState((this.value as any)[key], pathPart, this) } - equals(codec: AnyCodec, value: unknown, label = `${value}`) { + equals(shape: AnyShape, value: unknown, label = `${value}`) { if (this.value !== value) { - throw new ScaleAssertError(codec, this.value, `${this.path} !== ${label}`) + throw new ShapeAssertError(shape, this.value, `${this.path} !== ${label}`) } } - integer(codec: AnyCodec, min: number, max: number) { - this.typeof(codec, "number") + integer(shape: AnyShape, min: number, max: number) { + this.typeof(shape, "number") const value = this.value as number if (value !== (value > 0 ? value >>> 0 : value >> 0)) { - throw new ScaleAssertError(codec, this.value, `${this.path}: invalid int`) + throw new ShapeAssertError(shape, this.value, `${this.path}: invalid int`) } if (value < min) { - throw new ScaleAssertError(codec, this.value, `${this.path} < ${min}`) + throw new ShapeAssertError(shape, this.value, `${this.path} < ${min}`) } if (value > max) { - throw new ScaleAssertError(codec, this.value, `${this.path} > ${max}`) + throw new ShapeAssertError(shape, this.value, `${this.path} > ${max}`) } } - bigint(codec: AnyCodec, min: bigint, max: bigint) { - this.typeof(codec, "bigint") + bigint(shape: AnyShape, min: bigint, max: bigint) { + this.typeof(shape, "bigint") const value = this.value as bigint if (value < min) { - throw new ScaleAssertError(codec, this.value, `${this.path} < ${min}n`) + throw new ShapeAssertError(shape, this.value, `${this.path} < ${min}n`) } if (value > max) { - throw new ScaleAssertError(codec, this.value, `${this.path} > ${max}n`) + throw new ShapeAssertError(shape, this.value, `${this.path} > ${max}n`) } } } diff --git a/common/codec.ts b/common/codec.ts deleted file mode 100644 index 13afada..0000000 --- a/common/codec.ts +++ /dev/null @@ -1,138 +0,0 @@ -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 Codec ? I : never -export type Output = T extends Codec ? O : never - -export function createCodec( - _codec: - & ThisType> - & Pick, "_encode" | "_decode" | "_assert" | "_staticSize" | "_metadata">, -): Codec { - const { _staticSize, _encode, _assert, _decode, _metadata } = _codec - const codec: Codec = { - // @ts-ignore https://gist.github.com/tjjfvi/ea194c4fce76dacdd60a0943256332aa - __proto__: Codec.prototype, - _staticSize, - _encode, - _decode, - _assert, - _metadata, - } - return codec -} - -type NoInfer = T extends infer U ? U : never -export function withMetadata(metadata: Metadata, NoInfer>, codec: Codec): Codec { - const result: Codec = { - // @ts-ignore https://gist.github.com/tjjfvi/ea194c4fce76dacdd60a0943256332aa - __proto__: Codec.prototype, - ...codec, - _metadata: [...metadata as Metadata, ...codec._metadata], - } - return result -} - -const codecInspectCtx = new Map() -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 -export type Encodec = Codec -export type Decodec = Codec - -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: 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 - - /** 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(codec: Codec, value: unknown): asserts value is I { - codec._assert(new AssertState(value)) -} - -export function is(codec: Codec, value: unknown): value is T { - try { - codec._assert(new AssertState(value)) - return true - } catch (e) { - if (e instanceof ScaleAssertError) { - return false - } else { - throw e - } - } -} diff --git a/common/metadata.ts b/common/metadata.ts index 5b334c9..b351c75 100644 --- a/common/metadata.ts +++ b/common/metadata.ts @@ -1,28 +1,26 @@ -import { Codec } from "./codec.ts" +import { Shape } from "./shape.ts" export type Metadata = Array< | { type: "atomic" name: string - docs?: never factory?: never args?: never } | { type: "factory" name: string - docs?: never - factory: (...args: any) => Codec + factory: (...args: any) => Shape args: any[] } > -/** Metadata for an atomic codec */ +/** Metadata for an atomic shape */ export function metadata(name: string): Metadata -/** Metadata for a factory-made codec */ +/** Metadata for a factory-made shape */ export function metadata( name: string, - factory: (...args: A) => Codec, + factory: (...args: A) => Shape, ...args: A ): Metadata /** Concatenate multiple metadata arrays */ @@ -32,12 +30,12 @@ export function metadata( | Metadata[] | [ name: string, - factory?: (...args: any) => Codec, + factory?: (...args: any) => Shape, ...args: any[], ] ): Metadata { if (typeof fullArgs[0] !== "string") return fullArgs.flat() - const [name, factory, ...args] = fullArgs as [name: string, factory?: (...args: any) => Codec, ...args: any[]] + const [name, factory, ...args] = fullArgs as [name: string, factory?: (...args: any) => Shape, ...args: any[]] return [ factory ? { @@ -53,25 +51,25 @@ export function metadata( ] } -export class CodecVisitor { - #fallback?: (codec: Codec) => R - #visitors = new Map[number] | Function, (codec: Codec, ...args: any[]) => R>() +export class ShapeVisitor { + #fallback?: (shape: Shape) => R + #visitors = new Map[number] | Function, (shape: Shape, ...args: any[]) => R>() - add(codec: (...args: A) => Codec, fn: (codec: Codec, ...args: A) => R): this - add(codec: Codec, fn: (codec: Codec) => R): this - add(codec: Codec | Metadata[number] | Function, fn: (codec: Codec, ...args: any[]) => R): this { - if (codec instanceof Codec) { - codec = codec._metadata[0]! - if (!codec) throw new Error("Cannot register visitor for metadata-less codec") + add(shape: (...args: A) => Shape, fn: (shape: Shape, ...args: A) => R): this + add(shape: Shape, fn: (shape: Shape) => R): this + add(shape: Shape | Metadata[number] | Function, fn: (shape: Shape, ...args: any[]) => R): this { + if (shape instanceof Shape) { + shape = shape.metadata[0]! + if (!shape) throw new Error("Cannot register visitor for metadata-less shape") } - if (this.#visitors.has(codec)) { + if (this.#visitors.has(shape)) { throw new Error("Duplicate visitor") } - this.#visitors.set(codec, fn) + this.#visitors.set(shape, fn) return this } - fallback(fn: (codec: Codec) => R): this { + fallback(fn: (shape: Shape) => R): this { if (this.#fallback) { throw new Error("Duplicate fallback") } @@ -82,7 +80,7 @@ export class CodecVisitor { /** * ```ts * visitor.generic(() => - * visitor.add($.array, (codec, $el) => { + * visitor.add($.array, (shape, $el) => { * ... * }) * ) @@ -93,17 +91,17 @@ export class CodecVisitor { return this } - visit(codec: Codec): R { - for (const metadata of codec._metadata) { + visit(shape: Shape): R { + for (const metadata of shape.metadata) { let visitor = this.#visitors.get(metadata) - if (visitor) return visitor(codec) + if (visitor) return visitor(shape) if (metadata.type !== "factory") continue visitor = this.#visitors.get(metadata.factory) - if (visitor) return visitor(codec, ...metadata.args) + if (visitor) return visitor(shape, ...metadata.args) } if (this.#fallback) { - return this.#fallback(codec) + return this.#fallback(shape) } - throw new Error("Unrecognized codec") + throw new Error("Unrecognized shape") } } diff --git a/common/mod.ts b/common/mod.ts index 5cafe71..8de0705 100644 --- a/common/mod.ts +++ b/common/mod.ts @@ -2,6 +2,6 @@ export * from "./assert.ts" export * from "./buffer.ts" -export * from "./codec.ts" export * from "./metadata.ts" +export * from "./shape.ts" export * from "./util.ts" diff --git a/common/shape.ts b/common/shape.ts new file mode 100644 index 0000000..902c2a8 --- /dev/null +++ b/common/shape.ts @@ -0,0 +1,138 @@ +import { AssertState } from "./assert.ts" +import { DecodeBuffer, EncodeBuffer } from "./buffer.ts" +import { Metadata } from "./metadata.ts" +import { ShapeAssertError, ShapeEncodeError } from "./util.ts" + +export type Input = T extends Shape ? I : never +export type Output = T extends Shape ? O : never + +export function createShape( + _shape: + & ThisType> + & Pick, "subEncode" | "subDecode" | "subAssert" | "staticSize" | "metadata">, +): Shape { + const { staticSize, subEncode, subAssert, subDecode, metadata } = _shape + const shape: Shape = { + // @ts-ignore https://gist.github.com/tjjfvi/ea194c4fce76dacdd60a0943256332aa + __proto__: Shape.prototype, + staticSize, + subEncode, + subDecode, + subAssert, + metadata, + } + return shape +} + +type NoInfer = T extends infer U ? U : never +export function withMetadata(metadata: Metadata, NoInfer>, shape: Shape): Shape { + const result: Shape = { + // @ts-ignore https://gist.github.com/tjjfvi/ea194c4fce76dacdd60a0943256332aa + __proto__: Shape.prototype, + ...shape, + metadata: [...metadata as Metadata, ...shape.metadata], + } + return result +} + +const shapeInspectCtx = new Map() +let shapeInspectIdN = 0 +const nodeCustomInspect = Symbol.for("nodejs.util.inspect.custom") +const denoCustomInspect = Symbol.for("Deno.customInspect") + +abstract class _Shape { + 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 shapes in the case of $.deferred + private _inspect(inspect: (value: unknown) => string): string + private _inspect(this: AnyShape, inspect: (value: unknown) => string): string { + let id = shapeInspectCtx.get(this) + if (id !== undefined) { + if (id === null) { + shapeInspectCtx.set(this, id = shapeInspectIdN++) + } + return `$${id}` + } + try { + shapeInspectCtx.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 = shapeInspectCtx.get(this) + return id !== null ? `$${id} = ${content}` : content + } finally { + shapeInspectCtx.delete(this) + if (shapeInspectCtx.size === 0) shapeInspectIdN = 0 + } + } +} + +export type AnyShape = Shape +export type InShape = Shape +export type OutShape = Shape + +export abstract class Shape extends _Shape implements AnyShape { + /** 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 subEncode: (buffer: EncodeBuffer, value: I) => void + /** Decodes the value from the supplied buffer */ + abstract subDecode: (buffer: DecodeBuffer) => O + /** Asserts that the value is valid for this shape */ + abstract subAssert: (state: AssertState) => void + /** An array with metadata representing the construction of this shape */ + abstract metadata: Metadata + + /** Encodes the value into a new Uint8Array (throws if async) */ + encode(value: I) { + const buf = new EncodeBuffer(this.staticSize) + this.subEncode(buf, value) + if (buf.asyncCount) throw new ShapeEncodeError(this, value, "Attempted to synchronously encode an async shape") + return buf.finish() + } + + /** Asynchronously encodes the value into a new Uint8Array */ + async encodeAsync(value: I) { + const buf = new EncodeBuffer(this.staticSize) + this.subEncode(buf, value) + return buf.finishAsync() + } + + /** Decodes a value from the supplied Uint8Array */ + decode(array: Uint8Array) { + const buf = new DecodeBuffer(array) + return this.subDecode(buf) + } + + /** Requires the shape 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 shape */ +export function assert(shape: Shape, value: unknown): asserts value is I { + shape.subAssert(new AssertState(value)) +} + +export function is(shape: Shape, value: unknown): value is T { + try { + shape.subAssert(new AssertState(value)) + return true + } catch (e) { + if (e instanceof ShapeAssertError) { + return false + } else { + throw e + } + } +} diff --git a/common/test/__snapshots__/context.test.ts.snap b/common/test/__snapshots__/context.test.ts.snap index e6a5ec5..a1997c3 100644 --- a/common/test/__snapshots__/context.test.ts.snap +++ b/common/test/__snapshots__/context.test.ts.snap @@ -1,6 +1,6 @@ export const snapshot = {}; -snapshot[`CodecVisitor 1`] = `Error: Unrecognized codec`; +snapshot[`ShapeVisitor 1`] = `Error: Unrecognized shape`; snapshot[`\$graph a 1`] = ` 00 diff --git a/common/test/__snapshots__/visitor.test.ts.snap b/common/test/__snapshots__/visitor.test.ts.snap index 9cf4cd6..e70861f 100644 --- a/common/test/__snapshots__/visitor.test.ts.snap +++ b/common/test/__snapshots__/visitor.test.ts.snap @@ -1,3 +1,3 @@ export const snapshot = {}; -snapshot[`CodecVisitor 1`] = `Error: Unrecognized codec`; +snapshot[`ShapeVisitor 1`] = `Error: Unrecognized shape`; diff --git a/common/test/context.test.ts b/common/test/context.test.ts index f650e5d..618c94f 100644 --- a/common/test/context.test.ts +++ b/common/test/context.test.ts @@ -1,8 +1,8 @@ import * as $ from "../../mod.ts" -import { assertEquals, assertThrowsSnapshot, testCodec } from "../../test-util.ts" +import { assertEquals, assertThrowsSnapshot, testShape } from "../../test-util.ts" -Deno.test("CodecVisitor", async (t) => { - const visitor = new $.CodecVisitor() +Deno.test("ShapeVisitor", async (t) => { + const visitor = new $.ShapeVisitor() visitor .add($.u8, () => "$.u8") .add($.int, (_, signed, size) => `$.int(${signed}, ${size})`) @@ -23,35 +23,35 @@ class GraphDecodeCtx { } const $compactU32 = $.compact($.u32) -const $graph: $.Codec = $.createCodec({ - _metadata: $.metadata("$graph"), - _staticSize: $compactU32._staticSize * 2 + $.str._staticSize, - _encode(buffer, value) { +const $graph: $.Shape = $.createShape({ + metadata: $.metadata("$graph"), + staticSize: $compactU32.staticSize * 2 + $.str.staticSize, + subEncode(buffer, value) { const ctx = buffer.context.get(GraphEncodeCtx) const key = ctx.memo.get(value) if (key != null) { - return $compactU32._encode(buffer, key) + return $compactU32.subEncode(buffer, key) } - $compactU32._encode(buffer, ctx.memo.size) + $compactU32.subEncode(buffer, ctx.memo.size) ctx.memo.set(value, ctx.memo.size) - $.str._encode(buffer, value.label) - $.array($graph)._encode(buffer, value.to) + $.str.subEncode(buffer, value.label) + $.array($graph).subEncode(buffer, value.to) }, - _decode(buffer) { + subDecode(buffer) { const ctx = buffer.context.get(GraphDecodeCtx) - const key = $compactU32._decode(buffer) + const key = $compactU32.subDecode(buffer) if (key < ctx.memo.length) { return ctx.memo[key]! } const graph: Graph = { - label: $.str._decode(buffer), + label: $.str.subDecode(buffer), to: [], } ctx.memo.push(graph) - graph.to = $.array($graph)._decode(buffer) + graph.to = $.array($graph).subDecode(buffer) return graph }, - _assert() {}, + subAssert() {}, }) const a: Graph = { label: "a", to: [] } @@ -67,4 +67,4 @@ c.to = [a, e] e.to = [a, c, f] f.to = [a, b, c, d, e, f] -testCodec($graph, { a, b, c, d, e, f }) +testShape($graph, { a, b, c, d, e, f }) diff --git a/common/test/inspect.test.ts b/common/test/inspect.test.ts index 8b9302f..3296c49 100644 --- a/common/test/inspect.test.ts +++ b/common/test/inspect.test.ts @@ -6,7 +6,7 @@ type LinkedList = undefined | { next: LinkedList } -const $linkedList: $.Codec = $.option($.object( +const $linkedList: $.Shape = $.option($.object( $.field("val", $.u8), $.field("next", $.deferred(() => $linkedList)), )) @@ -34,9 +34,9 @@ $.array( type Foo = { bar: Bar; baz: Baz } type Bar = { foo: Foo; baz: Baz } type Baz = { foo: Foo; bar: Bar } - const $foo: $.Codec = $.object($.field("bar", $.deferred(() => $bar)), $.field("baz", $.deferred(() => $baz))) - const $bar: $.Codec = $.object($.field("foo", $.deferred(() => $foo)), $.field("baz", $.deferred(() => $baz))) - const $baz: $.Codec = $.object($.field("foo", $.deferred(() => $foo)), $.field("bar", $.deferred(() => $bar))) + const $foo: $.Shape = $.object($.field("bar", $.deferred(() => $bar)), $.field("baz", $.deferred(() => $baz))) + const $bar: $.Shape = $.object($.field("foo", $.deferred(() => $foo)), $.field("baz", $.deferred(() => $baz))) + const $baz: $.Shape = $.object($.field("foo", $.deferred(() => $foo)), $.field("bar", $.deferred(() => $bar))) assertEquals( Deno.inspect($foo, { depth: Infinity }), ` diff --git a/common/test/visitor.test.ts b/common/test/visitor.test.ts index 0bcd22f..aacabc3 100644 --- a/common/test/visitor.test.ts +++ b/common/test/visitor.test.ts @@ -1,8 +1,8 @@ import * as $ from "../../mod.ts" import { assertEquals, assertThrowsSnapshot } from "../../test-util.ts" -Deno.test("CodecVisitor", async (t) => { - const visitor = new $.CodecVisitor() +Deno.test("ShapeVisitor", async (t) => { + const visitor = new $.ShapeVisitor() visitor .add($.u8, () => "$.u8") .add($.int, (_, signed, size) => `$.int(${signed}, ${size})`) diff --git a/common/util.ts b/common/util.ts index cf10ab9..c38cea9 100644 --- a/common/util.ts +++ b/common/util.ts @@ -1,30 +1,30 @@ import type { DecodeBuffer } from "./buffer.ts" -import type { AnyCodec } from "./codec.ts" +import type { AnyShape } from "./shape.ts" -export abstract class ScaleError extends Error { - constructor(readonly codec: AnyCodec, message: string) { +export abstract class ShapeError extends Error { + constructor(readonly shape: AnyShape, message: string) { super(message) } } -export class ScaleAssertError extends ScaleError { - override readonly name = "ScaleAssertError" - constructor(codec: AnyCodec, readonly value: unknown, message: string) { - super(codec, message) +export class ShapeAssertError extends ShapeError { + override readonly name = "ShapeAssertError" + constructor(shape: AnyShape, readonly value: unknown, message: string) { + super(shape, message) } } -export class ScaleEncodeError extends ScaleError { - override readonly name = "ScaleEncodeError" - constructor(codec: AnyCodec, readonly value: unknown, message: string) { - super(codec, message) +export class ShapeEncodeError extends ShapeError { + override readonly name = "ShapeEncodeError" + constructor(shape: AnyShape, readonly value: unknown, message: string) { + super(shape, message) } } -export class ScaleDecodeError extends ScaleError { - override readonly name = "ScaleDecodeError" - constructor(codec: AnyCodec, readonly buffer: DecodeBuffer, message: string) { - super(codec, message) +export class ShapeDecodeError extends ShapeError { + override readonly name = "ShapeDecodeError" + constructor(shape: AnyShape, readonly buffer: DecodeBuffer, message: string) { + super(shape, message) } } diff --git a/dprint.json b/dprint.json index 81c87e7..58185f3 100644 --- a/dprint.json +++ b/dprint.json @@ -7,6 +7,11 @@ "arrowFunction.useParentheses": "force", "semiColons": "asi" }, + "markdown": { + "lineWidth": 80, + "textWrap": "always", + "emphasisKind": "asterisks" + }, "includes": ["**.{dockerfile,json,md,toml,ts}"], "excludes": ["target"], "plugins": [ diff --git a/examples/arrays.eg.ts b/examples/arrays.eg.ts index 9e83b03..99c5c6b 100644 --- a/examples/arrays.eg.ts +++ b/examples/arrays.eg.ts @@ -1,13 +1,13 @@ -// import * as $ from "https://deno.land/x/scale/mod.ts"; +// import * as $ from "https://deno.land/x/subshape/mod.ts"; import * as $ from "../mod.ts" -$.array($.u32) // Codec -$.sizedArray($.u32, 2) // Codec<[number, number]> +$.array($.u32) // Shape +$.sizedArray($.u32, 2) // Shape<[number, number]> -$.uint8Array // Codec -$.sizedUint8Array(32) // Codec +$.uint8Array // Shape +$.sizedUint8Array(32) // Shape -$.tuple($.bool, $.u8, $.str) // Codec<[boolean, number, string]> +$.tuple($.bool, $.u8, $.str) // Shape<[boolean, number, string]> // like boolean[] but backed by an ArrayBuffer -$.bitSequence // Codec +$.bitSequence // Shape diff --git a/examples/assertions.eg.ts b/examples/assertions.eg.ts index 1c8beab..6affd41 100644 --- a/examples/assertions.eg.ts +++ b/examples/assertions.eg.ts @@ -1,4 +1,4 @@ -// import * as $ from "https://deno.land/x/scale/mod.ts"; +// import * as $ from "https://deno.land/x/subshape/mod.ts"; import * as $ from "../mod.ts" // using `$.assert` (no explicit typing required) @@ -6,8 +6,8 @@ const a: unknown = 1 $.assert($.u8, a) a -// using `Codec.assert` (explicit typing required) +// using `Shape.assert` (explicit typing required) const b: unknown = 2 -const u8: $.Codec = $.u8 +const u8: $.Shape = $.u8 u8.assert(b) b diff --git a/examples/collections.eg.ts b/examples/collections.eg.ts index 568d2d0..f38be15 100644 --- a/examples/collections.eg.ts +++ b/examples/collections.eg.ts @@ -1,8 +1,8 @@ -// import * as $ from "https://deno.land/x/scale/mod.ts"; +// import * as $ from "https://deno.land/x/subshape/mod.ts"; import * as $ from "../mod.ts" -$.set($.u32) // Codec> +$.set($.u32) // Shape> -$.map($.str, $.u32) // Codec> +$.map($.str, $.u32) // Shape> -$.record($.u8) // Codec> +$.record($.u8) // Shape> diff --git a/examples/metadata.eg.ts b/examples/metadata.eg.ts index c0d4c68..cd73678 100644 --- a/examples/metadata.eg.ts +++ b/examples/metadata.eg.ts @@ -1,24 +1,24 @@ -// import * as $ from "https://deno.land/x/scale/mod.ts"; +// import * as $ from "https://deno.land/x/subshape/mod.ts"; import * as $ from "../mod.ts" -const visitor = new $.CodecVisitor() +const visitor = new $.ShapeVisitor() -// you can pass a plain codec: +// you can pass a plain shape: visitor.add($.u8, () => "$.u8") -// or a codec factory: -visitor.add($.int, (_codec, signed, size) => `$.int(${signed}, ${size})`) +// or a shape factory: +visitor.add($.int, (_shape, signed, size) => `$.int(${signed}, ${size})`) // ^^^^^^^^^^^^ // the arguments that were passed to the factory // you can handle generic factories like so: visitor.generic(() => { visitor.add($.array, (_, $el) => `$.array(${visitor.visit($el)})`) - // ^^^ Codec + // ^^^ Shape }) // if none of the other visitors match: -visitor.fallback((_codec) => "?") +visitor.fallback((_shape) => "?") visitor.visit($.array($.u8)) // "$.array($.u8)" visitor.visit($.u16) // "$.int(false, 16)" diff --git a/examples/objects.eg.ts b/examples/objects.eg.ts index 5f481be..a1e819d 100644 --- a/examples/objects.eg.ts +++ b/examples/objects.eg.ts @@ -1,4 +1,4 @@ -// import * as $ from "https://deno.land/x/scale/mod.ts"; +// import * as $ from "https://deno.land/x/subshape/mod.ts"; import * as $ from "../mod.ts" export const $superhero = $.object( @@ -8,7 +8,7 @@ export const $superhero = $.object( ) $superhero -// Codec<{ +// Shape<{ // pseudonym: string; // secretIdentity?: string | undefined; // superpowers: string[]; @@ -28,4 +28,4 @@ export const $myError = $.instance( (myError: MyError) => [myError.code, myError.message], // Specify how to extract arguments from an instance ) -$myError // Codec +$myError // Shape diff --git a/examples/primitives.eg.ts b/examples/primitives.eg.ts index 709f4b6..8f33615 100644 --- a/examples/primitives.eg.ts +++ b/examples/primitives.eg.ts @@ -1,34 +1,34 @@ -// import * as $ from "https://deno.land/x/scale/mod.ts"; +// import * as $ from "https://deno.land/x/subshape/mod.ts"; import * as $ from "../mod.ts" -$.bool // Codec +$.bool // Shape -$.u8 // Codec -$.i8 // Codec -$.u16 // Codec -$.i16 // Codec -$.u32 // Codec -$.i32 // Codec +$.u8 // Shape +$.i8 // Shape +$.u16 // Shape +$.i16 // Shape +$.u32 // Shape +$.i32 // Shape -$.u64 // Codec -$.i64 // Codec -$.u128 // Codec -$.i128 // Codec -$.u256 // Codec -$.i256 // Codec +$.u64 // Shape +$.i64 // Shape +$.u128 // Shape +$.i128 // Shape +$.u256 // Shape +$.i256 // Shape -// https://docs.substrate.io/reference/scale-codec/#fnref-1 -$.compact($.u8) // Codec -$.compact($.u16) // Codec -$.compact($.u32) // Codec -$.compact($.u64) // Codec -$.compact($.u128) // Codec -$.compact($.u256) // Codec +// https://docs.substrate.io/reference/scale-shape/#fnref-1 +$.compact($.u8) // Shape +$.compact($.u16) // Shape +$.compact($.u32) // Shape +$.compact($.u64) // Shape +$.compact($.u128) // Shape +$.compact($.u256) // Shape -$.str // Codec +$.str // Shape // (encodes as 0 bytes, and always decodes as a constant value) -$.constant(null) // Codec +$.constant(null) // Shape // (throws if reached) -$.never // Codec +$.never // Shape diff --git a/examples/recursive.eg.ts b/examples/recursive.eg.ts index 31a08f5..773ac22 100644 --- a/examples/recursive.eg.ts +++ b/examples/recursive.eg.ts @@ -1,4 +1,4 @@ -// import * as $ from "https://deno.land/x/scale/mod.ts"; +// import * as $ from "https://deno.land/x/subshape/mod.ts"; import * as $ from "../mod.ts" import { $interestingU8, $pet, InterestingU8 } from "./unions.eg.ts" @@ -11,7 +11,7 @@ interface Person { children: Person[] } -const $person: $.Codec = $.object( +const $person: $.Shape = $.object( $.field("name", $.str), $.field("favoriteU8", $interestingU8), $.field("pets", $.array($pet)), diff --git a/examples/unions.eg.ts b/examples/unions.eg.ts index e98382a..d89e7ab 100644 --- a/examples/unions.eg.ts +++ b/examples/unions.eg.ts @@ -1,13 +1,13 @@ -// import * as $ from "https://deno.land/x/scale/mod.ts"; +// import * as $ from "https://deno.land/x/subshape/mod.ts"; import * as $ from "../mod.ts" import { $myError } from "./objects.eg.ts" -$.option($.u32) // Codec -$.optionBool // Codec (stores as single byte; see OptionBool in Rust impl) +$.option($.u32) // Shape +$.optionBool // Shape (stores as single byte; see OptionBool in Rust impl) export const $myResult = $.result($.str, $myError) -$myResult // Codec +$myResult // Shape export const $animal = $.taggedUnion("type", [ $.variant("dog", $.field("bark", $.str)), @@ -15,7 +15,7 @@ export const $animal = $.taggedUnion("type", [ ]) $animal -// Codec< +// Shape< // | { type: "dog"; bark: string } // | { type: "cat"; purr: string } // > @@ -26,7 +26,7 @@ export const $pet = $.object( ) $pet -// Codec< +// Shape< // | { type: "dog"; bark: string; name: string } // | { type: "cat"; purr: string; name: string } // > @@ -37,7 +37,7 @@ export const $dinosaur = $.literalUnion([ "Psittacosaurus", ]) -$dinosaur // Codec<"Liopleurodon" | "Kosmoceratops" | "Psittacosaurus"> +$dinosaur // Shape<"Liopleurodon" | "Kosmoceratops" | "Psittacosaurus"> export enum InterestingU8 { Min = 0, @@ -49,6 +49,6 @@ export enum InterestingU8 { Max = 255, } -export const $interestingU8 = $.u8 as $.Codec +export const $interestingU8 = $.u8 as $.Shape -$interestingU8 // Codec +$interestingU8 // Shape diff --git a/fixtures.rs b/fixtures.rs index 3cba3f1..4761019 100644 --- a/fixtures.rs +++ b/fixtures.rs @@ -3,61 +3,61 @@ use itertools::Itertools; use std::{fs, path::Path}; -#[path = "./codecs/fixtures/array.rs"] +#[path = "./shapes/fixtures/array.rs"] pub mod array_fixtures; -#[path = "./codecs/fixtures/bool.rs"] +#[path = "./shapes/fixtures/bool.rs"] pub mod bool_fixtures; -#[path = "./codecs/fixtures/collections.rs"] +#[path = "./shapes/fixtures/collections.rs"] pub mod collections_fixtures; -#[path = "./codecs/fixtures/compact.rs"] +#[path = "./shapes/fixtures/compact.rs"] pub mod compact_fixtures; -#[path = "./codecs/fixtures/deferred.rs"] +#[path = "./shapes/fixtures/deferred.rs"] pub mod deferred_fixtures; -#[path = "./codecs/fixtures/float.rs"] +#[path = "./shapes/fixtures/float.rs"] pub mod float_fixtures; -#[path = "./codecs/fixtures/hex.rs"] +#[path = "./shapes/fixtures/hex.rs"] pub mod hex_fixtures; -#[path = "./codecs/fixtures/int.rs"] +#[path = "./shapes/fixtures/int.rs"] pub mod int_fixtures; -#[path = "./codecs/fixtures/iterable.rs"] +#[path = "./shapes/fixtures/iterable.rs"] pub mod iterable_fixtures; -#[path = "./codecs/fixtures/lenPrefixed.rs"] +#[path = "./shapes/fixtures/lenPrefixed.rs"] pub mod len_prefixed_fixtures; -#[path = "./codecs/fixtures/never.rs"] +#[path = "./shapes/fixtures/never.rs"] pub mod never_fixtures; -#[path = "./codecs/fixtures/option.rs"] +#[path = "./shapes/fixtures/option.rs"] pub mod option_fixtures; -#[path = "./codecs/fixtures/optionBool.rs"] +#[path = "./shapes/fixtures/optionBool.rs"] pub mod option_bool_fixtures; -#[path = "./codecs/fixtures/object.rs"] +#[path = "./shapes/fixtures/object.rs"] pub mod object_fixtures; -#[path = "./codecs/fixtures/result.rs"] +#[path = "./shapes/fixtures/result.rs"] pub mod result_fixtures; -#[path = "./codecs/fixtures/str.rs"] +#[path = "./shapes/fixtures/str.rs"] pub mod str_fixtures; -#[path = "./codecs/fixtures/tuple.rs"] +#[path = "./shapes/fixtures/tuple.rs"] pub mod tuple_fixtures; -#[path = "./codecs/fixtures/union.rs"] +#[path = "./shapes/fixtures/union.rs"] pub mod union_fixtures; -#[path = "./codecs/fixtures/bitSequence.rs"] +#[path = "./shapes/fixtures/bitSequence.rs"] pub mod bit_sequence; pub(crate) const LIPSUM: &'static str = include_str!("lipsum.txt"); diff --git a/mod.ts b/mod.ts index 5906d1c..3c935c9 100644 --- a/mod.ts +++ b/mod.ts @@ -1,2 +1,2 @@ -export * from "./codecs/mod.ts" export * from "./common/mod.ts" +export * from "./shapes/mod.ts" diff --git a/scale.ts b/scale.ts new file mode 100644 index 0000000..68fe9ac --- /dev/null +++ b/scale.ts @@ -0,0 +1,52 @@ +export { + array, + BitSequence, + bitSequence, + bool, + compact, + constant, + deferred, + documented, + field, + i128, + i16, + i256, + i32, + i64, + i8, + type Input, + int, + lenPrefixed, + literalUnion, + map, + never, + object, + option, + optionalField, + optionBool, + type Output, + record, + result, + set, + Shape, + ShapeAssertError, + ShapeDecodeError, + ShapeEncodeError, + ShapeError, + ShapeMap, + ShapeSet, + sizedArray, + sizedUint8Array, + str, + taggedUnion, + tuple, + u128, + u16, + u256, + u32, + u64, + u8, + uint8Array, + Variant, + variant, +} from "./mod.ts" diff --git a/codecs/array.ts b/shapes/array.ts similarity index 51% rename from codecs/array.ts rename to shapes/array.ts index c531b71..d6141fe 100644 --- a/codecs/array.ts +++ b/shapes/array.ts @@ -1,4 +1,4 @@ -import { Codec, createCodec, metadata } from "../common/mod.ts" +import { createShape, metadata, Shape } from "../common/mod.ts" import { compact } from "./compact.ts" import { u32 } from "./int.ts" @@ -12,98 +12,98 @@ type ArrayOfLength< : L extends A["length"] ? A : ArrayOfLength -export function sizedArray($el: Codec, length: L): Codec< +export function sizedArray($el: Shape, length: L): Shape< Readonly>, ArrayOfLength > { - return createCodec({ - _metadata: metadata("$.sizedArray", sizedArray, $el, length), - _staticSize: $el._staticSize * length, - _encode(buffer, value) { + return createShape({ + metadata: metadata("$.sizedArray", sizedArray, $el, length), + staticSize: $el.staticSize * length, + subEncode(buffer, value) { for (let i = 0; i < value.length; i++) { - $el._encode(buffer, value[i]!) + $el.subEncode(buffer, value[i]!) } }, - _decode(buffer) { + subDecode(buffer) { const value: O[] = Array(length) for (let i = 0; i < value.length; i++) { - value[i] = $el._decode(buffer) + value[i] = $el.subDecode(buffer) } return value as ArrayOfLength }, - _assert(assert) { + subAssert(assert) { assert.instanceof(this, Array) assert.key(this, "length").equals(this, length) for (let i = 0; i < length; i++) { - $el._assert(assert.key(this, i)) + $el.subAssert(assert.key(this, i)) } }, }) } -export function array($el: Codec): Codec { - return createCodec({ - _metadata: metadata("$.array", array, $el), - _staticSize: compactU32._staticSize, - _encode(buffer, value) { - compactU32._encode(buffer, value.length) +export function array($el: Shape): Shape { + return createShape({ + metadata: metadata("$.array", array, $el), + staticSize: compactU32.staticSize, + subEncode(buffer, value) { + compactU32.subEncode(buffer, value.length) if (value.length) { - buffer.pushAlloc(value.length * $el._staticSize) + buffer.pushAlloc(value.length * $el.staticSize) for (let i = 0; i < value.length; i++) { - $el._encode(buffer, value[i]!) + $el.subEncode(buffer, value[i]!) } buffer.popAlloc() } }, - _decode(buffer) { - const length = compactU32._decode(buffer) + subDecode(buffer) { + const length = compactU32.subDecode(buffer) const value: O[] = Array(length) for (let i = 0; i < value.length; i++) { - value[i] = $el._decode(buffer) + value[i] = $el.subDecode(buffer) } return value }, - _assert(assert) { + subAssert(assert) { assert.instanceof(this, Array) for (let i = 0; i < (assert.value as unknown[]).length; i++) { - $el._assert(assert.key(this, i)) + $el.subAssert(assert.key(this, i)) } }, }) } -export const uint8Array: Codec = createCodec({ - _metadata: metadata("$.uint8Array"), - _staticSize: compactU32._staticSize, - _encode(buffer, value) { - compactU32._encode(buffer, value.length) +export const uint8Array: Shape = createShape({ + metadata: metadata("$.uint8Array"), + staticSize: compactU32.staticSize, + subEncode(buffer, value) { + compactU32.subEncode(buffer, value.length) buffer.insertArray(value) // the contents of this will eventually be cloned by buffer }, - _decode(buffer) { - const length = compactU32._decode(buffer) + subDecode(buffer) { + const length = compactU32.subDecode(buffer) const value = buffer.array.subarray(buffer.index, buffer.index + length) buffer.index += length return value }, - _assert(assert) { + subAssert(assert) { assert.instanceof(this, Uint8Array) }, }) -export function sizedUint8Array(length: number): Codec { - return createCodec({ - _metadata: metadata("$.sizedUint8Array", sizedUint8Array, length), - // We could set `_staticSize` to `length`, but in this case it will usually +export function sizedUint8Array(length: number): Shape { + return createShape({ + metadata: metadata("$.sizedUint8Array", sizedUint8Array, length), + // We could set `staticSize` to `length`, but in this case it will usually // more efficient to insert the array dynamically, rather than manually copy // the bytes. - _staticSize: 0, - _encode(buffer, value) { + staticSize: 0, + subEncode(buffer, value) { buffer.insertArray(value) // the contents of this will eventually be cloned by buffer }, - _decode(buffer) { + subDecode(buffer) { return buffer.array.subarray(buffer.index, buffer.index += length) }, - _assert(assert) { + subAssert(assert) { assert.instanceof(this, Uint8Array) assert.key(this, "length").equals(this, length) }, diff --git a/shapes/bench/array.bench.ts b/shapes/bench/array.bench.ts new file mode 100644 index 0000000..e15f69f --- /dev/null +++ b/shapes/bench/array.bench.ts @@ -0,0 +1,18 @@ +import * as $ from "../../mod.ts" +import { benchShape } from "../../test-util.ts" + +function arr(length: number, el: (i: number) => T): T[] { + return Array.from({ length }, (_, i) => el(i)) +} + +benchShape("bool[0]", $.array($.bool), []) +benchShape("u128[0]", $.array($.u128), []) +benchShape("compactU256[0]", $.array($.compact($.u256)), []) + +benchShape("bool[128]", $.array($.bool), arr(128, (i) => i % 2 === 0)) +benchShape("u128[128]", $.array($.u128), arr(128, (i) => 2n ** BigInt(i % 100) + BigInt(i))) +benchShape("compactU256[128]", $.array($.compact($.u256)), arr(128, (i) => 2n ** BigInt(i % 100) + BigInt(i))) + +benchShape("bool[16384]", $.array($.bool), arr(16384, (i) => i % 2 === 0)) +benchShape("u128[16384]", $.array($.u128), arr(16384, (i) => 2n ** BigInt(i % 100) + BigInt(i))) +benchShape("compactU256[16384]", $.array($.compact($.u256)), arr(16384, (i) => 2n ** BigInt(i % 100) + BigInt(i))) diff --git a/shapes/bench/bool.bench.ts b/shapes/bench/bool.bench.ts new file mode 100644 index 0000000..fc6ee51 --- /dev/null +++ b/shapes/bench/bool.bench.ts @@ -0,0 +1,4 @@ +import * as $ from "../../mod.ts" +import { benchShape } from "../../test-util.ts" + +benchShape("bool", $.bool, true) diff --git a/shapes/bench/compact.bench.ts b/shapes/bench/compact.bench.ts new file mode 100644 index 0000000..8fa4f8c --- /dev/null +++ b/shapes/bench/compact.bench.ts @@ -0,0 +1,15 @@ +import * as $ from "../../mod.ts" +import { benchShape } from "../../test-util.ts" + +benchShape("compactU32 (6)", $.compact($.u32), 2 ** 6 - 1) +benchShape("compactU32 (14)", $.compact($.u32), 2 ** 14 - 1) +benchShape("compactU32 (30)", $.compact($.u32), 2 ** 30 - 1) +benchShape("compactU32 (32)", $.compact($.u32), 2 ** 32 - 1) + +benchShape("compactU256 (6)", $.compact($.u256), 2n ** 6n - 1n) +benchShape("compactU256 (14)", $.compact($.u256), 2n ** 14n - 1n) +benchShape("compactU256 (30)", $.compact($.u256), 2n ** 30n - 1n) +benchShape("compactU256 (32)", $.compact($.u256), 2n ** 32n - 1n) +benchShape("compactU256 (64)", $.compact($.u256), 2n ** 64n - 1n) +benchShape("compactU256 (128)", $.compact($.u256), 2n ** 128n - 1n) +benchShape("compactU256 (256)", $.compact($.u256), 2n ** 256n - 1n) diff --git a/codecs/bench/hex.bench.ts b/shapes/bench/hex.bench.ts similarity index 81% rename from codecs/bench/hex.bench.ts rename to shapes/bench/hex.bench.ts index 5a704ee..976073d 100644 --- a/codecs/bench/hex.bench.ts +++ b/shapes/bench/hex.bench.ts @@ -1,6 +1,6 @@ import * as $ from "../../mod.ts" import { decodeHex, encodeHex } from "../../mod.ts" -import { benchCodec } from "../../test-util.ts" +import { benchShape } from "../../test-util.ts" const words = await Deno.readFile("words.txt") const cargoLock = await Deno.readFile("Cargo.lock") @@ -27,10 +27,10 @@ for (const data of cases) { const $unsizedHex = $.hex($.uint8Array) for (const data of cases) { - benchCodec(`$unsizedHex ${data.length} bytes`, $unsizedHex, encodeHex(data)) + benchShape(`$unsizedHex ${data.length} bytes`, $unsizedHex, encodeHex(data)) } for (const data of cases) { const $sizedHex = $.hex($.sizedUint8Array(data.length)) - benchCodec(`$sizedHex ${data.length} bytes`, $sizedHex, encodeHex(data)) + benchShape(`$sizedHex ${data.length} bytes`, $sizedHex, encodeHex(data)) } diff --git a/shapes/bench/int.bench.ts b/shapes/bench/int.bench.ts new file mode 100644 index 0000000..034d4f5 --- /dev/null +++ b/shapes/bench/int.bench.ts @@ -0,0 +1,14 @@ +import * as $ from "../../mod.ts" +import { benchShape } from "../../test-util.ts" + +benchShape("u8", $.u8, 123) +benchShape("u16", $.u16, 123) +benchShape("u32", $.u32, 123) +benchShape("u64", $.u64, 123n) +benchShape("u128", $.u128, 123n) + +benchShape("i8", $.i8, 123) +benchShape("i16", $.i16, 123) +benchShape("i32", $.i32, 123) +benchShape("i64", $.i64, 123n) +benchShape("i128", $.i128, 123n) diff --git a/codecs/bench/object.bench.ts b/shapes/bench/object.bench.ts similarity index 73% rename from codecs/bench/object.bench.ts rename to shapes/bench/object.bench.ts index 780a0a5..4762172 100644 --- a/codecs/bench/object.bench.ts +++ b/shapes/bench/object.bench.ts @@ -1,23 +1,23 @@ import * as $ from "../../mod.ts" -import { benchCodec } from "../../test-util.ts" +import { benchShape } from "../../test-util.ts" // for comparison -benchCodec("u128", $.u128, 123n) +benchShape("u128", $.u128, 123n) -benchCodec("{}", $.object(), {}) -benchCodec("{ x: u128 }", $.field("x", $.u128), { x: 123n }) -benchCodec( +benchShape("{}", $.object(), {}) +benchShape("{ x: u128 }", $.field("x", $.u128), { x: 123n }) +benchShape( "{ x: u128, y: u128 }", $.object($.field("x", $.u128), $.field("y", $.u128)), { x: 123n, y: 456n }, ) -benchCodec( +benchShape( "{ x: u128, y: u128, z: u128 }", $.object($.field("x", $.u128), $.field("y", $.u128), $.field("z", $.u128)), { x: 123n, y: 456n, z: 789n }, ) -benchCodec( +benchShape( "Array<{ x: u128, y: u128, z: u128 }>", $.array($.object($.field("x", $.u128), $.field("y", $.u128), $.field("z", $.u128))), Array.from( @@ -30,4 +30,4 @@ const longKey = "thisIsTheKeyThatNeverEnds_itJustGoesRoundAndRoundMyFriends_somePeopleStartedWritingIt_notKnowingWhatItWas_andWeContinueWritingItForeverJustBecause_" .repeat(1000) -benchCodec("{ [longKey]: u128 }", $.object($.field(longKey, $.u128)), { [longKey]: 123n }) +benchShape("{ [longKey]: u128 }", $.object($.field(longKey, $.u128)), { [longKey]: 123n }) diff --git a/shapes/bench/option.bench.ts b/shapes/bench/option.bench.ts new file mode 100644 index 0000000..8480750 --- /dev/null +++ b/shapes/bench/option.bench.ts @@ -0,0 +1,8 @@ +import * as $ from "../../mod.ts" +import { benchShape } from "../../test-util.ts" + +benchShape("None", $.option($.bool), undefined) +benchShape("None", $.option($.u128), undefined) + +benchShape("Some", $.option($.bool), true) +benchShape("Some", $.option($.u128), 12345678901234567890n) diff --git a/shapes/bench/str.bench.ts b/shapes/bench/str.bench.ts new file mode 100644 index 0000000..a217bbc --- /dev/null +++ b/shapes/bench/str.bench.ts @@ -0,0 +1,19 @@ +import * as $ from "../../mod.ts" +import { benchShape, files } from "../../test-util.ts" + +const trolleybus = "🚎" +const special = "œ∑鮆¥üîøπåß∂ƒ©˙∆˚¬Ω≈ç√∫ñµ" +const lipsum = await files.lipsum() +const cargoLock = await files.cargoLock() + +benchShape(`""`, $.str, "") +benchShape(`"abc"`, $.str, "abc") +benchShape(`trolleybus`, $.str, trolleybus) +benchShape(`special`, $.str, special) +benchShape(`lipsum`, $.str, lipsum) +benchShape(`cargoLock`, $.str, cargoLock) +benchShape(`"abc" * 1000`, $.str, "abc".repeat(1000)) +benchShape(`trolleybus * 1000`, $.str, trolleybus.repeat(1000)) +benchShape(`special * 1000`, $.str, special.repeat(1000)) +benchShape(`lipsum * 1000`, $.str, lipsum.repeat(1000)) +benchShape(`cargoLock * 1000`, $.str, cargoLock.repeat(1000)) diff --git a/shapes/bench/tuple.bench.ts b/shapes/bench/tuple.bench.ts new file mode 100644 index 0000000..d586db9 --- /dev/null +++ b/shapes/bench/tuple.bench.ts @@ -0,0 +1,10 @@ +import * as $ from "../../mod.ts" +import { benchShape } from "../../test-util.ts" + +// for comparison +benchShape("u128", $.u128, 123n) + +benchShape("[]", $.tuple(), []) +benchShape("[u128]", $.tuple($.u128), [123n]) +benchShape("[u128, u128]", $.tuple($.u128, $.u128), [123n, 456n]) +benchShape("[u128, u128, u128]", $.tuple($.u128, $.u128, $.u128), [123n, 456n, 789n]) diff --git a/codecs/bitSequence.ts b/shapes/bitSequence.ts similarity index 84% rename from codecs/bitSequence.ts rename to shapes/bitSequence.ts index 70c07e6..6907bad 100644 --- a/codecs/bitSequence.ts +++ b/shapes/bitSequence.ts @@ -1,4 +1,4 @@ -import { Codec, createCodec, metadata } from "../common/mod.ts" +import { createShape, metadata, Shape } from "../common/mod.ts" import { compact } from "./compact.ts" import { u32 } from "./int.ts" @@ -66,19 +66,19 @@ Object.setPrototypeOf( }), ) -export const bitSequence: Codec = createCodec({ - _metadata: metadata("$.bitSequence"), - _staticSize: compactU32._staticSize, - _encode(buffer, value) { - compactU32._encode(buffer, value.length) +export const bitSequence: Shape = createShape({ + metadata: metadata("$.bitSequence"), + staticSize: compactU32.staticSize, + subEncode(buffer, value) { + compactU32.subEncode(buffer, value.length) buffer.insertArray(value.data) }, - _decode(buffer) { - const length = compactU32._decode(buffer) + subDecode(buffer) { + const length = compactU32.subDecode(buffer) const byteLength = Math.ceil(length / 8) return new BitSequence(length, buffer.array.subarray(buffer.index, buffer.index += byteLength)) }, - _assert(assert) { + subAssert(assert) { assert.instanceof(this, BitSequence) }, }) diff --git a/shapes/bool.ts b/shapes/bool.ts new file mode 100644 index 0000000..5f71a37 --- /dev/null +++ b/shapes/bool.ts @@ -0,0 +1,15 @@ +import { createShape, metadata, Shape } from "../common/mod.ts" + +export const bool: Shape = createShape({ + metadata: metadata("$.bool"), + staticSize: 1, + subEncode(buffer, value) { + buffer.array[buffer.index++] = +value + }, + subDecode(buffer) { + return !!buffer.array[buffer.index++]! + }, + subAssert(assert) { + assert.typeof(this, "boolean") + }, +}) diff --git a/codecs/collections.ts b/shapes/collections.ts similarity index 81% rename from codecs/collections.ts rename to shapes/collections.ts index 2d63815..6b473fb 100644 --- a/codecs/collections.ts +++ b/shapes/collections.ts @@ -1,34 +1,34 @@ -import { Codec, metadata, withMetadata } from "../common/mod.ts" +import { metadata, Shape, withMetadata } from "../common/mod.ts" import { iterable } from "./iterable.ts" import { encodeHexPrefixed } from "./mod.ts" import { tuple } from "./tuple.ts" export function map( - $key: Codec, - $value: Codec, -): Codec, ScaleMap> { - return withMetadata, ScaleMap>( + $key: Shape, + $value: Shape, +): Shape, ShapeMap> { + return withMetadata, ShapeMap>( metadata("$.map", map, $key, $value), iterable({ $el: tuple($key, $value), calcLength: (map) => map.size, - rehydrate: (values) => new ScaleMap($key, values), + rehydrate: (values) => new ShapeMap($key, values), assert(assert) { - assert.instanceof(this, ScaleMap) + assert.instanceof(this, ShapeMap) }, }), ) } -export function set($value: Codec): Codec, ScaleSet> { +export function set($value: Shape): Shape, ShapeSet> { return withMetadata( metadata("$.set", set, $value), iterable({ $el: $value, calcLength: (set) => set.size, - rehydrate: (values) => new ScaleSet($value, values), + rehydrate: (values) => new ShapeSet($value, values), assert(assert) { - assert.instanceof(this, ScaleSet) + assert.instanceof(this, ShapeSet) }, }), ) @@ -36,11 +36,11 @@ export function set($value: Codec): Codec, type Primitive = undefined | null | string | number | boolean | bigint | symbol -export class ScaleMap implements Map { +export class ShapeMap implements Map { #inner = new Map() #hexMemo = new WeakMap() - constructor(readonly $key: Codec, entries?: Iterable<[K, V]>) { + constructor(readonly $key: Shape, entries?: Iterable<[K, V]>) { if (entries) { for (const [key, value] of entries) { this.set(key, value) @@ -57,7 +57,7 @@ export class ScaleMap implements Map { } get [Symbol.toStringTag]() { - return "ScaleMap" + return "ShapeMap" } clear(): void { @@ -106,11 +106,11 @@ export class ScaleMap implements Map { } } -export class ScaleSet implements Set { +export class ShapeSet implements Set { #inner = new Map() #hexMemo = new WeakMap() - constructor(readonly $value: Codec, values?: Iterable) { + constructor(readonly $value: Shape, values?: Iterable) { if (values) { for (const value of values) { this.add(value) @@ -127,7 +127,7 @@ export class ScaleSet implements Set { } get [Symbol.toStringTag]() { - return "ScaleSet" + return "ShapeSet" } clear(): void { @@ -170,7 +170,7 @@ export class ScaleSet implements Set { } } -function transformKey($key: Codec, hexMemo: WeakMap, key: K): Primitive { +function transformKey($key: Shape, hexMemo: WeakMap, key: K): Primitive { if (typeof key === "string") { // This ensures that the hexes won't ever clash with regular string keys, // but leaves most string keys unchanged for performance diff --git a/codecs/compact.ts b/shapes/compact.ts similarity index 55% rename from codecs/compact.ts rename to shapes/compact.ts index 8f6f5b0..fa07781 100644 --- a/codecs/compact.ts +++ b/shapes/compact.ts @@ -1,5 +1,5 @@ -import { Codec, CodecVisitor, createCodec, metadata, ScaleDecodeError, withMetadata } from "../common/mod.ts" -import { AnyCodec } from "../mod.ts" +import { createShape, metadata, Shape, ShapeDecodeError, ShapeVisitor, withMetadata } from "../common/mod.ts" +import { AnyShape } from "../mod.ts" import { constant } from "./constant.ts" import { u128, u16, u256, u32, u64, u8 } from "./int.ts" import { field, object } from "./object.ts" @@ -9,49 +9,49 @@ const MAX_U6 = 0b00111111 const MAX_U14 = 0b00111111_11111111 const MAX_U30 = 0b00111111_11111111_11111111_11111111 -export const compactVisitor = new CodecVisitor() +export const compactVisitor = new ShapeVisitor() -export function compact(codec: Codec): Codec { - return compactVisitor.visit(codec) as any +export function compact(shape: Shape): Shape { + return compactVisitor.visit(shape) as any } -function compactNumber($base: Codec): Codec { - return createCodec({ - _metadata: metadata("$.compact", compact, $base), - _staticSize: 5, - _encode(buffer, value) { +function compactNumber($base: Shape): Shape { + return createShape({ + metadata: metadata("$.compact", compact, $base), + staticSize: 5, + subEncode(buffer, value) { if (value <= MAX_U6) { buffer.array[buffer.index++] = value << 2 } else if (value <= MAX_U14) { - u16._encode(buffer, (value << 2) | 0b01) + u16.subEncode(buffer, (value << 2) | 0b01) } else if (value <= MAX_U30) { // Because JS bitwise ops use *signed* 32-bit ints, this operation // produces negative values when `value >= 2 ** 29`. However, this is ok, // as `setUint32` correctly casts these negative values back to unsigned // 32-bit ints. - u32._encode(buffer, (value << 2) | 0b10) + u32.subEncode(buffer, (value << 2) | 0b10) } else { buffer.array[buffer.index++] = 0b11 - u32._encode(buffer, value) + u32.subEncode(buffer, value) } }, - _decode(buffer) { + subDecode(buffer) { switch (buffer.array[buffer.index]! & 0b11) { case 0: return buffer.array[buffer.index++]! >> 2 case 1: - return u16._decode(buffer) >> 2 + return u16.subDecode(buffer) >> 2 case 2: // We use an unsigned right shift, as the default shift operator // uses signed 32-bit ints, which would yield invalid values. - return u32._decode(buffer) >>> 2 + return u32.subDecode(buffer) >>> 2 default: - if (buffer.array[buffer.index++]! !== 3) throw new ScaleDecodeError(this, buffer, "Out of range for U32") - return u32._decode(buffer) + if (buffer.array[buffer.index++]! !== 3) throw new ShapeDecodeError(this, buffer, "Out of range for U32") + return u32.subDecode(buffer) } }, - _assert(assert) { - $base._assert(assert) + subAssert(assert) { + $base.subAssert(assert) }, }) } @@ -64,13 +64,13 @@ compactVisitor.add(u8, () => compactU8) compactVisitor.add(u16, () => compactU16) compactVisitor.add(u32, () => compactU32) -function compactBigInt($base: Codec): Codec { - return createCodec({ - _metadata: metadata("$.compact", compact, $base), - _staticSize: 5, - _encode(buffer, value) { +function compactBigInt($base: Shape): Shape { + return createShape({ + metadata: metadata("$.compact", compact, $base), + staticSize: 5, + subEncode(buffer, value) { if (value <= 0xff_ff_ff_ff) { - compactU32._encode(buffer, Number(value)) + compactU32.subEncode(buffer, Number(value)) return } let extraBytes = 0 @@ -80,7 +80,7 @@ function compactBigInt($base: Codec): Codec { extraBytes++ } buffer.array[buffer.index++] = (extraBytes << 2) | 0b11 - u32._encode(buffer, Number(value & 0xff_ff_ff_ffn)) + u32.subEncode(buffer, Number(value & 0xff_ff_ff_ffn)) _value = value >> 32n buffer.pushAlloc(extraBytes) for (let i = 0; i < extraBytes; i++) { @@ -89,21 +89,21 @@ function compactBigInt($base: Codec): Codec { } buffer.popAlloc() }, - _decode(buffer) { + subDecode(buffer) { const b = buffer.array[buffer.index]! if ((b & 0b11) < 3 || b === 3) { - return BigInt(compactU32._decode(buffer)) + return BigInt(compactU32.subDecode(buffer)) } const extraBytes = b >> 2 buffer.index++ - let value = BigInt(u32._decode(buffer)) + let value = BigInt(u32.subDecode(buffer)) for (let i = 0; i < extraBytes; i++) { value |= BigInt(buffer.array[buffer.index++]!) << BigInt(32 + i * 8) } return value }, - _assert(assert) { - $base._assert(assert) + subAssert(assert) { + $base.subAssert(assert) }, }) } @@ -116,20 +116,20 @@ compactVisitor.add(u64, () => compactU64) compactVisitor.add(u128, () => compactU128) compactVisitor.add(u256, () => compactU256) -compactVisitor.add(constant, (codec) => codec) +compactVisitor.add(constant, (shape) => shape) -compactVisitor.add(tuple, (codec, ...entries) => { - if (entries.length === 0) return codec - if (entries.length > 1) throw new Error("Cannot derive compact codec for tuples with more than one field") - return withMetadata(metadata("$.compact", compact, codec), tuple(compact(entries[0]!))) +compactVisitor.add(tuple, (shape, ...entries) => { + if (entries.length === 0) return shape + if (entries.length > 1) throw new Error("Cannot derive compact shape for tuples with more than one field") + return withMetadata(metadata("$.compact", compact, shape), tuple(compact(entries[0]!))) }) -compactVisitor.add(field, (codec, key, value) => { - return withMetadata(metadata("$.compact", compact, codec), field(key, compact(value))) +compactVisitor.add(field, (shape, key, value) => { + return withMetadata(metadata("$.compact", compact, shape), field(key, compact(value))) }) -compactVisitor.add(object, (codec, ...entries) => { - if (entries.length === 0) return codec - if (entries.length > 1) throw new Error("Cannot derive compact codec for objects with more than one field") - return withMetadata(metadata("$.compact", compact, codec), compact(entries[0]!)) +compactVisitor.add(object, (shape, ...entries) => { + if (entries.length === 0) return shape + if (entries.length > 1) throw new Error("Cannot derive compact shape for objects with more than one field") + return withMetadata(metadata("$.compact", compact, shape), compact(entries[0]!)) }) diff --git a/codecs/constant.ts b/shapes/constant.ts similarity index 54% rename from codecs/constant.ts rename to shapes/constant.ts index 17c2c5a..6152770 100644 --- a/codecs/constant.ts +++ b/shapes/constant.ts @@ -1,32 +1,32 @@ -import { Codec, createCodec, metadata, ScaleDecodeError } from "../common/mod.ts" +import { createShape, metadata, Shape, ShapeDecodeError } from "../common/mod.ts" -export function constant(value: T, codec: Pick, "encode">): Codec -export function constant(value: T, pattern?: Uint8Array): Codec -export function constant(value: T, c?: Pick, "encode"> | Uint8Array): Codec { +export function constant(value: T, shape: Pick, "encode">): Shape +export function constant(value: T, pattern?: Uint8Array): Shape +export function constant(value: T, c?: Pick, "encode"> | Uint8Array): Shape { const pattern = c && (c instanceof Uint8Array ? c : c.encode(value)) - return createCodec({ - _metadata: metadata("$.constant", constant, value, ...pattern ? [pattern] : []), - // We could set `_staticSize` to `pattern.length`, but in this case it will + return createShape({ + metadata: metadata("$.constant", constant, value, ...pattern ? [pattern] : []), + // We could set `staticSize` to `pattern.length`, but in this case it will // usually more efficient to insert `pattern` dynamically, rather than // manually copy the bytes. - _staticSize: 0, - _encode(buffer) { + staticSize: 0, + subEncode(buffer) { if (pattern) { buffer.insertArray(pattern) } }, - _decode(buffer) { + subDecode(buffer) { if (pattern) { const got = buffer.array.subarray(buffer.index, buffer.index += pattern.length) for (let i = 0; i < pattern.length; i++) { if (pattern[i] !== got[i]) { - throw new ScaleDecodeError(this, buffer, `Invalid pattern; expected ${hex(pattern)}, got ${hex(got)}`) + throw new ShapeDecodeError(this, buffer, `Invalid pattern; expected ${hex(pattern)}, got ${hex(got)}`) } } } return value }, - _assert(assert) { + subAssert(assert) { assert.equals(this, value) }, }) diff --git a/shapes/deferred.ts b/shapes/deferred.ts new file mode 100644 index 0000000..6057136 --- /dev/null +++ b/shapes/deferred.ts @@ -0,0 +1,28 @@ +import { createShape, metadata, Shape } from "../common/mod.ts" + +export function deferred(getShape: () => Shape): Shape { + let $shape: Shape + const shape = createShape({ + metadata: metadata("$.deferred", deferred, getShape), + staticSize: 0, + subEncode(buffer, value) { + $shape ??= getShape() + buffer.pushAlloc($shape.staticSize) + $shape.subEncode(buffer, value) + buffer.popAlloc() + }, + subDecode(buffer) { + $shape ??= getShape() + return $shape.subDecode(buffer) + }, + subAssert(assert) { + $shape ??= getShape() + $shape.subAssert(assert) + }, + }) + shape["_inspect"] = (inspect) => { + // Use ._inspect manually so that Deno doesn't detect the circularity + return `$.deferred(() => ${getShape()["_inspect"]!(inspect)})` + } + return shape +} diff --git a/shapes/documented.ts b/shapes/documented.ts new file mode 100644 index 0000000..8dcf9c1 --- /dev/null +++ b/shapes/documented.ts @@ -0,0 +1,8 @@ +import { metadata, Shape, withMetadata } from "../common/mod.ts" + +export function documented(docs: string, inner: Shape): Shape { + return withMetadata( + metadata("$.documented", documented, docs, inner) as never, + inner, + ) +} diff --git a/codecs/fixtures/array.rs b/shapes/fixtures/array.rs similarity index 100% rename from codecs/fixtures/array.rs rename to shapes/fixtures/array.rs diff --git a/codecs/fixtures/bitSequence.rs b/shapes/fixtures/bitSequence.rs similarity index 100% rename from codecs/fixtures/bitSequence.rs rename to shapes/fixtures/bitSequence.rs diff --git a/codecs/fixtures/bool.rs b/shapes/fixtures/bool.rs similarity index 100% rename from codecs/fixtures/bool.rs rename to shapes/fixtures/bool.rs diff --git a/codecs/fixtures/collections.rs b/shapes/fixtures/collections.rs similarity index 100% rename from codecs/fixtures/collections.rs rename to shapes/fixtures/collections.rs diff --git a/codecs/fixtures/compact.rs b/shapes/fixtures/compact.rs similarity index 100% rename from codecs/fixtures/compact.rs rename to shapes/fixtures/compact.rs diff --git a/codecs/fixtures/deferred.rs b/shapes/fixtures/deferred.rs similarity index 100% rename from codecs/fixtures/deferred.rs rename to shapes/fixtures/deferred.rs diff --git a/codecs/fixtures/float.rs b/shapes/fixtures/float.rs similarity index 100% rename from codecs/fixtures/float.rs rename to shapes/fixtures/float.rs diff --git a/codecs/fixtures/hex.rs b/shapes/fixtures/hex.rs similarity index 100% rename from codecs/fixtures/hex.rs rename to shapes/fixtures/hex.rs diff --git a/codecs/fixtures/int.rs b/shapes/fixtures/int.rs similarity index 100% rename from codecs/fixtures/int.rs rename to shapes/fixtures/int.rs diff --git a/codecs/fixtures/iterable.rs b/shapes/fixtures/iterable.rs similarity index 100% rename from codecs/fixtures/iterable.rs rename to shapes/fixtures/iterable.rs diff --git a/codecs/fixtures/lenPrefixed.rs b/shapes/fixtures/lenPrefixed.rs similarity index 100% rename from codecs/fixtures/lenPrefixed.rs rename to shapes/fixtures/lenPrefixed.rs diff --git a/codecs/fixtures/never.rs b/shapes/fixtures/never.rs similarity index 100% rename from codecs/fixtures/never.rs rename to shapes/fixtures/never.rs diff --git a/codecs/fixtures/object.rs b/shapes/fixtures/object.rs similarity index 100% rename from codecs/fixtures/object.rs rename to shapes/fixtures/object.rs diff --git a/codecs/fixtures/option.rs b/shapes/fixtures/option.rs similarity index 100% rename from codecs/fixtures/option.rs rename to shapes/fixtures/option.rs diff --git a/codecs/fixtures/optionBool.rs b/shapes/fixtures/optionBool.rs similarity index 100% rename from codecs/fixtures/optionBool.rs rename to shapes/fixtures/optionBool.rs diff --git a/codecs/fixtures/result.rs b/shapes/fixtures/result.rs similarity index 100% rename from codecs/fixtures/result.rs rename to shapes/fixtures/result.rs diff --git a/codecs/fixtures/str.rs b/shapes/fixtures/str.rs similarity index 100% rename from codecs/fixtures/str.rs rename to shapes/fixtures/str.rs diff --git a/codecs/fixtures/tuple.rs b/shapes/fixtures/tuple.rs similarity index 100% rename from codecs/fixtures/tuple.rs rename to shapes/fixtures/tuple.rs diff --git a/codecs/fixtures/union.rs b/shapes/fixtures/union.rs similarity index 100% rename from codecs/fixtures/union.rs rename to shapes/fixtures/union.rs diff --git a/codecs/float.ts b/shapes/float.ts similarity index 51% rename from codecs/float.ts rename to shapes/float.ts index 9ef3723..084b55b 100644 --- a/codecs/float.ts +++ b/shapes/float.ts @@ -1,18 +1,18 @@ -import { createCodec, metadata } from "../common/mod.ts" +import { createShape, metadata } from "../common/mod.ts" -export const f64 = createCodec({ - _metadata: metadata("$.f64"), - _staticSize: 8, - _encode(buffer, value) { +export const f64 = createShape({ + metadata: metadata("$.f64"), + staticSize: 8, + subEncode(buffer, value) { buffer.view.setFloat64(buffer.index, value, true) buffer.index += 8 }, - _decode(buffer) { + subDecode(buffer) { const value = buffer.view.getFloat64(buffer.index, true) buffer.index += 8 return value }, - _assert(assert) { + subAssert(assert) { assert.typeof(this, "number") }, }) diff --git a/codecs/hex.ts b/shapes/hex.ts similarity index 87% rename from codecs/hex.ts rename to shapes/hex.ts index f610d72..7122e93 100644 --- a/codecs/hex.ts +++ b/shapes/hex.ts @@ -1,4 +1,4 @@ -import { Codec, metadata, ScaleAssertError, withMetadata } from "../mod.ts" +import { metadata, Shape, ShapeAssertError, withMetadata } from "../mod.ts" import { transform } from "./transform.ts" const encodeLookup = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0")) @@ -31,7 +31,7 @@ export function decodeHex(hex: string): Uint8Array { } const hexRegex = /^(?:0x)?[\da-f]*$/i -export function hex($inner: Codec): Codec { +export function hex($inner: Shape): Shape { return withMetadata( metadata("$.hex", hex, $inner), transform({ @@ -41,7 +41,7 @@ export function hex($inner: Codec): Codec { assert(assert) { assert.typeof(this, "string") if (!hexRegex.test(assert.value as string)) { - throw new ScaleAssertError(this, assert.value, `${assert.path}: invalid hex`) + throw new ShapeAssertError(this, assert.value, `${assert.path}: invalid hex`) } }, }), diff --git a/shapes/instance.ts b/shapes/instance.ts new file mode 100644 index 0000000..d06377c --- /dev/null +++ b/shapes/instance.ts @@ -0,0 +1,23 @@ +import { AssertState } from "../common/assert.ts" +import { createShape, metadata, Shape } from "../common/mod.ts" + +export function instance( + ctor: new(...args: AO) => O, + $args: Shape, + toArgs: (value: I) => [...AI], +): Shape { + return createShape({ + metadata: metadata("$.instance", instance, ctor, $args, toArgs), + staticSize: $args.staticSize, + subEncode(buffer, value) { + $args.subEncode(buffer, toArgs(value)) + }, + subDecode(buffer) { + return new ctor(...$args.subDecode(buffer)) + }, + subAssert(assert) { + assert.instanceof(this, ctor) + $args.subAssert(new AssertState(toArgs(assert.value as I), "#arguments", assert)) + }, + }) +} diff --git a/codecs/int.ts b/shapes/int.ts similarity index 72% rename from codecs/int.ts rename to shapes/int.ts index d46e289..5de5b1a 100644 --- a/codecs/int.ts +++ b/shapes/int.ts @@ -1,39 +1,39 @@ -import { Codec, createCodec, metadata } from "../common/mod.ts" +import { createShape, metadata, Shape } from "../common/mod.ts" -export const u8 = createCodec({ - _metadata: intMetadata(false, 8), - _staticSize: 1, - _encode(buffer, value) { +export const u8 = createShape({ + metadata: intMetadata(false, 8), + staticSize: 1, + subEncode(buffer, value) { buffer.array[buffer.index++] = value }, - _decode(buffer) { + subDecode(buffer) { return buffer.array[buffer.index++]! }, - _assert(assert) { + subAssert(assert) { assert.integer(this, 0, 255) }, }) -function _intNumber(signed: boolean, size: 8 | 16 | 32): Codec { +function _intNumber(signed: boolean, size: 8 | 16 | 32): Shape { const byteSize = size / 8 const key = `${(signed ? "Int" : "Uint")}${size}` as const const getMethod = DataView.prototype[`get${key}`] const setMethod = DataView.prototype[`set${key}`] const min = signed ? -(2 ** (size - 1)) : 0 const max = (2 ** (size - +signed)) - 1 - return createCodec({ - _metadata: intMetadata(signed, size), - _staticSize: byteSize, - _encode(buffer, value) { + return createShape({ + metadata: intMetadata(signed, size), + staticSize: byteSize, + subEncode(buffer, value) { setMethod.call(buffer.view, buffer.index, value, true) buffer.index += byteSize }, - _decode(buffer) { + subDecode(buffer) { const value = getMethod.call(buffer.view, buffer.index, true) buffer.index += byteSize return value }, - _assert(assert) { + subAssert(assert) { assert.typeof(this, "number") assert.integer(this, min, max) }, @@ -46,23 +46,23 @@ export const i16 = _intNumber(true, 16) export const u32 = _intNumber(false, 32) export const i32 = _intNumber(true, 32) -function _intBigInt(signed: boolean, size: 64 | 128 | 256): Codec { +function _intBigInt(signed: boolean, size: 64 | 128 | 256): Shape { const byteSize = size / 8 const chunks = size / 64 const getMethod = DataView.prototype[signed ? "getBigInt64" : "getBigUint64"] const min = signed ? -(1n << BigInt(size - 1)) : 0n const max = (1n << BigInt(size - +signed)) - 1n - return createCodec({ - _metadata: intMetadata(signed, size), - _staticSize: byteSize, - _encode(buffer, value) { + return createShape({ + metadata: intMetadata(signed, size), + staticSize: byteSize, + subEncode(buffer, value) { for (let i = 0; i < chunks; i++) { buffer.view.setBigInt64(buffer.index, value, true) value >>= 64n buffer.index += 8 } }, - _decode(buffer) { + subDecode(buffer) { let value = getMethod.call(buffer.view, buffer.index + (byteSize - 8), true) for (let i = chunks - 2; i >= 0; i--) { value <<= 64n @@ -71,7 +71,7 @@ function _intBigInt(signed: boolean, size: 64 | 128 | 256): Codec { buffer.index += byteSize return value }, - _assert(assert) { + subAssert(assert) { assert.bigint(this, min, max) }, }) @@ -86,11 +86,11 @@ export const i256 = _intBigInt(true, 256) const intLookup = { u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, u256, i256 } -export function int(signed: boolean, size: 8 | 16 | 32): Codec -export function int(signed: boolean, size: 64 | 128 | 256): Codec -export function int(signed: boolean, size: 8 | 16 | 32 | 64 | 128 | 256): Codec | Codec -export function int(signed: boolean, size: 8 | 16 | 32 | 64 | 128 | 256): Codec -export function int(signed: boolean, size: 8 | 16 | 32 | 64 | 128 | 256): Codec { +export function int(signed: boolean, size: 8 | 16 | 32): Shape +export function int(signed: boolean, size: 64 | 128 | 256): Shape +export function int(signed: boolean, size: 8 | 16 | 32 | 64 | 128 | 256): Shape | Shape +export function int(signed: boolean, size: 8 | 16 | 32 | 64 | 128 | 256): Shape +export function int(signed: boolean, size: 8 | 16 | 32 | 64 | 128 | 256): Shape { const key = `${signed ? "i" : "u"}${size}` as const return intLookup[key] } diff --git a/codecs/iterable.ts b/shapes/iterable.ts similarity index 54% rename from codecs/iterable.ts rename to shapes/iterable.ts index 9bd364e..2122243 100644 --- a/codecs/iterable.ts +++ b/shapes/iterable.ts @@ -1,11 +1,11 @@ import { AssertState, - Codec, - createCodec, + createShape, metadata, - ScaleAssertError, - ScaleDecodeError, - ScaleEncodeError, + Shape, + ShapeAssertError, + ShapeDecodeError, + ShapeEncodeError, } from "../common/mod.ts" import { compact } from "./compact.ts" import { u32 } from "./int.ts" @@ -14,49 +14,49 @@ const compactU32 = compact(u32) export function iterable, TO = TI, O = I>( props: { - $el: Codec + $el: Shape calcLength: (iterable: I) => number rehydrate: (iterable: Iterable) => O - assert: (this: Codec, assert: AssertState) => void + assert: (this: Shape, assert: AssertState) => void }, -): Codec { - return createCodec({ - _metadata: metadata("$.iterable", iterable, props), - _staticSize: compactU32._staticSize, - _encode(buffer, value) { +): Shape { + return createShape({ + metadata: metadata("$.iterable", iterable, props), + staticSize: compactU32.staticSize, + subEncode(buffer, value) { const length = props.calcLength(value) - compactU32._encode(buffer, length) - buffer.pushAlloc(length * props.$el._staticSize) + compactU32.subEncode(buffer, length) + buffer.pushAlloc(length * props.$el.staticSize) let i = 0 for (const el of value) { - props.$el._encode(buffer, el) + props.$el.subEncode(buffer, el) i++ } - if (i !== length) throw new ScaleEncodeError(this, value, "Incorrect length returned by calcLength") + if (i !== length) throw new ShapeEncodeError(this, value, "Incorrect length returned by calcLength") buffer.popAlloc() }, - _decode(buffer) { - const length = compactU32._decode(buffer) + subDecode(buffer) { + const length = compactU32.subDecode(buffer) let done = false const value = props.rehydrate(function*() { for (let i = 0; i < length; i++) { - yield props.$el._decode(buffer) + yield props.$el.subDecode(buffer) } done = true }()) - if (!done) throw new ScaleDecodeError(this, buffer, "Iterable passed to rehydrate must be immediately exhausted") + if (!done) throw new ShapeDecodeError(this, buffer, "Iterable passed to rehydrate must be immediately exhausted") return value }, - _assert(assert) { + subAssert(assert) { props.assert.call(this, assert) const length = props.calcLength(assert.value as I) let i = 0 for (const el of assert.value as I) { - props.$el._assert(new AssertState(el, `#iterator[${i}]`)) + props.$el.subAssert(new AssertState(el, `#iterator[${i}]`)) i++ } if (i !== length) { - throw new ScaleAssertError(this, assert.value, `${assert.path}: Incorrect length returned by calcLength`) + throw new ShapeAssertError(this, assert.value, `${assert.path}: Incorrect length returned by calcLength`) } }, }) diff --git a/shapes/lenPrefixed.ts b/shapes/lenPrefixed.ts new file mode 100644 index 0000000..6f066a4 --- /dev/null +++ b/shapes/lenPrefixed.ts @@ -0,0 +1,30 @@ +import { createShape, metadata, Shape } from "../common/mod.ts" +import { compact } from "./compact.ts" +import { u32 } from "./int.ts" + +const compactU32 = compact(u32) + +export function lenPrefixed($inner: Shape): Shape { + return createShape({ + metadata: metadata("$.lenPrefixed", lenPrefixed, $inner), + staticSize: compactU32.staticSize + $inner.staticSize, + subEncode(buffer, extrinsic) { + const lengthCursor = buffer.createCursor(compactU32.staticSize) + const contentCursor = buffer.createCursor($inner.staticSize) + $inner.subEncode(contentCursor, extrinsic) + buffer.waitForBuffer(contentCursor, () => { + const length = contentCursor.finishedSize + contentCursor.index + compactU32.subEncode(lengthCursor, length) + lengthCursor.close() + contentCursor.close() + }) + }, + subDecode(buffer) { + const length = compactU32.subDecode(buffer) + return $inner.decode(buffer.array.subarray(buffer.index, buffer.index += length)) + }, + subAssert(assert) { + $inner.subAssert(assert) + }, + }) +} diff --git a/codecs/mod.ts b/shapes/mod.ts similarity index 100% rename from codecs/mod.ts rename to shapes/mod.ts diff --git a/shapes/never.ts b/shapes/never.ts new file mode 100644 index 0000000..7dfaa10 --- /dev/null +++ b/shapes/never.ts @@ -0,0 +1,15 @@ +import { createShape, metadata, Shape, ShapeAssertError, ShapeDecodeError, ShapeEncodeError } from "../common/mod.ts" + +export const never: Shape = createShape({ + metadata: metadata("$.never"), + staticSize: 0, + subEncode(value) { + throw new ShapeEncodeError(this, value, "Cannot encode $.never") + }, + subDecode(buffer) { + throw new ShapeDecodeError(this, buffer, "Cannot decode $.never") + }, + subAssert(assert) { + throw new ShapeAssertError(this, assert.value, `${assert.path}: Cannot validate $.never`) + }, +}) diff --git a/codecs/object.ts b/shapes/object.ts similarity index 53% rename from codecs/object.ts rename to shapes/object.ts index 36288b1..eb7a391 100644 --- a/codecs/object.ts +++ b/shapes/object.ts @@ -1,55 +1,55 @@ -import { AnyCodec, Codec, CodecVisitor, createCodec, Expand, Input, metadata, Output, U2I } from "../common/mod.ts" +import { AnyShape, createShape, Expand, Input, metadata, Output, Shape, ShapeVisitor, U2I } from "../common/mod.ts" import { constant } from "./constant.ts" import { option } from "./option.ts" -export function field(key: K, $value: Codec): Codec< +export function field(key: K, $value: Shape): Shape< Expand>>, Expand> > { - return createCodec({ - _metadata: metadata("$.field", field, key, $value), - _staticSize: $value._staticSize, - _encode(buffer, value) { - $value._encode(buffer, value[key]) + return createShape({ + metadata: metadata("$.field", field, key, $value), + staticSize: $value.staticSize, + subEncode(buffer, value) { + $value.subEncode(buffer, value[key]) }, - _decode(buffer) { - return { [key]: $value._decode(buffer) } as any + subDecode(buffer) { + return { [key]: $value.subDecode(buffer) } as any }, - _assert(assert) { - $value._assert(assert.key(this, key)) + subAssert(assert) { + $value.subAssert(assert.key(this, key)) }, }) } -export function optionalField(key: K, $value: Codec): Codec< +export function optionalField(key: K, $value: Shape): Shape< Expand>>>, Expand>> > { const $option = option($value) - return createCodec({ - _metadata: metadata("$.optionalField", optionalField, key, $value), - _staticSize: $value._staticSize, - _encode(buffer, value) { - $option._encode(buffer, value[key]) + return createShape({ + metadata: metadata("$.optionalField", optionalField, key, $value), + staticSize: $value.staticSize, + subEncode(buffer, value) { + $option.subEncode(buffer, value[key]) }, - _decode(buffer) { + subDecode(buffer) { if (buffer.array[buffer.index++]) { - return { [key]: $value._decode(buffer) } as any + return { [key]: $value.subDecode(buffer) } as any } else { return {} } }, - _assert(assert) { + subAssert(assert) { assert.typeof(this, "object") assert.nonNull(this) if (key in (assert.value as any)) { - $option._assert(assert.key(this, key)) + $option.subAssert(assert.key(this, key)) } }, }) } -export type InputObject = Expand< +export type InputObject = Expand< U2I< | { x: {} } | { @@ -57,7 +57,7 @@ export type InputObject = Expand< }[number] >["x"] > -export type OutputObject = Expand< +export type OutputObject = Expand< U2I< | { x: {} } | { @@ -67,52 +67,52 @@ export type OutputObject = Expand< > type UnionKeys = T extends T ? keyof T : never -export type ObjectMembers = [ +export type ObjectMembers = [ ...never extends T ? { - [K in keyof T]: AnyCodec extends T[K] ? AnyCodec + [K in keyof T]: AnyShape extends T[K] ? AnyShape : & UnionKeys> & { [L in keyof T]: K extends L ? never : UnionKeys> }[number] extends (infer O extends keyof any) - ? [O] extends [never] ? Codec & {}> : Codec<{ [_ in O]?: never }> + ? [O] extends [never] ? Shape & {}> : Shape<{ [_ in O]?: never }> : never } : T, ] -export function object(...members: ObjectMembers): Codec, OutputObject> { - return createCodec({ - _metadata: metadata("$.object", object, ...members), - _staticSize: members.map((x) => x._staticSize).reduce((a, b) => a + b, 0), - _encode: generateEncode(members as Codec[]), - _decode: generateDecode(members as Codec[]), - _assert(assert) { +export function object(...members: ObjectMembers): Shape, OutputObject> { + return createShape({ + metadata: metadata("$.object", object, ...members), + staticSize: members.map((x) => x.staticSize).reduce((a, b) => a + b, 0), + subEncode: generateEncode(members as Shape[]), + subDecode: generateDecode(members as Shape[]), + subAssert(assert) { assert.typeof(this, "object") assert.nonNull(this) for (const member of members as T) { - member._assert(assert) + member.subAssert(assert) } }, }) } -function generateEncode(members: Codec[]) { +function generateEncode(members: Shape[]) { const vars: string[] = [] const args: unknown[] = [] - const valueVisitor = new CodecVisitor<(v: string) => string>() - valueVisitor.add(constant, (codec, value, pattern) => (v) => { + const valueVisitor = new ShapeVisitor<(v: string) => string>() + valueVisitor.add(constant, (shape, value, pattern) => (v) => { if (pattern) { - return `${addVar(codec)}._encode(buffer, ${v})` + return `${addVar(shape)}.subEncode(buffer, ${v})` } return addVar(value) }) - valueVisitor.fallback((codec) => (v) => { - return `${addVar(codec)}._encode(buffer, ${v})` + valueVisitor.fallback((shape) => (v) => { + return `${addVar(shape)}.subEncode(buffer, ${v})` }) - const fieldVisitor = new CodecVisitor() + const fieldVisitor = new ShapeVisitor() fieldVisitor.add(field, (_, key, value) => { return valueVisitor.visit(value)(`value[${typeof key === "symbol" ? addVar(key) : JSON.stringify(key)}]`) }) @@ -122,8 +122,8 @@ function generateEncode(members: Codec[]) { fieldVisitor.add(object, (_, ...members) => { return members.map((x) => fieldVisitor.visit(x)).join(";") }) - fieldVisitor.fallback((codec) => { - return `${addVar(codec)}._encode(buffer, value)` + fieldVisitor.fallback((shape) => { + return `${addVar(shape)}.subEncode(buffer, value)` }) const content = members.map((x) => fieldVisitor.visit(x)).join(";") @@ -138,21 +138,21 @@ function generateEncode(members: Codec[]) { } } -function generateDecode(members: Codec[]) { +function generateDecode(members: Shape[]) { const vars: string[] = [] const args: unknown[] = [] - const valueVisitor = new CodecVisitor() - valueVisitor.add(constant, (codec, value, pattern) => { + const valueVisitor = new ShapeVisitor() + valueVisitor.add(constant, (shape, value, pattern) => { if (pattern) { - return `${addVar(codec)}._decode(buffer)` + return `${addVar(shape)}.subDecode(buffer)` } return addVar(value) }) - valueVisitor.fallback((codec) => { - return `${addVar(codec)}._decode(buffer)` + valueVisitor.fallback((shape) => { + return `${addVar(shape)}.subDecode(buffer)` }) - const fieldVisitor = new CodecVisitor() + const fieldVisitor = new ShapeVisitor() fieldVisitor.add(field, (_, key, value) => { return `[${typeof key === "symbol" ? addVar(key) : JSON.stringify(key)}]: ${valueVisitor.visit(value)}` }) @@ -162,8 +162,8 @@ function generateDecode(members: Codec[]) { fieldVisitor.add(object, (_, ...members) => { return members.map((x) => fieldVisitor.visit(x)).join(",") }) - fieldVisitor.fallback((codec) => { - return `...${addVar(codec)}._decode(buffer)` + fieldVisitor.fallback((shape) => { + return `...${addVar(shape)}.subDecode(buffer)` }) const content = members.map((x) => fieldVisitor.visit(x)).join(",") diff --git a/shapes/option.ts b/shapes/option.ts new file mode 100644 index 0000000..9fa152c --- /dev/null +++ b/shapes/option.ts @@ -0,0 +1,37 @@ +import { createShape, metadata, Shape, ShapeDecodeError } from "../common/mod.ts" + +export function option($some: Shape): Shape +export function option($some: Shape, none: N): Shape +export function option($some: Shape, none?: N): Shape { + if ($some.metadata.some((x) => x.factory === option && x.args[1] === none)) { + throw new Error("Nested option shape will not roundtrip correctly") + } + return createShape({ + metadata: metadata("$.option", option, $some, ...(none === undefined ? [] : [none!]) as [N]), + staticSize: 1 + $some.staticSize, + subEncode(buffer, value) { + if ((buffer.array[buffer.index++] = +(value !== none))) { + $some.subEncode(buffer, value as SI) + } + }, + subDecode(buffer) { + switch (buffer.array[buffer.index++]) { + case 0: + return none as N + case 1: { + const value = $some.subDecode(buffer) + if (value === none) { + throw new ShapeDecodeError(this, buffer, "Some(None) will not roundtrip correctly") + } + return value + } + default: + throw new ShapeDecodeError(this, buffer, "Option discriminant neither 0 nor 1") + } + }, + subAssert(assert) { + if (assert.value === none) return + $some.subAssert(assert) + }, + }) +} diff --git a/codecs/optionBool.ts b/shapes/optionBool.ts similarity index 50% rename from codecs/optionBool.ts rename to shapes/optionBool.ts index c0c7aff..ab7a40e 100644 --- a/codecs/optionBool.ts +++ b/shapes/optionBool.ts @@ -1,16 +1,16 @@ -import { Codec, createCodec, metadata } from "../common/mod.ts" +import { createShape, metadata, Shape } from "../common/mod.ts" -export const optionBool: Codec = createCodec({ - _metadata: metadata("$.optionBool"), - _staticSize: 1, - _encode(buffer, value) { +export const optionBool: Shape = createShape({ + metadata: metadata("$.optionBool"), + staticSize: 1, + subEncode(buffer, value) { buffer.array[buffer.index++] = value === undefined ? 0 : 1 + +!value }, - _decode(buffer) { + subDecode(buffer) { const byte = buffer.array[buffer.index++]! return byte === 0 ? undefined : !(byte - 1) }, - _assert(assert) { + subAssert(assert) { if (assert.value === undefined) return assert.typeof(this, "boolean") }, diff --git a/shapes/promise.ts b/shapes/promise.ts new file mode 100644 index 0000000..631a951 --- /dev/null +++ b/shapes/promise.ts @@ -0,0 +1,19 @@ +import { createShape, metadata, Shape } from "../common/mod.ts" + +export function promise($value: Shape): Shape, Promise> { + return createShape({ + metadata: metadata("$.promise", promise, $value), + staticSize: $value.staticSize, + subEncode(buffer, value) { + buffer.writeAsync($value.staticSize, async (buffer) => { + $value.subEncode(buffer, await value) + }) + }, + subDecode(buffer) { + return Promise.resolve($value.subDecode(buffer)) + }, + subAssert(assert) { + assert.instanceof(this, Promise) + }, + }) +} diff --git a/codecs/record.ts b/shapes/record.ts similarity index 74% rename from codecs/record.ts rename to shapes/record.ts index 44f7fc8..e29188e 100644 --- a/codecs/record.ts +++ b/shapes/record.ts @@ -1,10 +1,10 @@ -import { Codec } from "../common/mod.ts" +import { Shape } from "../common/mod.ts" import { array } from "./array.ts" import { str } from "./str.ts" import { transform } from "./transform.ts" import { tuple } from "./tuple.ts" -export function record($value: Codec): Codec>, Record> { +export function record($value: Shape): Shape>, Record> { return transform({ $base: array(tuple(str, $value)), encode: Object.entries, diff --git a/shapes/result.ts b/shapes/result.ts new file mode 100644 index 0000000..55cf726 --- /dev/null +++ b/shapes/result.ts @@ -0,0 +1,49 @@ +import { createShape, metadata, Shape, ShapeDecodeError } from "../common/mod.ts" + +export function result( + $ok: Shape, + $err: Shape, +): Shape { + if ($ok.metadata.some((x) => x.factory === result)) { + throw new Error("Nested result shape will not roundtrip correctly") + } + return createShape({ + metadata: metadata("$.result", result, $ok, $err), + staticSize: 1 + Math.max($ok.staticSize, $err.staticSize), + subEncode(buffer, value) { + if ((buffer.array[buffer.index++] = +(value instanceof Error))) { + $err.subEncode(buffer, value as UI) + } else { + $ok.subEncode(buffer, value as TI) + } + }, + subDecode(buffer) { + switch (buffer.array[buffer.index++]) { + case 0: { + const value = $ok.subDecode(buffer) + if (value instanceof Error) { + throw new ShapeDecodeError( + this, + buffer, + "An ok value that is instanceof Error will not roundtrip correctly", + ) + } + return value + } + case 1: { + return $err.subDecode(buffer) + } + default: { + throw new ShapeDecodeError(this, buffer, "Result discriminant neither 0 nor 1") + } + } + }, + subAssert(assert) { + if (assert.value instanceof Error) { + $err.subAssert(assert) + } else { + $ok.subAssert(assert) + } + }, + }) +} diff --git a/codecs/str.ts b/shapes/str.ts similarity index 57% rename from codecs/str.ts rename to shapes/str.ts index 9d5bcc4..f3a5e5e 100644 --- a/codecs/str.ts +++ b/shapes/str.ts @@ -1,4 +1,4 @@ -import { Codec, createCodec, metadata, ScaleDecodeError } from "../common/mod.ts" +import { createShape, metadata, Shape, ShapeDecodeError } from "../common/mod.ts" import { compact } from "./compact.ts" import { u32 } from "./int.ts" @@ -6,24 +6,24 @@ const compactU32 = compact(u32) const textEncoder = new TextEncoder() const textDecoder = new TextDecoder() -export const str: Codec = createCodec({ - _metadata: metadata("$.str"), - _staticSize: compactU32._staticSize, - _encode(buffer, value) { +export const str: Shape = createShape({ + metadata: metadata("$.str"), + staticSize: compactU32.staticSize, + subEncode(buffer, value) { const array = textEncoder.encode(value) - compactU32._encode(buffer, array.length) + compactU32.subEncode(buffer, array.length) buffer.insertArray(array) }, - _decode(buffer) { - const len = compactU32._decode(buffer) + subDecode(buffer) { + const len = compactU32.subDecode(buffer) if (buffer.array.length < buffer.index + len) { - throw new ScaleDecodeError(this, buffer, "Attempting to `str`-decode beyond bounds of input bytes") + throw new ShapeDecodeError(this, buffer, "Attempting to `str`-decode beyond bounds of input bytes") } const slice = buffer.array.subarray(buffer.index, buffer.index + len) buffer.index += len return textDecoder.decode(slice) }, - _assert(assert) { + subAssert(assert) { assert.typeof(this, "string") }, }) diff --git a/codecs/test/__snapshots__/array.test.ts.snap b/shapes/test/__snapshots__/array.test.ts.snap similarity index 60% rename from codecs/test/__snapshots__/array.test.ts.snap rename to shapes/test/__snapshots__/array.test.ts.snap index 557f1a7..262e7b4 100644 --- a/codecs/test/__snapshots__/array.test.ts.snap +++ b/shapes/test/__snapshots__/array.test.ts.snap @@ -37,15 +37,15 @@ snapshot[`\$.array(\$.u8) [ 16, 17, 18, 19, 20, 21 ] 1`] = ` 15 `; -snapshot[`\$.array(\$.u8) invalid null 1`] = `ScaleAssertError: !(value instanceof Array)`; +snapshot[`\$.array(\$.u8) invalid null 1`] = `ShapeAssertError: !(value instanceof Array)`; -snapshot[`\$.array(\$.u8) invalid undefined 1`] = `ScaleAssertError: !(value instanceof Array)`; +snapshot[`\$.array(\$.u8) invalid undefined 1`] = `ShapeAssertError: !(value instanceof Array)`; -snapshot[`\$.array(\$.u8) invalid 123 1`] = `ScaleAssertError: !(value instanceof Array)`; +snapshot[`\$.array(\$.u8) invalid 123 1`] = `ShapeAssertError: !(value instanceof Array)`; -snapshot[`\$.array(\$.u8) invalid [ "abc" ] 1`] = `ScaleAssertError: typeof value[0] !== "number"`; +snapshot[`\$.array(\$.u8) invalid [ "abc" ] 1`] = `ShapeAssertError: typeof value[0] !== "number"`; -snapshot[`\$.array(\$.u8) invalid [ 1, 2, 3, -1, 4 ] 1`] = `ScaleAssertError: value[3] < 0`; +snapshot[`\$.array(\$.u8) invalid [ 1, 2, 3, -1, 4 ] 1`] = `ShapeAssertError: value[3] < 0`; snapshot[`\$.sizedArray(\$.u8, 1) [ 1 ] 1`] = `01`; @@ -157,15 +157,15 @@ snapshot[`\$.sizedArray(\$.u8, 100) [1] * 100 1`] = ` 01 `; -snapshot[`\$.sizedArray(\$.u8, 3) invalid null 1`] = `ScaleAssertError: !(value instanceof Array)`; +snapshot[`\$.sizedArray(\$.u8, 3) invalid null 1`] = `ShapeAssertError: !(value instanceof Array)`; -snapshot[`\$.sizedArray(\$.u8, 3) invalid undefined 1`] = `ScaleAssertError: !(value instanceof Array)`; +snapshot[`\$.sizedArray(\$.u8, 3) invalid undefined 1`] = `ShapeAssertError: !(value instanceof Array)`; -snapshot[`\$.sizedArray(\$.u8, 3) invalid 123 1`] = `ScaleAssertError: !(value instanceof Array)`; +snapshot[`\$.sizedArray(\$.u8, 3) invalid 123 1`] = `ShapeAssertError: !(value instanceof Array)`; -snapshot[`\$.sizedArray(\$.u8, 3) invalid [] 1`] = `ScaleAssertError: value.length !== 3`; +snapshot[`\$.sizedArray(\$.u8, 3) invalid [] 1`] = `ShapeAssertError: value.length !== 3`; -snapshot[`\$.sizedArray(\$.u8, 3) invalid [ 1, 2, -1 ] 1`] = `ScaleAssertError: value[2] < 0`; +snapshot[`\$.sizedArray(\$.u8, 3) invalid [ 1, 2, -1 ] 1`] = `ShapeAssertError: value[2] < 0`; snapshot[`\$.uint8Array Uint8Array(5) [ 1, 2, 3, 4, 5 ] 1`] = ` 14 @@ -204,15 +204,15 @@ snapshot[`\$.uint8Array Uint8Array(6) [ 16, 17, 18, 19, 20, 21 ] 1`] = ` 15 `; -snapshot[`\$.uint8Array invalid null 1`] = `ScaleAssertError: !(value instanceof Uint8Array)`; +snapshot[`\$.uint8Array invalid null 1`] = `ShapeAssertError: !(value instanceof Uint8Array)`; -snapshot[`\$.uint8Array invalid undefined 1`] = `ScaleAssertError: !(value instanceof Uint8Array)`; +snapshot[`\$.uint8Array invalid undefined 1`] = `ShapeAssertError: !(value instanceof Uint8Array)`; -snapshot[`\$.uint8Array invalid 123 1`] = `ScaleAssertError: !(value instanceof Uint8Array)`; +snapshot[`\$.uint8Array invalid 123 1`] = `ShapeAssertError: !(value instanceof Uint8Array)`; -snapshot[`\$.uint8Array invalid [] 1`] = `ScaleAssertError: !(value instanceof Uint8Array)`; +snapshot[`\$.uint8Array invalid [] 1`] = `ShapeAssertError: !(value instanceof Uint8Array)`; -snapshot[`\$.uint8Array invalid [ 1, 2, -1 ] 1`] = `ScaleAssertError: !(value instanceof Uint8Array)`; +snapshot[`\$.uint8Array invalid [ 1, 2, -1 ] 1`] = `ShapeAssertError: !(value instanceof Uint8Array)`; snapshot[`\$.sizedUint8Array(4) Uint8Array(4) [ 0, 0, 0, 0 ] 1`] = ` 00 @@ -228,12 +228,12 @@ snapshot[`\$.sizedUint8Array(4) Uint8Array(4) [ 1, 2, 3, 4 ] 1`] = ` 04 `; -snapshot[`\$.sizedUint8Array(4) invalid null 1`] = `ScaleAssertError: !(value instanceof Uint8Array)`; +snapshot[`\$.sizedUint8Array(4) invalid null 1`] = `ShapeAssertError: !(value instanceof Uint8Array)`; -snapshot[`\$.sizedUint8Array(4) invalid undefined 1`] = `ScaleAssertError: !(value instanceof Uint8Array)`; +snapshot[`\$.sizedUint8Array(4) invalid undefined 1`] = `ShapeAssertError: !(value instanceof Uint8Array)`; -snapshot[`\$.sizedUint8Array(4) invalid [ 1, 2, 3, 4 ] 1`] = `ScaleAssertError: !(value instanceof Uint8Array)`; +snapshot[`\$.sizedUint8Array(4) invalid [ 1, 2, 3, 4 ] 1`] = `ShapeAssertError: !(value instanceof Uint8Array)`; -snapshot[`\$.sizedUint8Array(4) invalid Uint8Array(0) [] 1`] = `ScaleAssertError: value.length !== 4`; +snapshot[`\$.sizedUint8Array(4) invalid Uint8Array(0) [] 1`] = `ShapeAssertError: value.length !== 4`; -snapshot[`\$.sizedUint8Array(4) invalid Uint8Array(5) [ 0, 0, 0, 0, 0 ] 1`] = `ScaleAssertError: value.length !== 4`; +snapshot[`\$.sizedUint8Array(4) invalid Uint8Array(5) [ 0, 0, 0, 0, 0 ] 1`] = `ShapeAssertError: value.length !== 4`; diff --git a/codecs/test/__snapshots__/bitSequence.test.ts.snap b/shapes/test/__snapshots__/bitSequence.test.ts.snap similarity index 53% rename from codecs/test/__snapshots__/bitSequence.test.ts.snap rename to shapes/test/__snapshots__/bitSequence.test.ts.snap index 3fe2aee..c414faa 100644 --- a/codecs/test/__snapshots__/bitSequence.test.ts.snap +++ b/shapes/test/__snapshots__/bitSequence.test.ts.snap @@ -8,14 +8,14 @@ snapshot[`\$.bitSequence BitSequence { length: 13, data: Uint8Array(2) [ 106, 40 28 `; -snapshot[`\$.bitSequence invalid null 1`] = `ScaleAssertError: !(value instanceof BitSequence)`; +snapshot[`\$.bitSequence invalid null 1`] = `ShapeAssertError: !(value instanceof BitSequence)`; -snapshot[`\$.bitSequence invalid undefined 1`] = `ScaleAssertError: !(value instanceof BitSequence)`; +snapshot[`\$.bitSequence invalid undefined 1`] = `ShapeAssertError: !(value instanceof BitSequence)`; -snapshot[`\$.bitSequence invalid 123 1`] = `ScaleAssertError: !(value instanceof BitSequence)`; +snapshot[`\$.bitSequence invalid 123 1`] = `ShapeAssertError: !(value instanceof BitSequence)`; -snapshot[`\$.bitSequence invalid [] 1`] = `ScaleAssertError: !(value instanceof BitSequence)`; +snapshot[`\$.bitSequence invalid [] 1`] = `ShapeAssertError: !(value instanceof BitSequence)`; -snapshot[`\$.bitSequence invalid {} 1`] = `ScaleAssertError: !(value instanceof BitSequence)`; +snapshot[`\$.bitSequence invalid {} 1`] = `ShapeAssertError: !(value instanceof BitSequence)`; -snapshot[`\$.bitSequence invalid [ true, false ] 1`] = `ScaleAssertError: !(value instanceof BitSequence)`; +snapshot[`\$.bitSequence invalid [ true, false ] 1`] = `ShapeAssertError: !(value instanceof BitSequence)`; diff --git a/shapes/test/__snapshots__/bool.test.ts.snap b/shapes/test/__snapshots__/bool.test.ts.snap new file mode 100644 index 0000000..e8c1e77 --- /dev/null +++ b/shapes/test/__snapshots__/bool.test.ts.snap @@ -0,0 +1,15 @@ +export const snapshot = {}; + +snapshot[`\$.bool true 1`] = `01`; + +snapshot[`\$.bool false 1`] = `00`; + +snapshot[`\$.bool invalid null 1`] = `ShapeAssertError: typeof value !== "boolean"`; + +snapshot[`\$.bool invalid undefined 1`] = `ShapeAssertError: typeof value !== "boolean"`; + +snapshot[`\$.bool invalid 123 1`] = `ShapeAssertError: typeof value !== "boolean"`; + +snapshot[`\$.bool invalid [] 1`] = `ShapeAssertError: typeof value !== "boolean"`; + +snapshot[`\$.bool invalid {} 1`] = `ShapeAssertError: typeof value !== "boolean"`; diff --git a/shapes/test/__snapshots__/collections.test.ts.snap b/shapes/test/__snapshots__/collections.test.ts.snap new file mode 100644 index 0000000..d60d993 --- /dev/null +++ b/shapes/test/__snapshots__/collections.test.ts.snap @@ -0,0 +1,90 @@ +export const snapshot = {}; + +snapshot[`\$.set(\$.u8) ShapeSet { "\$value": \$.u8 } 1`] = `00`; + +snapshot[`\$.set(\$.u8) ShapeSet { "\$value": \$.u8 } 2`] = ` +10 +00 +02 +04 +08 +`; + +snapshot[`\$.set(\$.u8) ShapeSet { "\$value": \$.u8 } 3`] = ` +10 +02 +03 +05 +07 +`; + +snapshot[`\$.set(\$.u8) invalid null 1`] = `ShapeAssertError: !(value instanceof ShapeSet)`; + +snapshot[`\$.set(\$.u8) invalid undefined 1`] = `ShapeAssertError: !(value instanceof ShapeSet)`; + +snapshot[`\$.set(\$.u8) invalid 123 1`] = `ShapeAssertError: !(value instanceof ShapeSet)`; + +snapshot[`\$.set(\$.u8) invalid [ 123 ] 1`] = `ShapeAssertError: !(value instanceof ShapeSet)`; + +snapshot[`\$.set(\$.u8) invalid Set(1) { null } 1`] = `ShapeAssertError: !(value instanceof ShapeSet)`; + +snapshot[`\$.set(\$.u8) invalid Set(5) { 1, 2, 3, -1, 4 } 1`] = `ShapeAssertError: !(value instanceof ShapeSet)`; + +snapshot[`\$.set(\$.u8) invalid ShapeSet { "\$value": \$.i8 } 1`] = `ShapeAssertError: #iterator[3] < 0`; + +snapshot[`\$.map(\$.str, \$.u8) ShapeMap { "\$key": \$.str } 1`] = `00`; + +snapshot[`\$.map(\$.str, \$.u8) ShapeMap { "\$key": \$.str } 2`] = ` +08 +04 +30 +00 +04 +31 +01 +`; + +snapshot[`\$.map(\$.str, \$.u8) ShapeMap { "\$key": \$.str } 3`] = ` +14 +0c +32 +5e +30 +00 +0c +32 +5e +31 +02 +0c +32 +5e +32 +04 +0c +32 +5e +33 +08 +0c +32 +5e +34 +10 +`; + +snapshot[`\$.map(\$.str, \$.u8) invalid null 1`] = `ShapeAssertError: !(value instanceof ShapeMap)`; + +snapshot[`\$.map(\$.str, \$.u8) invalid undefined 1`] = `ShapeAssertError: !(value instanceof ShapeMap)`; + +snapshot[`\$.map(\$.str, \$.u8) invalid 123 1`] = `ShapeAssertError: !(value instanceof ShapeMap)`; + +snapshot[`\$.map(\$.str, \$.u8) invalid [ 123 ] 1`] = `ShapeAssertError: !(value instanceof ShapeMap)`; + +snapshot[`\$.map(\$.str, \$.u8) invalid [ [ "a", 1 ] ] 1`] = `ShapeAssertError: !(value instanceof ShapeMap)`; + +snapshot[`\$.map(\$.str, \$.u8) invalid Map(1) { "a" => null } 1`] = `ShapeAssertError: !(value instanceof ShapeMap)`; + +snapshot[`\$.map(\$.str, \$.u8) invalid Map(4) { "a" => 1, "b" => 2, "c" => -1, "d" => 0 } 1`] = `ShapeAssertError: !(value instanceof ShapeMap)`; + +snapshot[`\$.map(\$.str, \$.u8) invalid Map(4) { "a" => 1, "b" => 2, null => 3, "d" => 0 } 1`] = `ShapeAssertError: !(value instanceof ShapeMap)`; diff --git a/codecs/test/__snapshots__/compact.test.ts.snap b/shapes/test/__snapshots__/compact.test.ts.snap similarity index 68% rename from codecs/test/__snapshots__/compact.test.ts.snap rename to shapes/test/__snapshots__/compact.test.ts.snap index f288605..9d2f331 100644 --- a/codecs/test/__snapshots__/compact.test.ts.snap +++ b/shapes/test/__snapshots__/compact.test.ts.snap @@ -166,42 +166,42 @@ snapshot[`\$.compact(\$.object(\$.field("foo", \$.u32))) { foo: 456 } 1`] = ` 07 `; -snapshot[`\$.compact(\$.u8) invalid null 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.compact(\$.u8) invalid null 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.compact(\$.u8) invalid undefined 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.compact(\$.u8) invalid undefined 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.compact(\$.u8) invalid -1 1`] = `ScaleAssertError: value < 0`; +snapshot[`\$.compact(\$.u8) invalid -1 1`] = `ShapeAssertError: value < 0`; -snapshot[`\$.compact(\$.u8) invalid 256 1`] = `ScaleAssertError: value > 255`; +snapshot[`\$.compact(\$.u8) invalid 256 1`] = `ShapeAssertError: value > 255`; -snapshot[`\$.compact(\$.u128) invalid null 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.compact(\$.u128) invalid null 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.compact(\$.u128) invalid undefined 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.compact(\$.u128) invalid undefined 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.compact(\$.u128) invalid -1 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.compact(\$.u128) invalid -1 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.compact(\$.u128) invalid 100000000000000000000000000000000000000000000000000000000n 1`] = `ScaleAssertError: value > 340282366920938463463374607431768211455n`; +snapshot[`\$.compact(\$.u128) invalid 100000000000000000000000000000000000000000000000000000000n 1`] = `ShapeAssertError: value > 340282366920938463463374607431768211455n`; -snapshot[`\$.compact(\$.tuple(\$.u32)) invalid null 1`] = `ScaleAssertError: !(value instanceof Array)`; +snapshot[`\$.compact(\$.tuple(\$.u32)) invalid null 1`] = `ShapeAssertError: !(value instanceof Array)`; -snapshot[`\$.compact(\$.tuple(\$.u32)) invalid undefined 1`] = `ScaleAssertError: !(value instanceof Array)`; +snapshot[`\$.compact(\$.tuple(\$.u32)) invalid undefined 1`] = `ShapeAssertError: !(value instanceof Array)`; -snapshot[`\$.compact(\$.tuple(\$.u32)) invalid [] 1`] = `ScaleAssertError: value.length !== 1`; +snapshot[`\$.compact(\$.tuple(\$.u32)) invalid [] 1`] = `ShapeAssertError: value.length !== 1`; -snapshot[`\$.compact(\$.tuple(\$.u32)) invalid [ -1 ] 1`] = `ScaleAssertError: value[0] < 0`; +snapshot[`\$.compact(\$.tuple(\$.u32)) invalid [ -1 ] 1`] = `ShapeAssertError: value[0] < 0`; -snapshot[`\$.compact(\$.tuple(\$.u32)) invalid [ 123n ] 1`] = `ScaleAssertError: typeof value[0] !== "number"`; +snapshot[`\$.compact(\$.tuple(\$.u32)) invalid [ 123n ] 1`] = `ShapeAssertError: typeof value[0] !== "number"`; -snapshot[`\$.compact(\$.tuple(\$.u32)) invalid [ 9007199254740991 ] 1`] = `ScaleAssertError: value[0]: invalid int`; +snapshot[`\$.compact(\$.tuple(\$.u32)) invalid [ 9007199254740991 ] 1`] = `ShapeAssertError: value[0]: invalid int`; -snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid null 1`] = `ScaleAssertError: value == null`; +snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid null 1`] = `ShapeAssertError: value == null`; -snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid undefined 1`] = `ScaleAssertError: typeof value !== "object"`; +snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid undefined 1`] = `ShapeAssertError: typeof value !== "object"`; -snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid {} 1`] = `ScaleAssertError: !("foo" in value)`; +snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid {} 1`] = `ShapeAssertError: !("foo" in value)`; -snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid { foo: -1 } 1`] = `ScaleAssertError: value.foo < 0`; +snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid { foo: -1 } 1`] = `ShapeAssertError: value.foo < 0`; -snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid { foo: 123n } 1`] = `ScaleAssertError: typeof value.foo !== "number"`; +snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid { foo: 123n } 1`] = `ShapeAssertError: typeof value.foo !== "number"`; -snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid { foo: 9007199254740991 } 1`] = `ScaleAssertError: value.foo: invalid int`; +snapshot[`\$.compact(\$.field("foo", \$.u32)) invalid { foo: 9007199254740991 } 1`] = `ShapeAssertError: value.foo: invalid int`; diff --git a/codecs/test/__snapshots__/constant.test.ts.snap b/shapes/test/__snapshots__/constant.test.ts.snap similarity index 66% rename from codecs/test/__snapshots__/constant.test.ts.snap rename to shapes/test/__snapshots__/constant.test.ts.snap index bda35ee..a0a2cdd 100644 --- a/codecs/test/__snapshots__/constant.test.ts.snap +++ b/shapes/test/__snapshots__/constant.test.ts.snap @@ -7,6 +7,6 @@ snapshot[`\$.constant(1635018093, Uint8Array(4) [ 109, 101, 116, 97 ]) 163501809 61 `; -snapshot[`\$.constant(1635018093, Uint8Array(4) [ 109, 101, 116, 97 ]) invalid 123 1`] = `ScaleAssertError: value !== 1635018093`; +snapshot[`\$.constant(1635018093, Uint8Array(4) [ 109, 101, 116, 97 ]) invalid 123 1`] = `ShapeAssertError: value !== 1635018093`; -snapshot[`constantPattern.decode throws 1`] = `ScaleDecodeError: Invalid pattern; expected 0x6d657461, got 0x7b000000`; +snapshot[`constantPattern.decode throws 1`] = `ShapeDecodeError: Invalid pattern; expected 0x6d657461, got 0x7b000000`; diff --git a/codecs/test/__snapshots__/deferred.test.ts.snap b/shapes/test/__snapshots__/deferred.test.ts.snap similarity index 83% rename from codecs/test/__snapshots__/deferred.test.ts.snap rename to shapes/test/__snapshots__/deferred.test.ts.snap index 47b1c16..0722a07 100644 --- a/codecs/test/__snapshots__/deferred.test.ts.snap +++ b/shapes/test/__snapshots__/deferred.test.ts.snap @@ -26,16 +26,16 @@ snapshot[`\$0 = \$.option( snapshot[`\$0 = \$.option( \$.object(\$.field("val", \$.u8), \$.field("next", \$.deferred(() => \$0))) -) invalid null 1`] = `ScaleAssertError: value == null`; +) invalid null 1`] = `ShapeAssertError: value == null`; snapshot[`\$0 = \$.option( \$.object(\$.field("val", \$.u8), \$.field("next", \$.deferred(() => \$0))) -) invalid { val: 1, next: null } 1`] = `ScaleAssertError: value.next == null`; +) invalid { val: 1, next: null } 1`] = `ShapeAssertError: value.next == null`; snapshot[`\$0 = \$.option( \$.object(\$.field("val", \$.u8), \$.field("next", \$.deferred(() => \$0))) -) invalid { val: 1, next: { val: -1, next: undefined } } 1`] = `ScaleAssertError: value.next.val < 0`; +) invalid { val: 1, next: { val: -1, next: undefined } } 1`] = `ShapeAssertError: value.next.val < 0`; snapshot[`\$0 = \$.option( \$.object(\$.field("val", \$.u8), \$.field("next", \$.deferred(() => \$0))) -) invalid { val: 1, next: { val: 2, next: { val: 3, next: { val: -1, next: undefined } } } } 1`] = `ScaleAssertError: value.next.next.next.val < 0`; +) invalid { val: 1, next: { val: 2, next: { val: 3, next: { val: -1, next: undefined } } } } 1`] = `ShapeAssertError: value.next.next.next.val < 0`; diff --git a/codecs/test/__snapshots__/float.test.ts.snap b/shapes/test/__snapshots__/float.test.ts.snap similarity index 82% rename from codecs/test/__snapshots__/float.test.ts.snap rename to shapes/test/__snapshots__/float.test.ts.snap index dccb148..210fe71 100644 --- a/codecs/test/__snapshots__/float.test.ts.snap +++ b/shapes/test/__snapshots__/float.test.ts.snap @@ -143,8 +143,8 @@ f0 7f `; -snapshot[`\$.f64 invalid "0" 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.f64 invalid "0" 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.f64 invalid null 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.f64 invalid null 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.f64 invalid {} 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.f64 invalid {} 1`] = `ShapeAssertError: typeof value !== "number"`; diff --git a/codecs/test/__snapshots__/hex.test.ts.snap b/shapes/test/__snapshots__/hex.test.ts.snap similarity index 96% rename from codecs/test/__snapshots__/hex.test.ts.snap rename to shapes/test/__snapshots__/hex.test.ts.snap index fce60cf..8f50cbd 100644 --- a/codecs/test/__snapshots__/hex.test.ts.snap +++ b/shapes/test/__snapshots__/hex.test.ts.snap @@ -8845,32 +8845,32 @@ c0 cc `; -snapshot[`\$.hex(\$.uint8Array) invalid null 1`] = `ScaleAssertError: typeof value !== "string"`; +snapshot[`\$.hex(\$.uint8Array) invalid null 1`] = `ShapeAssertError: typeof value !== "string"`; -snapshot[`\$.hex(\$.uint8Array) invalid 4660 1`] = `ScaleAssertError: typeof value !== "string"`; +snapshot[`\$.hex(\$.uint8Array) invalid 4660 1`] = `ShapeAssertError: typeof value !== "string"`; -snapshot[`\$.hex(\$.uint8Array) invalid "0y00" 1`] = `ScaleAssertError: value: invalid hex`; +snapshot[`\$.hex(\$.uint8Array) invalid "0y00" 1`] = `ShapeAssertError: value: invalid hex`; -snapshot[`\$.hex(\$.uint8Array) invalid "hex" 1`] = `ScaleAssertError: value: invalid hex`; +snapshot[`\$.hex(\$.uint8Array) invalid "hex" 1`] = `ShapeAssertError: value: invalid hex`; -snapshot[`\$.hex(\$.uint8Array) invalid "0x000000000000000000000." 1`] = `ScaleAssertError: value: invalid hex`; +snapshot[`\$.hex(\$.uint8Array) invalid "0x000000000000000000000." 1`] = `ShapeAssertError: value: invalid hex`; -snapshot[`\$.hex(\$.uint8Array) invalid Uint8Array(5) [ 0, 1, 2, 3, 4 ] 1`] = `ScaleAssertError: typeof value !== "string"`; +snapshot[`\$.hex(\$.uint8Array) invalid Uint8Array(5) [ 0, 1, 2, 3, 4 ] 1`] = `ShapeAssertError: typeof value !== "string"`; -snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid null 1`] = `ScaleAssertError: typeof value !== "string"`; +snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid null 1`] = `ShapeAssertError: typeof value !== "string"`; -snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid 4660 1`] = `ScaleAssertError: typeof value !== "string"`; +snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid 4660 1`] = `ShapeAssertError: typeof value !== "string"`; -snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "0y00" 1`] = `ScaleAssertError: value: invalid hex`; +snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "0y00" 1`] = `ShapeAssertError: value: invalid hex`; -snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "hex" 1`] = `ScaleAssertError: value: invalid hex`; +snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "hex" 1`] = `ShapeAssertError: value: invalid hex`; -snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "0x000000000000000000000." 1`] = `ScaleAssertError: value: invalid hex`; +snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "0x000000000000000000000." 1`] = `ShapeAssertError: value: invalid hex`; -snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid Uint8Array(5) [ 0, 1, 2, 3, 4 ] 1`] = `ScaleAssertError: typeof value !== "string"`; +snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid Uint8Array(5) [ 0, 1, 2, 3, 4 ] 1`] = `ShapeAssertError: typeof value !== "string"`; -snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "0000" 1`] = `ScaleAssertError: value#encode.length !== 1`; +snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "0000" 1`] = `ShapeAssertError: value#encode.length !== 1`; -snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "" 1`] = `ScaleAssertError: value#encode.length !== 1`; +snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "" 1`] = `ShapeAssertError: value#encode.length !== 1`; -snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "-1" 1`] = `ScaleAssertError: value: invalid hex`; +snapshot[`\$.hex(\$.sizedUint8Array(1)) invalid "-1" 1`] = `ShapeAssertError: value: invalid hex`; diff --git a/codecs/test/__snapshots__/instance.test.ts.snap b/shapes/test/__snapshots__/instance.test.ts.snap similarity index 63% rename from codecs/test/__snapshots__/instance.test.ts.snap rename to shapes/test/__snapshots__/instance.test.ts.snap index ea7adc2..2ef76c3 100644 --- a/codecs/test/__snapshots__/instance.test.ts.snap +++ b/shapes/test/__snapshots__/instance.test.ts.snap @@ -45,14 +45,14 @@ snapshot[`\$myError MyError { 01 `; -snapshot[`\$myError invalid null 1`] = `ScaleAssertError: !(value instanceof MyError)`; +snapshot[`\$myError invalid null 1`] = `ShapeAssertError: !(value instanceof MyError)`; -snapshot[`\$myError invalid undefined 1`] = `ScaleAssertError: !(value instanceof MyError)`; +snapshot[`\$myError invalid undefined 1`] = `ShapeAssertError: !(value instanceof MyError)`; -snapshot[`\$myError invalid { code: 123, message: "foo", payload: { a: "abc", b: 2, c: true } } 1`] = `ScaleAssertError: !(value instanceof MyError)`; +snapshot[`\$myError invalid { code: 123, message: "foo", payload: { a: "abc", b: 2, c: true } } 1`] = `ShapeAssertError: !(value instanceof MyError)`; -snapshot[`\$myError invalid [Error: foo] 1`] = `ScaleAssertError: !(value instanceof MyError)`; +snapshot[`\$myError invalid [Error: foo] 1`] = `ShapeAssertError: !(value instanceof MyError)`; -snapshot[`\$myError invalid MyError { code: -1, message: "a", payload: { a: "abc", b: 2, c: true } } 1`] = `ScaleAssertError: value#arguments[0] < 0`; +snapshot[`\$myError invalid MyError { code: -1, message: "a", payload: { a: "abc", b: 2, c: true } } 1`] = `ShapeAssertError: value#arguments[0] < 0`; -snapshot[`\$myError invalid MyError { code: 123, message: "a", payload: { a: "abc", b: 2, c: "idk" } } 1`] = `ScaleAssertError: typeof value#arguments[2].c !== "boolean"`; +snapshot[`\$myError invalid MyError { code: 123, message: "a", payload: { a: "abc", b: 2, c: "idk" } } 1`] = `ShapeAssertError: typeof value#arguments[2].c !== "boolean"`; diff --git a/codecs/test/__snapshots__/int.test.ts.snap b/shapes/test/__snapshots__/int.test.ts.snap similarity index 52% rename from codecs/test/__snapshots__/int.test.ts.snap rename to shapes/test/__snapshots__/int.test.ts.snap index fa67d2c..785f328 100644 --- a/codecs/test/__snapshots__/int.test.ts.snap +++ b/shapes/test/__snapshots__/int.test.ts.snap @@ -1002,236 +1002,236 @@ ff 7f `; -snapshot[`\$.u8 invalid null 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u8 invalid null 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u8 invalid undefined 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u8 invalid undefined 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u8 invalid {} 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u8 invalid {} 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u8 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u8 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u8 invalid NaN 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.u8 invalid NaN 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.u8 invalid 1.2 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.u8 invalid 1.2 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.u8 invalid 0n 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u8 invalid 0n 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u8 invalid -1 1`] = `ScaleAssertError: value < 0`; +snapshot[`\$.u8 invalid -1 1`] = `ShapeAssertError: value < 0`; -snapshot[`\$.u8 invalid 256 1`] = `ScaleAssertError: value > 255`; +snapshot[`\$.u8 invalid 256 1`] = `ShapeAssertError: value > 255`; -snapshot[`\$.u16 invalid null 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u16 invalid null 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u16 invalid undefined 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u16 invalid undefined 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u16 invalid {} 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u16 invalid {} 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u16 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u16 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u16 invalid NaN 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.u16 invalid NaN 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.u16 invalid 1.2 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.u16 invalid 1.2 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.u16 invalid 0n 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u16 invalid 0n 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u16 invalid -1 1`] = `ScaleAssertError: value < 0`; +snapshot[`\$.u16 invalid -1 1`] = `ShapeAssertError: value < 0`; -snapshot[`\$.u16 invalid 65536 1`] = `ScaleAssertError: value > 65535`; +snapshot[`\$.u16 invalid 65536 1`] = `ShapeAssertError: value > 65535`; -snapshot[`\$.u32 invalid null 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u32 invalid null 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u32 invalid undefined 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u32 invalid undefined 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u32 invalid {} 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u32 invalid {} 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u32 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u32 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u32 invalid NaN 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.u32 invalid NaN 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.u32 invalid 1.2 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.u32 invalid 1.2 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.u32 invalid 0n 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.u32 invalid 0n 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.u32 invalid -1 1`] = `ScaleAssertError: value < 0`; +snapshot[`\$.u32 invalid -1 1`] = `ShapeAssertError: value < 0`; -snapshot[`\$.u32 invalid 4294967296 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.u32 invalid 4294967296 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.u64 invalid null 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u64 invalid null 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u64 invalid undefined 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u64 invalid undefined 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u64 invalid {} 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u64 invalid {} 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u64 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u64 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u64 invalid NaN 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u64 invalid NaN 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u64 invalid 1.2 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u64 invalid 1.2 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u64 invalid 0 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u64 invalid 0 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u64 invalid -1n 1`] = `ScaleAssertError: value < 0n`; +snapshot[`\$.u64 invalid -1n 1`] = `ShapeAssertError: value < 0n`; -snapshot[`\$.u64 invalid 18446744073709551616n 1`] = `ScaleAssertError: value > 18446744073709551615n`; +snapshot[`\$.u64 invalid 18446744073709551616n 1`] = `ShapeAssertError: value > 18446744073709551615n`; -snapshot[`\$.u128 invalid null 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u128 invalid null 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u128 invalid undefined 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u128 invalid undefined 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u128 invalid {} 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u128 invalid {} 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u128 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u128 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u128 invalid NaN 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u128 invalid NaN 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u128 invalid 1.2 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u128 invalid 1.2 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u128 invalid 0 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u128 invalid 0 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u128 invalid -1n 1`] = `ScaleAssertError: value < 0n`; +snapshot[`\$.u128 invalid -1n 1`] = `ShapeAssertError: value < 0n`; -snapshot[`\$.u128 invalid 340282366920938463463374607431768211456n 1`] = `ScaleAssertError: value > 340282366920938463463374607431768211455n`; +snapshot[`\$.u128 invalid 340282366920938463463374607431768211456n 1`] = `ShapeAssertError: value > 340282366920938463463374607431768211455n`; -snapshot[`\$.u256 invalid null 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u256 invalid null 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u256 invalid undefined 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u256 invalid undefined 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u256 invalid {} 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u256 invalid {} 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u256 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u256 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u256 invalid NaN 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u256 invalid NaN 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u256 invalid 1.2 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u256 invalid 1.2 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u256 invalid 0 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.u256 invalid 0 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.u256 invalid -1n 1`] = `ScaleAssertError: value < 0n`; +snapshot[`\$.u256 invalid -1n 1`] = `ShapeAssertError: value < 0n`; -snapshot[`\$.u256 invalid 115792089237316195423570985008687907853269984665640564039457584007913129639936n 1`] = `ScaleAssertError: value > 115792089237316195423570985008687907853269984665640564039457584007913129639935n`; +snapshot[`\$.u256 invalid 115792089237316195423570985008687907853269984665640564039457584007913129639936n 1`] = `ShapeAssertError: value > 115792089237316195423570985008687907853269984665640564039457584007913129639935n`; -snapshot[`\$.i8 invalid null 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i8 invalid null 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i8 invalid undefined 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i8 invalid undefined 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i8 invalid {} 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i8 invalid {} 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i8 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i8 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i8 invalid NaN 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.i8 invalid NaN 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.i8 invalid 1.2 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.i8 invalid 1.2 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.i8 invalid 0n 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i8 invalid 0n 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i8 invalid -129 1`] = `ScaleAssertError: value < -128`; +snapshot[`\$.i8 invalid -129 1`] = `ShapeAssertError: value < -128`; -snapshot[`\$.i8 invalid 128 1`] = `ScaleAssertError: value > 127`; +snapshot[`\$.i8 invalid 128 1`] = `ShapeAssertError: value > 127`; -snapshot[`\$.i16 invalid null 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i16 invalid null 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i16 invalid undefined 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i16 invalid undefined 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i16 invalid {} 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i16 invalid {} 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i16 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i16 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i16 invalid NaN 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.i16 invalid NaN 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.i16 invalid 1.2 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.i16 invalid 1.2 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.i16 invalid 0n 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i16 invalid 0n 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i16 invalid -32769 1`] = `ScaleAssertError: value < -32768`; +snapshot[`\$.i16 invalid -32769 1`] = `ShapeAssertError: value < -32768`; -snapshot[`\$.i16 invalid 32768 1`] = `ScaleAssertError: value > 32767`; +snapshot[`\$.i16 invalid 32768 1`] = `ShapeAssertError: value > 32767`; -snapshot[`\$.i32 invalid null 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i32 invalid null 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i32 invalid undefined 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i32 invalid undefined 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i32 invalid {} 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i32 invalid {} 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i32 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i32 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i32 invalid NaN 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.i32 invalid NaN 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.i32 invalid 1.2 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.i32 invalid 1.2 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.i32 invalid 0n 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.i32 invalid 0n 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.i32 invalid -2147483649 1`] = `ScaleAssertError: value: invalid int`; +snapshot[`\$.i32 invalid -2147483649 1`] = `ShapeAssertError: value: invalid int`; -snapshot[`\$.i32 invalid 2147483648 1`] = `ScaleAssertError: value > 2147483647`; +snapshot[`\$.i32 invalid 2147483648 1`] = `ShapeAssertError: value > 2147483647`; -snapshot[`\$.i64 invalid null 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i64 invalid null 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i64 invalid undefined 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i64 invalid undefined 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i64 invalid {} 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i64 invalid {} 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i64 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i64 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i64 invalid NaN 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i64 invalid NaN 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i64 invalid 1.2 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i64 invalid 1.2 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i64 invalid 0 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i64 invalid 0 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i64 invalid -9223372036854775809n 1`] = `ScaleAssertError: value < -9223372036854775808n`; +snapshot[`\$.i64 invalid -9223372036854775809n 1`] = `ShapeAssertError: value < -9223372036854775808n`; -snapshot[`\$.i64 invalid 9223372036854775808n 1`] = `ScaleAssertError: value > 9223372036854775807n`; +snapshot[`\$.i64 invalid 9223372036854775808n 1`] = `ShapeAssertError: value > 9223372036854775807n`; -snapshot[`\$.i128 invalid null 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid null 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid undefined 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid undefined 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid {} 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid {} 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid NaN 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid NaN 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid 1.2 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid 1.2 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid 0 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid 0 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid -170141183460469231731687303715884105729n 1`] = `ScaleAssertError: value < -170141183460469231731687303715884105728n`; +snapshot[`\$.i128 invalid -170141183460469231731687303715884105729n 1`] = `ShapeAssertError: value < -170141183460469231731687303715884105728n`; -snapshot[`\$.i128 invalid 170141183460469231731687303715884105728n 1`] = `ScaleAssertError: value > 170141183460469231731687303715884105727n`; +snapshot[`\$.i128 invalid 170141183460469231731687303715884105728n 1`] = `ShapeAssertError: value > 170141183460469231731687303715884105727n`; -snapshot[`\$.i128 invalid null 2`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid null 2`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid undefined 2`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid undefined 2`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid {} 2`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid {} 2`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid "abc" 2`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid "abc" 2`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid NaN 2`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid NaN 2`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid 1.2 2`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid 1.2 2`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid 0 2`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i128 invalid 0 2`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i128 invalid -170141183460469231731687303715884105729n 2`] = `ScaleAssertError: value < -170141183460469231731687303715884105728n`; +snapshot[`\$.i128 invalid -170141183460469231731687303715884105729n 2`] = `ShapeAssertError: value < -170141183460469231731687303715884105728n`; -snapshot[`\$.i128 invalid 170141183460469231731687303715884105728n 2`] = `ScaleAssertError: value > 170141183460469231731687303715884105727n`; +snapshot[`\$.i128 invalid 170141183460469231731687303715884105728n 2`] = `ShapeAssertError: value > 170141183460469231731687303715884105727n`; -snapshot[`\$.i256 invalid null 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i256 invalid null 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i256 invalid undefined 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i256 invalid undefined 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i256 invalid {} 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i256 invalid {} 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i256 invalid "abc" 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i256 invalid "abc" 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i256 invalid NaN 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i256 invalid NaN 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i256 invalid 1.2 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i256 invalid 1.2 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i256 invalid 0 1`] = `ScaleAssertError: typeof value !== "bigint"`; +snapshot[`\$.i256 invalid 0 1`] = `ShapeAssertError: typeof value !== "bigint"`; -snapshot[`\$.i256 invalid -57896044618658097711785492504343953926634992332820282019728792003956564819969n 1`] = `ScaleAssertError: value < -57896044618658097711785492504343953926634992332820282019728792003956564819968n`; +snapshot[`\$.i256 invalid -57896044618658097711785492504343953926634992332820282019728792003956564819969n 1`] = `ShapeAssertError: value < -57896044618658097711785492504343953926634992332820282019728792003956564819968n`; -snapshot[`\$.i256 invalid 57896044618658097711785492504343953926634992332820282019728792003956564819968n 1`] = `ScaleAssertError: value > 57896044618658097711785492504343953926634992332820282019728792003956564819967n`; +snapshot[`\$.i256 invalid 57896044618658097711785492504343953926634992332820282019728792003956564819968n 1`] = `ShapeAssertError: value > 57896044618658097711785492504343953926634992332820282019728792003956564819967n`; diff --git a/codecs/test/__snapshots__/iterable.test.ts.snap b/shapes/test/__snapshots__/iterable.test.ts.snap similarity index 100% rename from codecs/test/__snapshots__/iterable.test.ts.snap rename to shapes/test/__snapshots__/iterable.test.ts.snap diff --git a/codecs/test/__snapshots__/lenPrefixed.test.ts.snap b/shapes/test/__snapshots__/lenPrefixed.test.ts.snap similarity index 98% rename from codecs/test/__snapshots__/lenPrefixed.test.ts.snap rename to shapes/test/__snapshots__/lenPrefixed.test.ts.snap index 2ea4e38..f8dee14 100644 --- a/codecs/test/__snapshots__/lenPrefixed.test.ts.snap +++ b/shapes/test/__snapshots__/lenPrefixed.test.ts.snap @@ -3770,8 +3770,8 @@ ed 01 `; -snapshot[`\$.lenPrefixed(\$.u8) invalid null 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.lenPrefixed(\$.u8) invalid null 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.lenPrefixed(\$.u8) invalid undefined 1`] = `ScaleAssertError: typeof value !== "number"`; +snapshot[`\$.lenPrefixed(\$.u8) invalid undefined 1`] = `ShapeAssertError: typeof value !== "number"`; -snapshot[`\$.lenPrefixed(\$.u8) invalid -1 1`] = `ScaleAssertError: value < 0`; +snapshot[`\$.lenPrefixed(\$.u8) invalid -1 1`] = `ShapeAssertError: value < 0`; diff --git a/shapes/test/__snapshots__/never.test.ts.snap b/shapes/test/__snapshots__/never.test.ts.snap new file mode 100644 index 0000000..bb6a5f0 --- /dev/null +++ b/shapes/test/__snapshots__/never.test.ts.snap @@ -0,0 +1,11 @@ +export const snapshot = {}; + +snapshot[`\$.option(\$.never) undefined 1`] = `00`; + +snapshot[`\$.never invalid null 1`] = `ShapeAssertError: value: Cannot validate \$.never`; + +snapshot[`\$.never invalid 0 1`] = `ShapeAssertError: value: Cannot validate \$.never`; + +snapshot[`\$.option(\$.never) invalid null 1`] = `ShapeAssertError: value: Cannot validate \$.never`; + +snapshot[`\$.option(\$.never) invalid 0 1`] = `ShapeAssertError: value: Cannot validate \$.never`; diff --git a/shapes/test/__snapshots__/object.test.ts.snap b/shapes/test/__snapshots__/object.test.ts.snap new file mode 100644 index 0000000..7ad088a --- /dev/null +++ b/shapes/test/__snapshots__/object.test.ts.snap @@ -0,0 +1,164 @@ +export const snapshot = {}; + +snapshot[`\$person { name: "Darrel", nickName: "The Durst", superPower: "telekinesis", luckyNumber: 9 } 1`] = ` +18 +44 +61 +72 +72 +65 +6c +24 +54 +68 +65 +20 +44 +75 +72 +73 +74 +01 +2c +74 +65 +6c +65 +6b +69 +6e +65 +73 +69 +73 +09 +`; + +snapshot[`\$person { name: "Michael", nickName: "Mike", luckyNumber: 7 } 1`] = ` +1c +4d +69 +63 +68 +61 +65 +6c +10 +4d +69 +6b +65 +00 +07 +`; + +snapshot[`\$person invalid null 1`] = `ShapeAssertError: value == null`; + +snapshot[`\$person invalid undefined 1`] = `ShapeAssertError: typeof value !== "object"`; + +snapshot[`\$person invalid 123 1`] = `ShapeAssertError: typeof value !== "object"`; + +snapshot[`\$person invalid [Function (anonymous)] 1`] = `ShapeAssertError: typeof value !== "object"`; + +snapshot[`\$person invalid {} 1`] = `ShapeAssertError: !("name" in value)`; + +snapshot[`\$person invalid { name: "", nickName: "", superPower: 0, luckyNumber: 0 } 1`] = `ShapeAssertError: typeof value.superPower !== "string"`; + +snapshot[`\$person invalid { name: "", nickName: "", superPower: "", luckyNumber: -1 } 1`] = `ShapeAssertError: value.luckyNumber < 0`; + +snapshot[`\$person invalid { name: "", nickName: "", superPower: "", unluckyNumber: 0 } 1`] = `ShapeAssertError: !("luckyNumber" in value)`; + +snapshot[`\$.object( + \$.taggedUnion( + "_tag", + [ + Variant { tag: "a", shape: \$.object([]) }, + Variant { tag: "b", shape: \$.object(\$.field("x", \$.u8)) } + ] + ), + \$.field("bar", \$.u8) +) { _tag: "a", bar: 123 } 1`] = ` +00 +7b +`; + +snapshot[`\$.object( + \$.taggedUnion( + "_tag", + [ + Variant { tag: "a", shape: \$.object([]) }, + Variant { tag: "b", shape: \$.object(\$.field("x", \$.u8)) } + ] + ), + \$.field("bar", \$.u8) +) { _tag: "b", x: 0, bar: 123 } 1`] = ` +01 +00 +7b +`; + +snapshot[`\$.object( + \$.taggedUnion( + "_tag", + [ + Variant { tag: "a", shape: \$.object([]) }, + Variant { tag: "b", shape: \$.object(\$.field("x", \$.u8)) } + ] + ), + \$.field("bar", \$.u8) +) invalid null 1`] = `ShapeAssertError: value == null`; + +snapshot[`\$.object( + \$.taggedUnion( + "_tag", + [ + Variant { tag: "a", shape: \$.object([]) }, + Variant { tag: "b", shape: \$.object(\$.field("x", \$.u8)) } + ] + ), + \$.field("bar", \$.u8) +) invalid { _tag: null, bar: 1 } 1`] = `ShapeAssertError: typeof value._tag !== "string"`; + +snapshot[`\$.object( + \$.taggedUnion( + "_tag", + [ + Variant { tag: "a", shape: \$.object([]) }, + Variant { tag: "b", shape: \$.object(\$.field("x", \$.u8)) } + ] + ), + \$.field("bar", \$.u8) +) invalid { _tag: "", bar: 1 } 1`] = `ShapeAssertError: value._tag: invalid tag`; + +snapshot[`\$.object( + \$.taggedUnion( + "_tag", + [ + Variant { tag: "a", shape: \$.object([]) }, + Variant { tag: "b", shape: \$.object(\$.field("x", \$.u8)) } + ] + ), + \$.field("bar", \$.u8) +) invalid { _tag: "b", bar: 1 } 1`] = `ShapeAssertError: !("x" in value)`; + +snapshot[`\$.object( + \$.taggedUnion( + "_tag", + [ + Variant { tag: "a", shape: \$.object([]) }, + Variant { tag: "b", shape: \$.object(\$.field("x", \$.u8)) } + ] + ), + \$.field("bar", \$.u8) +) invalid { _tag: "b", x: -1, bar: 1 } 1`] = `ShapeAssertError: value.x < 0`; + +snapshot[`\$.object( + \$.taggedUnion( + "_tag", + [ + Variant { tag: "a", shape: \$.object([]) }, + Variant { tag: "b", shape: \$.object(\$.field("x", \$.u8)) } + ] + ), + \$.field("bar", \$.u8) +) invalid { _tag: "a", bar: -1 } 1`] = `ShapeAssertError: value.bar < 0`; diff --git a/codecs/test/__snapshots__/option.test.ts.snap b/shapes/test/__snapshots__/option.test.ts.snap similarity index 90% rename from codecs/test/__snapshots__/option.test.ts.snap rename to shapes/test/__snapshots__/option.test.ts.snap index b8711c0..132091a 100644 --- a/codecs/test/__snapshots__/option.test.ts.snap +++ b/shapes/test/__snapshots__/option.test.ts.snap @@ -53,4 +53,4 @@ snapshot[`\$.option(\$.str, null) "low" 1`] = ` snapshot[`\$.option(\$.str, null) null 1`] = `00`; -snapshot[`\$.option(\$.bool) invalid 123 1`] = `ScaleAssertError: typeof value !== "boolean"`; +snapshot[`\$.option(\$.bool) invalid 123 1`] = `ShapeAssertError: typeof value !== "boolean"`; diff --git a/codecs/test/__snapshots__/optionBool.test.ts.snap b/shapes/test/__snapshots__/optionBool.test.ts.snap similarity index 74% rename from codecs/test/__snapshots__/optionBool.test.ts.snap rename to shapes/test/__snapshots__/optionBool.test.ts.snap index 8363048..baac3e2 100644 --- a/codecs/test/__snapshots__/optionBool.test.ts.snap +++ b/shapes/test/__snapshots__/optionBool.test.ts.snap @@ -6,4 +6,4 @@ snapshot[`\$.optionBool true 1`] = `01`; snapshot[`\$.optionBool false 1`] = `02`; -snapshot[`\$.optionBool invalid 123 1`] = `ScaleAssertError: typeof value !== "boolean"`; +snapshot[`\$.optionBool invalid 123 1`] = `ShapeAssertError: typeof value !== "boolean"`; diff --git a/codecs/test/__snapshots__/promise.test.ts.snap b/shapes/test/__snapshots__/promise.test.ts.snap similarity index 98% rename from codecs/test/__snapshots__/promise.test.ts.snap rename to shapes/test/__snapshots__/promise.test.ts.snap index c1cf39a..4545f40 100644 --- a/codecs/test/__snapshots__/promise.test.ts.snap +++ b/shapes/test/__snapshots__/promise.test.ts.snap @@ -986,4 +986,4 @@ cd 03 `; -snapshot[`\$.promise(\$.u8) invalid 0 1`] = `ScaleAssertError: !(value instanceof Promise)`; +snapshot[`\$.promise(\$.u8) invalid 0 1`] = `ShapeAssertError: !(value instanceof Promise)`; diff --git a/codecs/test/__snapshots__/record.test.ts.snap b/shapes/test/__snapshots__/record.test.ts.snap similarity index 86% rename from codecs/test/__snapshots__/record.test.ts.snap rename to shapes/test/__snapshots__/record.test.ts.snap index 16ad347..cd63022 100644 --- a/codecs/test/__snapshots__/record.test.ts.snap +++ b/shapes/test/__snapshots__/record.test.ts.snap @@ -143,7 +143,7 @@ snapshot[`\$.transform( encode: [Function: entries], decode: [Function: fromEntries] } -) invalid [ true, false ] 1`] = `ScaleAssertError: typeof value#encode[0][1] !== "string"`; +) invalid [ true, false ] 1`] = `ShapeAssertError: typeof value#encode[0][1] !== "string"`; snapshot[`\$.transform( { @@ -151,7 +151,7 @@ snapshot[`\$.transform( encode: [Function: entries], decode: [Function: fromEntries] } -) invalid [ 1, 2, 3, -1, 4 ] 1`] = `ScaleAssertError: typeof value#encode[0][1] !== "string"`; +) invalid [ 1, 2, 3, -1, 4 ] 1`] = `ShapeAssertError: typeof value#encode[0][1] !== "string"`; snapshot[`\$.transform( { @@ -159,7 +159,7 @@ snapshot[`\$.transform( encode: [Function: entries], decode: [Function: fromEntries] } -) invalid { this: "should" } 1`] = `ScaleAssertError: typeof value#encode[0][1] !== "number"`; +) invalid { this: "should" } 1`] = `ShapeAssertError: typeof value#encode[0][1] !== "number"`; snapshot[`\$.transform( { @@ -167,7 +167,7 @@ snapshot[`\$.transform( encode: [Function: entries], decode: [Function: fromEntries] } -) invalid { be: "invalid" } 1`] = `ScaleAssertError: typeof value#encode[0][1] !== "number"`; +) invalid { be: "invalid" } 1`] = `ShapeAssertError: typeof value#encode[0][1] !== "number"`; snapshot[`\$.transform( { @@ -175,4 +175,4 @@ snapshot[`\$.transform( encode: [Function: entries], decode: [Function: fromEntries] } -) invalid { and: "this" } 1`] = `ScaleAssertError: typeof value#encode[0][1] !== "number"`; +) invalid { and: "this" } 1`] = `ShapeAssertError: typeof value#encode[0][1] !== "number"`; diff --git a/codecs/test/__snapshots__/result.test.ts.snap b/shapes/test/__snapshots__/result.test.ts.snap similarity index 80% rename from codecs/test/__snapshots__/result.test.ts.snap rename to shapes/test/__snapshots__/result.test.ts.snap index 1c264f4..a89e9a9 100644 --- a/codecs/test/__snapshots__/result.test.ts.snap +++ b/shapes/test/__snapshots__/result.test.ts.snap @@ -36,7 +36,7 @@ snapshot[`\$.result( \$.tuple(\$.str), [Function (anonymous)] ) -) invalid null 1`] = `ScaleAssertError: typeof value !== "string"`; +) invalid null 1`] = `ShapeAssertError: typeof value !== "string"`; snapshot[`\$.result( \$.str, @@ -45,7 +45,7 @@ snapshot[`\$.result( \$.tuple(\$.str), [Function (anonymous)] ) -) invalid undefined 1`] = `ScaleAssertError: typeof value !== "string"`; +) invalid undefined 1`] = `ShapeAssertError: typeof value !== "string"`; snapshot[`\$.result( \$.str, @@ -54,7 +54,7 @@ snapshot[`\$.result( \$.tuple(\$.str), [Function (anonymous)] ) -) invalid [Error: foo] 1`] = `ScaleAssertError: !(value instanceof StrErr)`; +) invalid [Error: foo] 1`] = `ShapeAssertError: !(value instanceof StrErr)`; snapshot[`\$.result( \$.str, @@ -63,4 +63,4 @@ snapshot[`\$.result( \$.tuple(\$.str), [Function (anonymous)] ) -) invalid { [StrErr: null] str: null, message: "StrErr: null" } 1`] = `ScaleAssertError: typeof value#arguments[0] !== "string"`; +) invalid { [StrErr: null] str: null, message: "StrErr: null" } 1`] = `ShapeAssertError: typeof value#arguments[0] !== "string"`; diff --git a/codecs/test/__snapshots__/str.test.ts.snap b/shapes/test/__snapshots__/str.test.ts.snap similarity index 98% rename from codecs/test/__snapshots__/str.test.ts.snap rename to shapes/test/__snapshots__/str.test.ts.snap index a4a3dda..74ebee1 100644 --- a/codecs/test/__snapshots__/str.test.ts.snap +++ b/shapes/test/__snapshots__/str.test.ts.snap @@ -501,7 +501,7 @@ f9 `; snapshot[`\$.str words 1`] = ` -55 +39 03 61 76 @@ -557,14 +557,6 @@ snapshot[`\$.str words 1`] = ` 6f 6e 0a -44 -65 -63 -6f -64 -65 -63 -0a 64 65 6e @@ -586,14 +578,6 @@ snapshot[`\$.str words 1`] = ` 6e 74 0a -45 -6e -63 -6f -64 -65 -63 -0a 68 79 64 @@ -704,6 +688,15 @@ snapshot[`\$.str words 1`] = ` 73 75 0a +73 +75 +62 +73 +68 +61 +70 +65 +0a 74 79 70 @@ -9442,10 +9435,10 @@ snapshot[`\$.str cargoLock 1`] = ` 0a `; -snapshot[`\$.str invalid null 1`] = `ScaleAssertError: typeof value !== "string"`; +snapshot[`\$.str invalid null 1`] = `ShapeAssertError: typeof value !== "string"`; -snapshot[`\$.str invalid undefined 1`] = `ScaleAssertError: typeof value !== "string"`; +snapshot[`\$.str invalid undefined 1`] = `ShapeAssertError: typeof value !== "string"`; -snapshot[`\$.str invalid 123 1`] = `ScaleAssertError: typeof value !== "string"`; +snapshot[`\$.str invalid 123 1`] = `ShapeAssertError: typeof value !== "string"`; -snapshot[`\$.str invalid Symbol(foo) 1`] = `ScaleAssertError: typeof value !== "string"`; +snapshot[`\$.str invalid Symbol(foo) 1`] = `ShapeAssertError: typeof value !== "string"`; diff --git a/codecs/test/__snapshots__/transform.test.ts.snap b/shapes/test/__snapshots__/transform.test.ts.snap similarity index 68% rename from codecs/test/__snapshots__/transform.test.ts.snap rename to shapes/test/__snapshots__/transform.test.ts.snap index 74b6ee9..efa3d46 100644 --- a/codecs/test/__snapshots__/transform.test.ts.snap +++ b/shapes/test/__snapshots__/transform.test.ts.snap @@ -4,4 +4,4 @@ snapshot[`\$boxU8 { value: 0 } 1`] = `00`; snapshot[`\$boxU8 { value: 1 } 1`] = `01`; -snapshot[`\$boxU8 invalid { value: -1 } 1`] = `ScaleAssertError: value#encode < 0`; +snapshot[`\$boxU8 invalid { value: -1 } 1`] = `ShapeAssertError: value#encode < 0`; diff --git a/codecs/test/__snapshots__/tuple.test.ts.snap b/shapes/test/__snapshots__/tuple.test.ts.snap similarity index 78% rename from codecs/test/__snapshots__/tuple.test.ts.snap rename to shapes/test/__snapshots__/tuple.test.ts.snap index 7351e20..06db31d 100644 --- a/codecs/test/__snapshots__/tuple.test.ts.snap +++ b/shapes/test/__snapshots__/tuple.test.ts.snap @@ -483,16 +483,16 @@ snapshot[`\$.tuple(\$.str, \$.i16, \$.option(\$.u16)) [ "GOODBYE!", 2, 101 ] 1`] 00 `; -snapshot[`\$.tuple(\$.str, \$.u8) invalid null 1`] = `ScaleAssertError: !(value instanceof Array)`; +snapshot[`\$.tuple(\$.str, \$.u8) invalid null 1`] = `ShapeAssertError: !(value instanceof Array)`; -snapshot[`\$.tuple(\$.str, \$.u8) invalid undefined 1`] = `ScaleAssertError: !(value instanceof Array)`; +snapshot[`\$.tuple(\$.str, \$.u8) invalid undefined 1`] = `ShapeAssertError: !(value instanceof Array)`; -snapshot[`\$.tuple(\$.str, \$.u8) invalid 123 1`] = `ScaleAssertError: !(value instanceof Array)`; +snapshot[`\$.tuple(\$.str, \$.u8) invalid 123 1`] = `ShapeAssertError: !(value instanceof Array)`; -snapshot[`\$.tuple(\$.str, \$.u8) invalid [] 1`] = `ScaleAssertError: value.length !== 2`; +snapshot[`\$.tuple(\$.str, \$.u8) invalid [] 1`] = `ShapeAssertError: value.length !== 2`; -snapshot[`\$.tuple(\$.str, \$.u8) invalid [ "", 1, 1 ] 1`] = `ScaleAssertError: value.length !== 2`; +snapshot[`\$.tuple(\$.str, \$.u8) invalid [ "", 1, 1 ] 1`] = `ShapeAssertError: value.length !== 2`; -snapshot[`\$.tuple(\$.str, \$.u8) invalid [ "", -1 ] 1`] = `ScaleAssertError: value[1] < 0`; +snapshot[`\$.tuple(\$.str, \$.u8) invalid [ "", -1 ] 1`] = `ShapeAssertError: value[1] < 0`; -snapshot[`\$.tuple(\$.str, \$.u8) invalid [ null, 1 ] 1`] = `ScaleAssertError: typeof value[0] !== "string"`; +snapshot[`\$.tuple(\$.str, \$.u8) invalid [ null, 1 ] 1`] = `ShapeAssertError: typeof value[0] !== "string"`; diff --git a/codecs/test/__snapshots__/union.test.ts.snap b/shapes/test/__snapshots__/union.test.ts.snap similarity index 71% rename from codecs/test/__snapshots__/union.test.ts.snap rename to shapes/test/__snapshots__/union.test.ts.snap index 74182cb..51edbf0 100644 --- a/codecs/test/__snapshots__/union.test.ts.snap +++ b/shapes/test/__snapshots__/union.test.ts.snap @@ -72,20 +72,20 @@ snapshot[`\$.literalUnion(interestingU8s) "LargestSquare" 1`] = `e1`; snapshot[`\$.literalUnion(interestingU8s) "Max" 1`] = `ff`; -snapshot[`\$abc invalid null 1`] = `ScaleAssertError: value == null`; +snapshot[`\$abc invalid null 1`] = `ShapeAssertError: value == null`; -snapshot[`\$abc invalid { _tag: null } 1`] = `ScaleAssertError: typeof value._tag !== "string"`; +snapshot[`\$abc invalid { _tag: null } 1`] = `ShapeAssertError: typeof value._tag !== "string"`; -snapshot[`\$abc invalid { _tag: "" } 1`] = `ScaleAssertError: value._tag: invalid tag`; +snapshot[`\$abc invalid { _tag: "" } 1`] = `ShapeAssertError: value._tag: invalid tag`; -snapshot[`\$abc invalid { _tag: "B" } 1`] = `ScaleAssertError: !("B" in value)`; +snapshot[`\$abc invalid { _tag: "B" } 1`] = `ShapeAssertError: !("B" in value)`; -snapshot[`\$abc invalid { _tag: "B", B: null } 1`] = `ScaleAssertError: typeof value.B !== "string"`; +snapshot[`\$abc invalid { _tag: "B", B: null } 1`] = `ShapeAssertError: typeof value.B !== "string"`; -snapshot[`\$abc invalid { _tag: "D", a: 1 } 1`] = `ScaleAssertError: !("b" in value)`; +snapshot[`\$abc invalid { _tag: "D", a: 1 } 1`] = `ShapeAssertError: !("b" in value)`; -snapshot[`\$abc invalid { _tag: "D", b: 1n } 1`] = `ScaleAssertError: !("a" in value)`; +snapshot[`\$abc invalid { _tag: "D", b: 1n } 1`] = `ShapeAssertError: !("a" in value)`; -snapshot[`\$abc invalid { _tag: "D", a: -1, b: 1n } 1`] = `ScaleAssertError: value.a < 0`; +snapshot[`\$abc invalid { _tag: "D", a: -1, b: 1n } 1`] = `ShapeAssertError: value.a < 0`; -snapshot[`\$abc invalid { _tag: "D", a: 1, b: -1n } 1`] = `ScaleAssertError: value.b < 0n`; +snapshot[`\$abc invalid { _tag: "D", a: 1, b: -1n } 1`] = `ShapeAssertError: value.b < 0n`; diff --git a/codecs/test/array.test.ts b/shapes/test/array.test.ts similarity index 71% rename from codecs/test/array.test.ts rename to shapes/test/array.test.ts index e5efb05..e12bb55 100644 --- a/codecs/test/array.test.ts +++ b/shapes/test/array.test.ts @@ -1,7 +1,7 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" -testCodec($.array($.u8), [ +testShape($.array($.u8), [ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], @@ -16,9 +16,9 @@ testInvalid($.array($.u8), [ [1, 2, 3, -1, 4], ]) -testCodec($.sizedArray($.u8, 1), [[1]]) -testCodec($.sizedArray($.u8, 2), [[1, 1]]) -testCodec($.sizedArray($.u8, 100), { "[1] * 100": Array(100).fill(1) as any }) +testShape($.sizedArray($.u8, 1), [[1]]) +testShape($.sizedArray($.u8, 2), [[1, 1]]) +testShape($.sizedArray($.u8, 100), { "[1] * 100": Array(100).fill(1) as any }) testInvalid($.sizedArray($.u8, 3), [ null, @@ -28,7 +28,7 @@ testInvalid($.sizedArray($.u8, 3), [ [1, 2, -1], ]) -testCodec($.uint8Array, [ +testShape($.uint8Array, [ new Uint8Array([1, 2, 3, 4, 5]), new Uint8Array([6, 7, 8, 9, 10]), new Uint8Array([11, 12, 13, 14, 15]), @@ -43,7 +43,7 @@ testInvalid($.uint8Array, [ [1, 2, -1], ]) -testCodec($.sizedUint8Array(4), [ +testShape($.sizedUint8Array(4), [ new Uint8Array([0, 0, 0, 0]), new Uint8Array([1, 2, 3, 4]), ]) diff --git a/codecs/test/bitSequence.test.ts b/shapes/test/bitSequence.test.ts similarity index 87% rename from codecs/test/bitSequence.test.ts rename to shapes/test/bitSequence.test.ts index 75e1c1c..1c08c0f 100644 --- a/codecs/test/bitSequence.test.ts +++ b/shapes/test/bitSequence.test.ts @@ -1,5 +1,5 @@ import * as $ from "../../mod.ts" -import { assertEquals, testCodec, testInvalid } from "../../test-util.ts" +import { assertEquals, testInvalid, testShape } from "../../test-util.ts" Deno.test("BitSequence", () => { const sequence = new $.BitSequence(100) @@ -14,7 +14,7 @@ Deno.test("BitSequence", () => { assertEquals(sequence[121], undefined) }) -testCodec($.bitSequence, [ +testShape($.bitSequence, [ $.BitSequence.from([]), $.BitSequence.from([0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1]), ]) diff --git a/codecs/test/bool.test.ts b/shapes/test/bool.test.ts similarity index 52% rename from codecs/test/bool.test.ts rename to shapes/test/bool.test.ts index 1171b04..ea8a0c8 100644 --- a/codecs/test/bool.test.ts +++ b/shapes/test/bool.test.ts @@ -1,7 +1,7 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" -testCodec($.bool, [true, false]) +testShape($.bool, [true, false]) testInvalid($.bool, [ null, diff --git a/codecs/test/collections.test.ts b/shapes/test/collections.test.ts similarity index 70% rename from codecs/test/collections.test.ts rename to shapes/test/collections.test.ts index 0345deb..8b8061a 100644 --- a/codecs/test/collections.test.ts +++ b/shapes/test/collections.test.ts @@ -1,11 +1,11 @@ import { assertEquals } from "https://deno.land/std@0.161.0/testing/asserts.ts" import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" -testCodec($.set($.u8), [ - new $.ScaleSet($.u8), - new $.ScaleSet($.u8, [0, 2, 4, 8]), - new $.ScaleSet($.u8, [2, 3, 5, 7]), +testShape($.set($.u8), [ + new $.ShapeSet($.u8), + new $.ShapeSet($.u8, [0, 2, 4, 8]), + new $.ShapeSet($.u8, [2, 3, 5, 7]), ]) testInvalid($.set($.u8), [ @@ -15,13 +15,13 @@ testInvalid($.set($.u8), [ [123], new Set([null]), new Set([1, 2, 3, -1, 4]), - new $.ScaleSet($.i8, [1, 2, 3, -1, 4]), + new $.ShapeSet($.i8, [1, 2, 3, -1, 4]), ]) -testCodec($.map($.str, $.u8), [ - new $.ScaleMap($.str), - new $.ScaleMap($.str, [["0", 0], ["1", 1]]), - new $.ScaleMap($.str, [["2^0", 0], ["2^1", 2], ["2^2", 4], ["2^3", 8], ["2^4", 16]]), +testShape($.map($.str, $.u8), [ + new $.ShapeMap($.str), + new $.ShapeMap($.str, [["0", 0], ["1", 1]]), + new $.ShapeMap($.str, [["2^0", 0], ["2^1", 2], ["2^2", 4], ["2^3", 8], ["2^4", 16]]), ]) testInvalid($.map($.str, $.u8), [ @@ -35,8 +35,8 @@ testInvalid($.map($.str, $.u8), [ new Map([["a", 1], ["b", 2], [null, 3], ["d", 0]]), ]) -Deno.test("ScaleSet", () => { - const set = new $.ScaleSet($.uint8Array, [new Uint8Array([0])]) +Deno.test("ShapeSet", () => { + const set = new $.ShapeSet($.uint8Array, [new Uint8Array([0])]) assertEquals(set.has(new Uint8Array([0])), true) assertEquals(set.size, 1) assertEquals(set.has(new Uint8Array([1])), false) diff --git a/codecs/test/compact.test.ts b/shapes/test/compact.test.ts similarity index 61% rename from codecs/test/compact.test.ts rename to shapes/test/compact.test.ts index 61292dd..23d8b3a 100644 --- a/codecs/test/compact.test.ts +++ b/shapes/test/compact.test.ts @@ -1,12 +1,12 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" -testCodec($.compact($.u32), [0, 1, 6, 8, 14, 16, 30, 32].map((x) => (2 ** x) - 1)) -testCodec($.compact($.u256), [0, 6, 14, 30, 40, 64, 128, 128, 256].map((x) => (1n << BigInt(x)) - 1n)) +testShape($.compact($.u32), [0, 1, 6, 8, 14, 16, 30, 32].map((x) => (2 ** x) - 1)) +testShape($.compact($.u256), [0, 6, 14, 30, 40, 64, 128, 128, 256].map((x) => (1n << BigInt(x)) - 1n)) -testCodec($.compact($.tuple($.u32)), [[123]]) -testCodec($.compact($.field("foo", $.u32)), [{ foo: 456 }]) -testCodec($.compact($.object($.field("foo", $.u32))), [{ foo: 456 }]) +testShape($.compact($.tuple($.u32)), [[123]]) +testShape($.compact($.field("foo", $.u32)), [{ foo: 456 }]) +testShape($.compact($.object($.field("foo", $.u32))), [{ foo: 456 }]) testInvalid($.compact($.u8), [ null, diff --git a/codecs/test/constant.test.ts b/shapes/test/constant.test.ts similarity index 75% rename from codecs/test/constant.test.ts rename to shapes/test/constant.test.ts index eeeca0e..46b840c 100644 --- a/codecs/test/constant.test.ts +++ b/shapes/test/constant.test.ts @@ -1,9 +1,9 @@ import * as $ from "../../mod.ts" -import { assertThrowsSnapshot, testCodec, testInvalid } from "../../test-util.ts" +import { assertThrowsSnapshot, testInvalid, testShape } from "../../test-util.ts" const magicNumber = 0x6174656d const $magicNumber = $.constant(magicNumber, $.u32) -testCodec($magicNumber, [magicNumber]) +testShape($magicNumber, [magicNumber]) testInvalid($magicNumber, [123]) diff --git a/codecs/test/deferred.test.ts b/shapes/test/deferred.test.ts similarity index 77% rename from codecs/test/deferred.test.ts rename to shapes/test/deferred.test.ts index 92af5d3..fbc2fcb 100644 --- a/codecs/test/deferred.test.ts +++ b/shapes/test/deferred.test.ts @@ -1,17 +1,17 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" type LinkedList = undefined | { val: number next: LinkedList } -const $linkedList: $.Codec = $.option($.object( +const $linkedList: $.Shape = $.option($.object( $.field("val", $.u8), $.field("next", $.deferred(() => $linkedList)), )) -testCodec($linkedList, [ +testShape($linkedList, [ undefined, { val: 1, next: undefined }, { val: 1, next: { val: 2, next: { val: 3, next: undefined } } }, diff --git a/codecs/test/float.test.ts b/shapes/test/float.test.ts similarity index 75% rename from codecs/test/float.test.ts rename to shapes/test/float.test.ts index 4d8168c..56dfbcd 100644 --- a/codecs/test/float.test.ts +++ b/shapes/test/float.test.ts @@ -1,7 +1,7 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" -testCodec($.f64, [ +testShape($.f64, [ 0, 1, -1, diff --git a/codecs/test/hex.test.ts b/shapes/test/hex.test.ts similarity index 82% rename from codecs/test/hex.test.ts rename to shapes/test/hex.test.ts index f0ab997..b5ce1b3 100644 --- a/codecs/test/hex.test.ts +++ b/shapes/test/hex.test.ts @@ -1,23 +1,23 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" import { encodeHex } from "../hex.ts" const $unsizedHex = $.hex($.uint8Array) -testCodec($unsizedHex, { +testShape($unsizedHex, { "\"\"": "", "\"000102030405\"": "000102030405", "\"deadbeef\"": "deadbeef", "Cargo.lock": encodeHex(await Deno.readFile("Cargo.lock")), }) -testCodec($.hex($.sizedUint8Array(1)), [ +testShape($.hex($.sizedUint8Array(1)), [ "00", "01", "ff", ]) -testCodec($.hex($.sizedUint8Array(16)), [ +testShape($.hex($.sizedUint8Array(16)), [ "dd0000dd0000eeeeeeee00000cc000cc", "dd0000dd0000ee000000000000cccc00", "dddddddd0000eeeeeeee0000000cc000", diff --git a/codecs/test/instance.test.ts b/shapes/test/instance.test.ts similarity index 92% rename from codecs/test/instance.test.ts rename to shapes/test/instance.test.ts index 476d9df..93b0773 100644 --- a/codecs/test/instance.test.ts +++ b/shapes/test/instance.test.ts @@ -1,5 +1,5 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" // Doesn't extend `Error` to avoid call-stack instability class MyError { @@ -31,7 +31,7 @@ const $myError = $.withMetadata( ), ) -testCodec($myError, [ +testShape($myError, [ new MyError( 1, "At war with my Arch system config", diff --git a/codecs/test/int.test.ts b/shapes/test/int.test.ts similarity index 66% rename from codecs/test/int.test.ts rename to shapes/test/int.test.ts index 327f8f7..402bc3e 100644 --- a/codecs/test/int.test.ts +++ b/shapes/test/int.test.ts @@ -1,5 +1,5 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" function generateIntTests(signed: boolean, size: number) { const s = BigInt(size) @@ -21,20 +21,20 @@ function generateIntTests(signed: boolean, size: number) { ] } -testCodec($.u8, generateIntTests(false, 8).map(Number)) -testCodec($.u16, generateIntTests(false, 16).map(Number)) -testCodec($.u32, generateIntTests(false, 32).map(Number)) -testCodec($.u64, generateIntTests(false, 64).map(BigInt)) -testCodec($.u128, generateIntTests(false, 128).map(BigInt)) -testCodec($.u256, generateIntTests(false, 256).map(BigInt)) +testShape($.u8, generateIntTests(false, 8).map(Number)) +testShape($.u16, generateIntTests(false, 16).map(Number)) +testShape($.u32, generateIntTests(false, 32).map(Number)) +testShape($.u64, generateIntTests(false, 64).map(BigInt)) +testShape($.u128, generateIntTests(false, 128).map(BigInt)) +testShape($.u256, generateIntTests(false, 256).map(BigInt)) -testCodec($.i8, generateIntTests(true, 8).map(Number)) -testCodec($.i16, generateIntTests(true, 16).map(Number)) -testCodec($.i32, generateIntTests(true, 32).map(Number)) -testCodec($.i64, generateIntTests(true, 64).map(BigInt)) -testCodec($.i128, generateIntTests(true, 128).map(BigInt)) -testCodec($.i128, generateIntTests(true, 128).map(BigInt)) -testCodec($.i256, generateIntTests(true, 256).map(BigInt)) +testShape($.i8, generateIntTests(true, 8).map(Number)) +testShape($.i16, generateIntTests(true, 16).map(Number)) +testShape($.i32, generateIntTests(true, 32).map(Number)) +testShape($.i64, generateIntTests(true, 64).map(BigInt)) +testShape($.i128, generateIntTests(true, 128).map(BigInt)) +testShape($.i128, generateIntTests(true, 128).map(BigInt)) +testShape($.i256, generateIntTests(true, 256).map(BigInt)) function generateIntInvalids(signed: boolean, size: number, fn: typeof Number | typeof BigInt) { const s = BigInt(size) diff --git a/codecs/test/iterable.test.ts b/shapes/test/iterable.test.ts similarity index 79% rename from codecs/test/iterable.test.ts rename to shapes/test/iterable.test.ts index ec09323..bb88c7e 100644 --- a/codecs/test/iterable.test.ts +++ b/shapes/test/iterable.test.ts @@ -1,5 +1,5 @@ import * as $ from "../../mod.ts" -import { testCodec } from "../../test-util.ts" +import { testShape } from "../../test-util.ts" const $iterableArray = $.withMetadata( $.metadata("$iterableArray"), @@ -11,7 +11,7 @@ const $iterableArray = $.withMetadata( }), ) -testCodec($iterableArray, [ +testShape($iterableArray, [ [], [0, 2, 4, 8], [2, 3, 5, 7], diff --git a/codecs/test/lenPrefixed.test.ts b/shapes/test/lenPrefixed.test.ts similarity index 55% rename from codecs/test/lenPrefixed.test.ts rename to shapes/test/lenPrefixed.test.ts index f4ef7aa..7fa2b1d 100644 --- a/codecs/test/lenPrefixed.test.ts +++ b/shapes/test/lenPrefixed.test.ts @@ -1,15 +1,15 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" -testCodec($.lenPrefixed($.constant(null)), [null]) +testShape($.lenPrefixed($.constant(null)), [null]) -testCodec($.lenPrefixed($.array($.compact($.u32))), { +testShape($.lenPrefixed($.array($.compact($.u32))), { empty: [], primes: [2, 3, 5, 7, 11, 13, 17, 23, 29], squares: [...Array(1000)].map((_, i) => i ** 2), }) -testCodec($.lenPrefixed($.promise($.compact($.u32))), { +testShape($.lenPrefixed($.promise($.compact($.u32))), { 123: () => new Promise((r) => setTimeout(() => r(123), 100)), }, true) diff --git a/codecs/test/never.test.ts b/shapes/test/never.test.ts similarity index 51% rename from codecs/test/never.test.ts rename to shapes/test/never.test.ts index 70a4056..48ece10 100644 --- a/codecs/test/never.test.ts +++ b/shapes/test/never.test.ts @@ -1,7 +1,7 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" -testCodec($.option($.never), [undefined]) +testShape($.option($.never), [undefined]) testInvalid($.never, [null, 0]) testInvalid($.option($.never), [null, 0]) diff --git a/codecs/test/object.test.ts b/shapes/test/object.test.ts similarity index 91% rename from codecs/test/object.test.ts rename to shapes/test/object.test.ts index 825182c..2a83417 100644 --- a/codecs/test/object.test.ts +++ b/shapes/test/object.test.ts @@ -1,5 +1,5 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" const $person = $.withMetadata( $.metadata("$person"), @@ -11,7 +11,7 @@ const $person = $.withMetadata( ), ) -testCodec($person, [ +testShape($person, [ { name: "Darrel", nickName: "The Durst", @@ -45,7 +45,7 @@ const $bar = $.field("bar", $.u8) const $foobar = $.object($foo, $bar) -testCodec($foobar, [ +testShape($foobar, [ { _tag: "a", bar: 123 }, { _tag: "b", x: 0, bar: 123 }, ]) diff --git a/codecs/test/option.test.ts b/shapes/test/option.test.ts similarity index 56% rename from codecs/test/option.test.ts rename to shapes/test/option.test.ts index 309e334..176a650 100644 --- a/codecs/test/option.test.ts +++ b/shapes/test/option.test.ts @@ -1,11 +1,11 @@ import * as $ from "../../mod.ts" -import { assertThrows, testCodec, testInvalid } from "../../test-util.ts" +import { assertThrows, testInvalid, testShape } from "../../test-util.ts" -testCodec($.option($.str), ["HELLO!"]) -testCodec($.option($.u8), [1]) -testCodec($.option($.u32), [2 ** 32 - 1]) -testCodec($.option($.bool), [true, false, undefined]) -testCodec($.option($.str, null), ["hi", "low", null]) +testShape($.option($.str), ["HELLO!"]) +testShape($.option($.u8), [1]) +testShape($.option($.u32), [2 ** 32 - 1]) +testShape($.option($.bool), [true, false, undefined]) +testShape($.option($.str, null), ["hi", "low", null]) testInvalid($.option($.bool), [123]) @@ -14,7 +14,7 @@ Deno.test("option roundtrip error", () => { assertThrows(() => $.option($.withMetadata($.metadata("$foo"), $.option($.u8)))) assertThrows(() => { const $foo = $.option($.u8) - $foo._metadata = [] + $foo.metadata = [] $.option($foo) // Some(None) .decode(new Uint8Array([1, 0])) diff --git a/shapes/test/optionBool.test.ts b/shapes/test/optionBool.test.ts new file mode 100644 index 0000000..ba23ace --- /dev/null +++ b/shapes/test/optionBool.test.ts @@ -0,0 +1,6 @@ +import * as $ from "../../mod.ts" +import { testInvalid, testShape } from "../../test-util.ts" + +testShape($.optionBool, [undefined, true, false]) + +testInvalid($.optionBool, [123]) diff --git a/codecs/test/promise.test.ts b/shapes/test/promise.test.ts similarity index 73% rename from codecs/test/promise.test.ts rename to shapes/test/promise.test.ts index b92880e..afcc7f2 100644 --- a/codecs/test/promise.test.ts +++ b/shapes/test/promise.test.ts @@ -1,19 +1,19 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" -testCodec($.promise($.u8), { +testShape($.promise($.u8), { 0: () => prom(0), 255: () => prom(255), }, true) -testCodec($.array($.promise($.u8)), { +testShape($.array($.promise($.u8)), { empty: () => [], single: () => [prom(0)], sequential: () => Array.from({ length: 256 }, (_, i) => prom(i)), times13: () => Array.from({ length: 256 }, (_, i) => prom(i * 13 % 256)), }, true) -testCodec($.promise($.array($.promise($.compact($.u32)))), { +testShape($.promise($.array($.promise($.compact($.u32)))), { times13: () => Promise.resolve(Array.from({ length: 256 }, (_, i) => prom(i * 13 % 256))), }, true) diff --git a/codecs/test/record.test.ts b/shapes/test/record.test.ts similarity index 73% rename from codecs/test/record.test.ts rename to shapes/test/record.test.ts index 586a7ea..958252c 100644 --- a/codecs/test/record.test.ts +++ b/shapes/test/record.test.ts @@ -1,12 +1,12 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" -testCodec($.record($.str), [ +testShape($.record($.str), [ { a: "hi", b: "sup", c: "yo" }, { the: "quick", brown: "fox", jumped: "over", theLazy: "hen" }, ]) -testCodec($.record($.record($.u8)), [{ +testShape($.record($.record($.u8)), [{ one: { two: 3 }, four: { five: 6 }, seven: { eight: 9 }, diff --git a/codecs/test/result.test.ts b/shapes/test/result.test.ts similarity index 86% rename from codecs/test/result.test.ts rename to shapes/test/result.test.ts index 8129ddc..87bd999 100644 --- a/codecs/test/result.test.ts +++ b/shapes/test/result.test.ts @@ -1,5 +1,5 @@ import * as $ from "../../mod.ts" -import { assertThrows, testCodec, testInvalid } from "../../test-util.ts" +import { assertThrows, testInvalid, testShape } from "../../test-util.ts" class StrErr extends Error { constructor(readonly str: string) { @@ -10,7 +10,7 @@ class StrErr extends Error { const $strError = $.instance(StrErr, $.tuple($.str), (err: StrErr) => [err.str]) -testCodec($.result($.str, $strError), [ +testShape($.result($.str, $strError), [ "ok", new StrErr("err"), ]) @@ -27,7 +27,7 @@ Deno.test("result roundtrip error", async () => { assertThrows(() => $.result($.withMetadata($.metadata("$foo"), $.result($.u8, $strError)), $strError)) assertThrows(() => { const $foo = $.result($.u8, $strError) - $foo._metadata = [] + $foo.metadata = [] $.result($foo, $strError) // Ok(Err("")) .decode(new Uint8Array([0, 1, 0])) diff --git a/codecs/test/str.test.ts b/shapes/test/str.test.ts similarity index 77% rename from codecs/test/str.test.ts rename to shapes/test/str.test.ts index 30f1d9f..930bbb7 100644 --- a/codecs/test/str.test.ts +++ b/shapes/test/str.test.ts @@ -1,7 +1,7 @@ import * as $ from "../../mod.ts" -import { files, testCodec, testInvalid } from "../../test-util.ts" +import { files, testInvalid, testShape } from "../../test-util.ts" -testCodec($.str, { +testShape($.str, { "\"\"": "", quickBrownFox: "The quick brown fox jumps over the lazy dog", lipsum: await files.lipsum(), diff --git a/codecs/test/transform.test.ts b/shapes/test/transform.test.ts similarity index 71% rename from codecs/test/transform.test.ts rename to shapes/test/transform.test.ts index 38538a2..5418ba5 100644 --- a/codecs/test/transform.test.ts +++ b/shapes/test/transform.test.ts @@ -1,5 +1,5 @@ import * as $ from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" const $boxU8 = $.withMetadata( $.metadata("$boxU8"), @@ -11,6 +11,6 @@ const $boxU8 = $.withMetadata( }), ) -testCodec($boxU8, [{ value: 0 }, { value: 1 }]) +testShape($boxU8, [{ value: 0 }, { value: 1 }]) testInvalid($boxU8, [{ value: -1 }]) diff --git a/codecs/test/tuple.test.ts b/shapes/test/tuple.test.ts similarity index 50% rename from codecs/test/tuple.test.ts rename to shapes/test/tuple.test.ts index 84873c6..cc77bc8 100644 --- a/codecs/test/tuple.test.ts +++ b/shapes/test/tuple.test.ts @@ -1,9 +1,9 @@ import * as $ from "../../mod.ts" -import { files, testCodec, testInvalid } from "../../test-util.ts" +import { files, testInvalid, testShape } from "../../test-util.ts" -testCodec($.tuple($.str, $.u8, $.str, $.u32), [["HELLO!", 1, await files.lipsum(), 2 ** 32 - 1]]) +testShape($.tuple($.str, $.u8, $.str, $.u32), [["HELLO!", 1, await files.lipsum(), 2 ** 32 - 1]]) -testCodec($.tuple($.str, $.i16, $.option($.u16)), [["GOODBYE!", 2, 101]]) +testShape($.tuple($.str, $.i16, $.option($.u16)), [["GOODBYE!", 2, 101]]) testInvalid($.tuple($.str, $.u8), [ null, diff --git a/codecs/test/union.test.ts b/shapes/test/union.test.ts similarity index 87% rename from codecs/test/union.test.ts rename to shapes/test/union.test.ts index 2ea7140..e0a2c01 100644 --- a/codecs/test/union.test.ts +++ b/shapes/test/union.test.ts @@ -1,6 +1,6 @@ import * as $ from "../../mod.ts" import { metadata } from "../../mod.ts" -import { testCodec, testInvalid } from "../../test-util.ts" +import { testInvalid, testShape } from "../../test-util.ts" const $abc = $.withMetadata( metadata("$abc"), @@ -36,16 +36,16 @@ const names = [ const $names = $.withMetadata(metadata("$.literalUnion(names)"), $.literalUnion(names)) -testCodec($abc, [ +testShape($abc, [ { _tag: "A" }, { _tag: "B", B: "HELLO" }, { _tag: "C", C: [255, 101010101n] }, { _tag: "D", a: 101, b: 999n }, ]) -testCodec($names, [...names]) +testShape($names, [...names]) -testCodec($interestingU8s, Object.values(interestingU8s)) +testShape($interestingU8s, Object.values(interestingU8s)) testInvalid($abc, [ null, diff --git a/shapes/transform.ts b/shapes/transform.ts new file mode 100644 index 0000000..d21ea7e --- /dev/null +++ b/shapes/transform.ts @@ -0,0 +1,25 @@ +import { AssertState, createShape, metadata, Shape } from "../common/mod.ts" + +export function transform( + props: { + $base: Shape + encode: (value: UI) => TI + decode: (value: TO) => UO + assert?: (this: Shape, assert: AssertState) => void + }, +): Shape { + return createShape({ + metadata: metadata("$.transform", transform, props), + staticSize: props.$base.staticSize, + subEncode(buffer, value) { + props.$base.subEncode(buffer, props.encode(value)) + }, + subDecode(buffer) { + return props.decode(props.$base.subDecode(buffer)) + }, + subAssert(assert) { + props.assert?.call(this, assert) + props.$base.subAssert(new AssertState(props.encode(assert.value as UI), "#encode", assert)) + }, + }) +} diff --git a/shapes/tuple.ts b/shapes/tuple.ts new file mode 100644 index 0000000..b5dc279 --- /dev/null +++ b/shapes/tuple.ts @@ -0,0 +1,34 @@ +import { AnyShape, createShape, Input, metadata, Output, Shape } from "../common/mod.ts" + +export type InputTuple = { + readonly [K in keyof T]: Input +} +export type OutputTuple = { + [K in keyof T]: Output +} + +export function tuple(...shapes: [...T]): Shape, OutputTuple> { + return createShape({ + metadata: metadata("$.tuple", tuple, ...shapes), + staticSize: shapes.map((x) => x.staticSize).reduce((a, b) => a + b, 0), + subEncode(buffer, value) { + for (let i = 0; i < shapes.length; i++) { + shapes[i].subEncode(buffer, value[i] as never) + } + }, + subDecode(buffer) { + const value = Array(shapes.length) + for (let i = 0; i < shapes.length; i++) { + value[i] = shapes[i].subDecode(buffer) + } + return value as any + }, + subAssert(assert) { + assert.instanceof(this, Array) + assert.key(this, "length").equals(this, shapes.length) + for (let i = 0; i < shapes.length; i++) { + shapes[i].subAssert(assert.key(this, i)) + } + }, + }) +} diff --git a/codecs/union.ts b/shapes/union.ts similarity index 63% rename from codecs/union.ts rename to shapes/union.ts index a7bec67..42ccc26 100644 --- a/codecs/union.ts +++ b/shapes/union.ts @@ -1,15 +1,15 @@ -import { AnyCodec, Input, Output } from "../common/codec.ts" -import { Codec, createCodec, Expand, metadata, Narrow, ScaleAssertError, ScaleDecodeError } from "../common/mod.ts" +import { createShape, Expand, metadata, Narrow, Shape, ShapeAssertError, ShapeDecodeError } from "../common/mod.ts" +import { AnyShape, Input, Output } from "../common/shape.ts" import { constant } from "./constant.ts" import { field, InputObject, object, ObjectMembers, OutputObject } from "./object.ts" export class Variant { - constructor(readonly tag: T, readonly codec: Codec) {} + constructor(readonly tag: T, readonly shape: Shape) {} } export type AnyVariant = Variant -export function variant( +export function variant( tag: T, ...members: ObjectMembers ): Variant, OutputObject> { @@ -22,7 +22,7 @@ export type InputTaggedUnion< > = { [I in keyof M]: Expand< & Readonly["tag"]>> - & Input["codec"]> + & Input["shape"]> > }[keyof M & number] export type OutputTaggedUnion< @@ -31,52 +31,52 @@ export type OutputTaggedUnion< > = { [I in keyof M]: Expand< & Record["tag"]> - & Output["codec"]> + & Output["shape"]> > }[keyof M & number] export function taggedUnion< K extends keyof any, M extends [] | Record>, ->(tagKey: K, members: M): Codec, OutputTaggedUnion> { +>(tagKey: K, members: M): Shape, OutputTaggedUnion> { const tagToDiscriminant: Record = Object.create(null) - const discriminantToMember: Record = Object.create(null) + const discriminantToMember: Record = Object.create(null) for (const _discriminant in members) { const discriminant = +_discriminant if (isNaN(discriminant)) continue - const { tag, codec } = (members as M)[discriminant]! + const { tag, shape } = (members as M)[discriminant]! tagToDiscriminant[tag] = discriminant - discriminantToMember[discriminant] = object(field(tagKey, constant(tag)) as any, codec) + discriminantToMember[discriminant] = object(field(tagKey, constant(tag)) as any, shape) } - return createCodec({ - _metadata: metadata("$.taggedUnion", taggedUnion, tagKey, members), - _staticSize: 1 + Math.max(...Object.values(discriminantToMember).map((x) => x._staticSize)), - _encode(buffer, value) { + return createShape({ + metadata: metadata("$.taggedUnion", taggedUnion, tagKey, members), + staticSize: 1 + Math.max(...Object.values(discriminantToMember).map((x) => x.staticSize)), + subEncode(buffer, value) { const discriminant = tagToDiscriminant[value[tagKey]]! const $member = discriminantToMember[discriminant]! buffer.array[buffer.index++] = discriminant - $member._encode(buffer, value as never) + $member.subEncode(buffer, value as never) }, - _decode(buffer) { + subDecode(buffer) { const discriminant = buffer.array[buffer.index++]! const $member = discriminantToMember[discriminant] if (!$member) { - throw new ScaleDecodeError(this, buffer, `No such member codec matching the discriminant \`${discriminant}\``) + throw new ShapeDecodeError(this, buffer, `No such member shape matching the discriminant \`${discriminant}\``) } - return $member._decode(buffer) as any + return $member.subDecode(buffer) as any }, - _assert(assert) { + subAssert(assert) { const assertTag = assert.key(this, tagKey) assertTag.typeof(this, "string") if (!((assertTag.value as string) in tagToDiscriminant)) { - throw new ScaleAssertError(this, assertTag.value, `${assertTag.path}: invalid tag`) + throw new ShapeAssertError(this, assertTag.value, `${assertTag.path}: invalid tag`) } - discriminantToMember[tagToDiscriminant[assertTag.value as string]!]!._assert(assert) + discriminantToMember[tagToDiscriminant[assertTag.value as string]!]!.subAssert(assert) }, }) } -export function literalUnion(members: Record): Codec { +export function literalUnion(members: Record): Shape { const keyToDiscriminant: Map = new Map() for (const _discriminant in members) { const discriminant = +_discriminant @@ -84,21 +84,21 @@ export function literalUnion(members: Record): Code const key = members[discriminant] as T keyToDiscriminant.set(key, discriminant) } - return createCodec({ - _metadata: metadata("$.literalUnion", literalUnion, members), - _staticSize: 1, - _encode(buffer, value) { + return createShape({ + metadata: metadata("$.literalUnion", literalUnion, members), + staticSize: 1, + subEncode(buffer, value) { const discriminant = keyToDiscriminant.get(value)! buffer.array[buffer.index++] = discriminant }, - _decode(buffer) { + subDecode(buffer) { const discriminant = buffer.array[buffer.index++]! return members[discriminant]! }, - _assert(assert) { + subAssert(assert) { assert.typeof(this, "string") if (!keyToDiscriminant.has(assert.value as T)) { - throw new ScaleAssertError(this, assert.value, `${assert.path} invalid value`) + throw new ShapeAssertError(this, assert.value, `${assert.path} invalid value`) } }, }) diff --git a/test-util.ts b/test-util.ts index 29588c8..fe474aa 100644 --- a/test-util.ts +++ b/test-util.ts @@ -2,20 +2,20 @@ import { assertEquals, assertThrows } from "https://deno.land/std@0.161.0/testing/asserts.ts" import { assertSnapshot } from "https://deno.land/std@0.161.0/testing/snapshot.ts" -import { AnyCodec, assert, Codec, DecodeBuffer, ScaleAssertError } from "./common/mod.ts" +import { AnyShape, assert, DecodeBuffer, Shape, ShapeAssertError } from "./common/mod.ts" const [lipsum, words, cargoLock] = ["lipsum.txt", "words.txt", "Cargo.lock"].map((fileName) => () => Deno.readTextFile(fileName) ) export const files = { lipsum: lipsum!, words: words!, cargoLock: cargoLock! } -export function testCodec( - codec: Codec, +export function testShape( + shape: Shape, values: NoInfer[] | Record | (() => NoInfer)>, async?: boolean, ): void -export function testCodec( - codec: Codec, +export function testShape( + shape: Shape, values: T[] | Record T)>, async = false, ) { @@ -29,23 +29,23 @@ export function testCodec( iterableLimit: Infinity, }) : key - Deno.test(`${Deno.inspect(codec)} ${label}`, async (t) => { + Deno.test(`${Deno.inspect(shape)} ${label}`, async (t) => { if (typeof value === "function") { value = (value as () => T)() } - assert(codec, value) - const encoded = async ? await codec.encodeAsync(value) : codec.encode(value) + assert(shape, value) + const encoded = async ? await shape.encodeAsync(value) : shape.encode(value) await assertSnapshot(t, encoded, { serializer: serializeU8A }) const decodeBuffer = new DecodeBuffer(encoded) - const decoded = codec._decode(decodeBuffer) + const decoded = shape.subDecode(decodeBuffer) assertEquals(decoded, value) assertEquals(decodeBuffer.index, encoded.length) - assert(codec, decoded) + assert(shape, decoded) }) } } -export function testInvalid(codec: AnyCodec, values: unknown[] | Record unknown)>) { +export function testInvalid(shape: AnyShape, values: unknown[] | Record unknown)>) { for (const key in values) { let value: unknown = values[key as never] const label = values instanceof Array @@ -56,11 +56,11 @@ export function testInvalid(codec: AnyCodec, values: unknown[] | Record { + Deno.test(`${Deno.inspect(shape)} invalid ${label}`, async (t) => { if (typeof value === "function") { value = (value as () => unknown)() } - await assertThrowsSnapshot(t, () => assert(codec as Codec, value), ScaleAssertError) + await assertThrowsSnapshot(t, () => assert(shape as Shape, value), ShapeAssertError) }) } } @@ -70,14 +70,14 @@ function serializeU8A(array: Uint8Array) { } type NoInfer = T extends infer U ? U : never -export function benchCodec(name: string, codec: Codec, value: NoInfer): void -export function benchCodec(name: string, codec: Codec, value: T) { - const encoded = codec.encode(value) +export function benchShape(name: string, shape: Shape, value: NoInfer): void +export function benchShape(name: string, shape: Shape, value: T) { + const encoded = shape.encode(value) Deno.bench(`- ${name} (${encoded.length}B) [encode] `, () => { - codec.encode(value) + shape.encode(value) }) Deno.bench(` ${name} (${encoded.length}B) [decode]`, () => { - codec.decode(encoded) + shape.decode(encoded) }) } diff --git a/words.txt b/words.txt index e5e1d89..fc8862c 100644 --- a/words.txt +++ b/words.txt @@ -4,11 +4,9 @@ bitvec combinators contravariance cummon -Decodec deno denoland dprint -Encodec hydrokinesis jsbin Kosmoceratops @@ -20,5 +18,6 @@ protobuf Psittacosaurus rustup soramitsu +subshape typeof xffn