Schema 0.64 (Release)
The To
and From
type extractors have been renamed to Type
and Encoded
respectively.
Before:
Now:
The reason for this change is that the terms “From” and “To” were too generic and depended on the context. For example, when encoding, the meaning of “From” and “To” were reversed.
As a consequence, the APIs AST.to
, AST.from
, Schema.to
, and Schema.from
have been renamed respectively to AST.typeAST
, AST.encodedAST
, Schema.typeSchema
, and Schema.encodedSchema
.
Now, in addition to the pipe
method, all schemas have a annotations
method that can be used to add annotations:
For backward compatibility and to leverage a pipeline, you can still use the pipeable S.annotations
API:
An “API Interface” is an interface
specifically defined for a schema exported from @effect/schema
or for a particular API exported from @effect/schema
. Let’s see an example with a simple schema:
Example (An Age
schema)
The benefit is that when we hover over the Age
schema, we see Age
instead of Schema<number, number, never>
. This is a small improvement if we only think about the Age
schema, but as we’ll see shortly, these improvements in schema visualization add up, resulting in a significant improvement in the readability of our schemas.
Many of the built-in schemas exported from @effect/schema
have been equipped with API interfaces, for example number
or never
.
Note. Notice that we had to add a $
suffix to the API interface name because we couldn’t simply use “number” since it’s a reserved name for the TypeScript number
type.
Now let’s see an example with a combinator that, given an input schema for a certain type A
, returns the schema of the pair readonly [A, A]
:
Example (A pair
combinator)
Note: The S.Schema.Any
helper represents any schema, except for never
. For more information on the asSchema
helper, refer to the following section “Understanding Opaque Names”.
If we try to use our pair
combinator, we see that readability is also improved in this case:
In hover, we simply see pair<S.$number>
instead of the old:
The new name is not only shorter and more readable but also carries along the origin of the schema, which is a call to the pair
combinator.
Opaque names generated in this way are very convenient, but sometimes there’s a need to see what the underlying types are, perhaps for debugging purposes while you declare your schemas. At any time, you can use the asSchema
function, which returns an Schema<A, I, R>
compatible with your opaque definition:
Note. The call to asSchema
is negligible in terms of overhead since it’s nothing more than a glorified identity function.
Many of the built-in combinators exported from @effect/schema
have been equipped with API interfaces, for example struct
:
In hover, we simply see:
instead of the old:
The benefits of API interfaces don’t end with better readability; in fact, the driving force behind the introduction of API interfaces arises more from the need to expose some important information about the schemas that users generate. Let’s see some examples related to literals and structs:
Example (Exposed literals)
Now when we define literals, we can retrieve them using the literals
field exposed by the generated schema:
Example (Exposed fields)
Similarly to what we’ve seen for literals, when we define a struct, we can retrieve its fields
:
Being able to retrieve the fields
is particularly advantageous when you want to extend a struct with new fields; now you can do it simply using the spread operator:
The list of APIs equipped with API interfaces is extensive; here we provide only the main ones just to give you an idea of the new development possibilities that have opened up:
All the API interfaces equipped with schemas and built-in combinators are compatible with the annotations
method, meaning that their type is not lost but remains the original one before annotation:
As you can see, the type of Name
is still $string
and has not been lost, becoming Schema<string, string, never>
.
This doesn’t happen by default with API interfaces defined in userland:
However, the fix is very simple; just modify the definition of the Age
API interface using the Annotable
interface exported by @effect/schema
:
Now, defining a Class
requires an identifier (to avoid dual package hazard):
Similar to the case with struct
, classes now also expose fields
:
Now the struct
constructor optionally accepts a list of key/value pairs representing index signatures:
Example
Since the record
constructor returns a schema that exposes both the key
and the value
, instead of passing a bare object { key, value }
, you can use the record
constructor:
The tuple
constructor has been improved to allow building any variant supported by TypeScript:
As before, to define a tuple with required elements, simply specify the list of elements:
To define an optional element, wrap the schema of the element with the optionalElement
modifier:
To define rest elements, follow the list of elements (required or optional) with an element for the rest:
and optionally other elements that follow the rest:
The definition of property signatures has been completely redesigned to allow for any type of transformation. Recall that a PropertySignature
generally represents a transformation from a “From” field:
to a “To” field:
Let’s start with the simple definition of a property signature that can be used to add annotations:
Let’s delve into the details of all the information contained in the type of a PropertySignature
:
age
: is the key of the “To” fieldToToken
: either"?:"
or":"
,"?:"
indicates that the “To” field is optional,":"
indicates that the “To” field is requiredToType
: the type of the “To” fieldFromKey
(optional, default =never
): indicates the key from the field from which the transformation starts, by default it is equal to the key of the “To” field (i.e.,"age"
in this case)FormToken
: either"?:"
or":"
,"?:"
indicates that the “From” field is optional,":"
indicates that the “From” field is requiredFromType
: the type of the “From” field
In our case, the type
indicates that there is the following transformation:
age
is the key of the “To” fieldToToken = ":"
indicates that theage
field is requiredToType = number
indicates that the type of theage
field isnumber
FromKey = never
indicates that the decoding occurs from the same field namedage
FormToken = "."
indicates that the decoding occurs from a requiredage
fieldFromType = string
indicates that the decoding occurs from astring
typeage
field
Let’s see an example of decoding:
Now, suppose the field from which decoding occurs is named "AGE"
, but for our model, we want to keep the name in lowercase "age"
. To achieve this result, we need to map the field key from "AGE"
to "age"
, and to do that, we can use the fromKey
combinator:
This modification is represented in the type of the created PropertySignature
:
Now, let’s see an example of decoding:
Now messages are not only of type string
but can return an Effect
so that they can have dependencies (for example, from an internationalization service). Let’s see the outline of a similar situation with a very simplified example for demonstration purposes:
- The
Format
module has been removed
-
Tuple
has been refactored toTupleType
, and its_tag
has consequently been renamed. The type of itsrest
property has changed fromOption.Option<ReadonlyArray.NonEmptyReadonlyArray<AST>>
toReadonlyArray<AST>
. -
Transform
has been refactored toTransformation
, and its_tag
property has consequently been renamed. Its propertytransformation
has now the typeTransformationKind = FinalTransformation | ComposeTransformation | TypeLiteralTransformation
. -
createRecord
has been removed -
AST.to
has been renamed toAST.typeAST
-
AST.from
has been renamed toAST.encodedAST
-
ExamplesAnnotation
andDefaultAnnotation
now accept a type parameter -
format
has been removed: BeforeNow
-
setAnnotation
has been removed (useannotations
instead) -
mergeAnnotations
has been renamed toannotations
-
The
ParseResult
module now uses classes and custom constructors have been removed: BeforeNow
-
Transform
has been refactored toTransformation
, and itskind
property now accepts"Encoded"
,"Transformation"
, or"Type"
as values -
move
defaultParseOption
fromParser.ts
toAST.ts
-
uniqueSymbol
has been renamed touniqueSymbolFromSelf
-
Schema.Schema.To
has been renamed toSchema.Schema.Type
, andSchema.to
toSchema.typeSchema
-
Schema.Schema.From
has been renamed toSchema.Schema.Encoded
, andSchema.from
toSchema.encodedSchema
-
The type parameters of
TaggedRequest
have been swapped -
The signature of
PropertySignature
has been changed fromPropertySignature<From, FromOptional, To, ToOptional>
toPropertySignature<ToToken extends Token, To, Key extends PropertyKey, FromToken extends Token, From, R>
-
Class APIs
- Class APIs now expose
fields
and require an identifier
- Class APIs now expose
-
element
andrest
have been removed in favor ofarray
andtuple
:Before
Now
-
optionalElement
has been refactored:Before
Now
-
use
TreeFormatter
inBrandSchema
s -
Schema annotations interfaces have been refactored:
- add
PropertySignatureAnnotations
(baseline) - remove
DocAnnotations
- rename
DeclareAnnotations
toAnnotations
- add
-
propertySignatureAnnotations
has been replaced by thepropertySignature
constructor which owns aannotations
method BeforeNow
- The type parameters of
SerializableWithResult
andWithResult
have been swapped
-
enhance the
struct
API to allow records: -
enhance the
extend
API to allow nested (non-overlapping) fields: -
add
Annotable
interface -
add
asSchema
-
add add
Schema.Any
,Schema.All
,Schema.AnyNoContext
helpers -
refactor
annotations
API to be a method within theSchema
interface -
add support for
AST.keyof
,AST.getPropertySignatures
,Parser.getSearchTree
to Classes -
fix
BrandAnnotation
type and addgetBrandAnnotation
-
add
annotations?
parameter to Class constructors: