subShape provides primitives and patterns for crafting composable shapes featuring cohesive typing, validation, serialization, and reflection.
npm install @talismn/subshape-fork> ### _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.
``ts`
import * as $ from "https://deno.land/x/subshape/mod.ts"
``
npm install subshape
`ts`
import * as $ from "@talismn/subshape-fork"
`ts
import * as $ from "https://deno.land/x/subshape/mod.ts"
const $superhero = $.object(
$.field("pseudonym", $.str),
$.optionalField("secretIdentity", $.str),
$.field("superpowers", $.array($.str)),
)
`
#### Typing
`ts`
type Superhero = $.Output
// type Superhero = {
// pseudonym: string;
// secretIdentity?: string | undefined;
// superpowers: string[];
// }
#### Validation
`ts
const spiderMan = {
pseudonym: "Spider-Man",
secretIdentity: "Peter Parker",
superpowers: ["does whatever a spider can"],
}
$.assert($superhero, spiderMan) // ok!
const bob = {
pseudonym: "Bob",
secretIdentity: "Robert",
superpowers: null,
}
$.assert($superhero, bob) // ShapeAssertError: !(value.superpowers instanceof Array)
`
#### Serialization
`ts
const encoded = $superhero.encode(spiderMan)
// encoded: Uint8Array
const decoded = $superhero.decode(encoded)
// decoded: Superhero
console.log(decoded)
// Prints:
// {
// pseudonym: "Spider-Man",
// secretIdentity: "Peter Parker",
// superpowers: [ "does whatever a spider can" ]
// }
`
#### Reflection
`ts
$superhero.metadata // Metadata
console.log($superhero)
// Prints:
// $.object(
// $.field("pseudonym", $.str),
// $.optionalField("secretIdentity", $.str),
// $.field("superpowers", $.array($.str))
// )
`
Further examples can be found in the
examples
directory.
This library adopts a convention of denoting shapes with a $ – $.foo for$foo
built-in shapes, and 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 { ... }
const $person = $.object(...)
const person = { ... }
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.
While we recommend following this convention for consistency, you can, of
course, adopt an alternative convention if the $ is problematic – $.foo cans.foo
easily become or subshape.foo with an alternate import name.
Some shapes require asynchronous encoding. Calling .encode() on a shape will.encodeAsync()
throw if it or another shape it calls is asynchronous. In this case, you must
call instead, which returns a Promise. You can.encodeAsync()
call on any shape; if it is a synchronous shape, it will simply
resolve immediately.
Asynchronous decoding is not supported.
If your encoding/decoding logic is more complicated, you can create custom
shapes with createShape:
`ts
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,
subEncode(buffer, value) {
// Encode value into buffer.array, starting at buffer.index.DataView
// A is also supplied as buffer.view.staticSize
// At first, you may only write at most as many bytes as .buffer.index
// After you write bytes, you must update to be the first unwritten byte.
// If you need to write more bytes, call buffer.pushAlloc(size).size
// If you do this, you can then write at most bytes,buffer.popAlloc()
// and then you must call .
// You can also call buffer.insertArray() to insert an array without consuming any bytes.
// You can delegate to another shape by calling $bar.subEncode(buffer, bar).$bar.staticSize
// Before doing so, you must ensure that bytes are free,staticSize
// either by including it in or by calling buffer.pushAlloc().subEncode
// Note that you should use and not encode.
// See the EncodeBuffer class for information on other methods.
// ...
},
subDecode(buffer) {
// Decode value from buffer.array, starting at buffer.index.DataView
// A is also supplied as buffer.view.buffer.index
// After you read bytes, you must update to be the first unread byte.
// You can delegate to another shape by calling $bar.subDecode(buffer).subDecode
// Note that you should use and not decode.
// ...
return value
},
subAssert(assert) {
// Validate that assert.value is valid for this shape.assert
// exposes various utility methods, such as assert.instanceof.AssertState
// See the class for information on other methods.
// You can delegate to another shape by calling $bar.subAssert(assert) or $bar.subAssert(assert.access("key")).$.ShapeAssertError
// Any errors thrown should be an instance of , and should use assert.path.
// ...
},
})
``