runtypes / runtypes Goto Github PK
View Code? Open in Web Editor NEWRuntime validation for static types
License: MIT License
Runtime validation for static types
License: MIT License
const Info = Record({
x: Number
}).And(Partial({
y: Number
}));
const AllPartialInfo = Partialize(Info);
type AllPartialInfo = Static<typeof AllPartialInfo>;
// -> { x?: number | undefined; } & { y?: number | undefined; }
Is there something like above Partialize
?
When reading the README, as a TypeScript n00b, I thought “oh, this is so close—if only I could use Static<T>
to make an interface
because that would be more usable for me than type
.” I didn’t realize I could just use:
interface IAsteroid extends Static<typeof Asteroid> {}
I think it could be helpful to add an example of this to the README.
I'm noticing that I can't seem to correctly extract the static type from Runtype's with nested rt.Record({}).
const Example = rt.Record({
b: rt.Record({
c: rt.String
})
});
type StaticExample = rt.Static<typeof Example>;
Effective type of StaticExample (as reported by vscode):
type StaticExample = {
b: any;
}
And this produces no type error ...
const x: StaticExample = {
b: ""
};
Am I doing something wrong or is this a bug or a known limitation? I'm using runtypes 2.0.3 with typescript 2.7.2 and this tsconfig:
{
"compileOnSave": true,
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"lib": ["es2017", "es2017.object", "esnext.asynciterable"],
"strict": true,
"strictNullChecks": true,
"noImplicitAny": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"module": "commonjs",
"esModuleInterop": true,
"target": "es2015",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"inlineSourceMap": true,
"inlineSources": true,
"forceConsistentCasingInFileNames": true,
"strictFunctionTypes": false,
"noErrorTruncation": true,
"strictPropertyInitialization": false
},
"include": ["./src/**/*"],
"exclude": ["node_modules"]
}
Hi!
This is a proof of concept gcanti/io-ts#29
Seems implementable with runtypes
too, what do you think about the API?
A common use case when validating data from external APIs is to have dates encoded as strings.
Let's say I get the following data from a JSON API:
{ "date": "2015-10-12 12:00:00" }
I would want to represent it with the following Runtype:
Record({ date: InstanceOf(Date) })
Is there a simple way to go from the first to the second without having to use an intermediate Record({ date: String })
runtype, and doing the validation ?
I think that having a convertFrom
method on the Runtype
interface would help a lot.
interface Runtype<A> {
// ...
convertFrom<B> : (checkBefore: (x: any) => B) => Runtype<A>;
}
The returned Runtype
would be exactly the same, but the check
method would be changed to include the additional validation in checkBefore
before the one from the initial Runtype
.
I could then do:
const Input = Record({
date: InstanceOf(Date).convertFrom(x => {
const strX = String.check(x);
const timestamp = Date.parse(strX);
if(isNan(timestamp)) {
throw new ValidationError('Invalid date string ' + strX);
}
return new Date(timestamp);
})
});
Do you think it could be added to the library (I would be willing to do it)?
Some examples of TypeScript's static error messages:
;(): number | boolean => 'hello'
// [ts] Type '"hello"' is not assignable to type 'number | boolean'.
;(): { x: number } => ({})
// [ts]
// Type '{}' is not assignable to type '{ x: number; }'.
// Property 'x' is missing in type '{}'.
;(): { x: number } => ({ x: 'hello' })
// [ts]
// Type '{ x: string; }' is not assignable to type '{ x: number; }'.
// Types of property 'x' are incompatible.
// Type 'string' is not assignable to type 'number'.
// Record({ x: Number }).check({ x: 'hello' })
;(): { x: number } | { x: boolean } => ({ x: 'hello' })
// [ts]
// Type '{ x: "hello"; }' is not assignable to type '{ x: number; } | { x: boolean; }'.
// Type '{ x: "hello"; }' is not assignable to type '{ x: boolean; }'.
// Types of property 'x' are incompatible.
// Type '"hello"' is not assignable to type 'boolean'.
;(): { x: number } | { x: boolean } => ({})
// [ts]
// Type '{}' is not assignable to type '{ x: number; } | { x: boolean; }'.
// Type '{}' is not assignable to type '{ x: boolean; }'.
// Property 'x' is missing in type '{}'.
Some notes:
show
The following simple example fails to build for me when setting declaration
to true
in TypeScript's compilerOptions
.
Code:
import * as rt from 'runtypes';
export const Test = rt.Record({ });
tsconfig.json
:
{
"include": [
"src/conversion/test.ts"
],
"compilerOptions": {
"noEmit": false,
"noUnusedLocals": true,
"strict": true,
"rootDir": ".",
"outDir": "./lib",
"declaration": true
}
}
Error:
src/conversion/test.ts(3,14): error TS4023: Exported variable 'Test' has or is using name 'Record' from external module ".../node_modules/runtypes/lib/types/record" but cannot be named.
Any idea why that is?
I'd really like to be able to define my types in runtypes, and then use the type MyType = Static<typeof MyType>
feature, but I cannot figure out how to incorporate this into my project
The examples show this all being done in one file, but what if I have a types.d.ts
file in my project where I am declaring the types that are shared throughout multiple files? I tried importing the runtypes that I declared and the Static
function into my types.d.ts
file and then declaring the type there, but that seemed to make my d.ts file invalid.
Any ideas?
Thanks in advance and thanks for this project.
Currently the Record type's validation verifies that a property exists before checking their types. This causes problems when some properties on a Record should be optional.
Current Behavior:
Record({
id: String,
name: Union(Undefined,String)
}).validate({id: '1234'}).success === false
Expected Behavior:
Record({
id: String,
name: Union(Undefined,String)
}).validate({id: '1234'}).success === true
Also acceptable:
Record({
id: String,
name: Optional(String)
}).validate({id: '1234'}).success === true
Current Workaround:
Intersect(
Record({
id: String,
}),
Partial({
name: String
})
).validate({id: '1234'}).success === true
It would be great to provide error meta data, something like this:
let error = {
success: false,
message: '...',
path: ['key1', 'key2', ...],
}
This can be useful for form validation because we validate the whole form with an object type, but need to show the message in each field.
In this simple example:
const SubValue = Record({
a: Number,
});
const ParentValue = Record({
v: Array(SubValue),
});
const pv = ParentValue.check({});
The type returns from check()
is {v: any[]}
. But when I look at the type of ParentValue it's fully realized. What am I missing?
First of all, thank you for putting such an amazing library. It utilizes both typescript and javascript really well, producing such an empowering and comprehensive developer experience :)
I found your project while looking at the ways to pattern match in typescript. So my suggestions are biased.
Let's say we have this:
const One = Record({ tag: Literal('one'), descOfOne: String });
const Two = Record({ tag: Literal('two'), descOfTwo: String });
type OneOrTwo = Static<typeof One> | Static<typeof Two>;
While matching I want to extract payload as well. Something like:
const DescriptionOfOne = One.pipe(x=>x.descOfOne);
//not Runtype anymore, can be used only for pattern matching
// OneOrTwo | null -> string
const getDescription = matchCase<OneOrTwo | null>()
.of(DescriptionOfOne, desc => desc) // acceptes extracted prop
.of(Two, ({descOfTwo}) => descOfTwo) // normal types can be used just fine
.of(Void, ()=> 'nothing');
DescriptionOfOne has different type now. Lets say Pattern<One, string>.
const NonEmptyDesc = DescriptionOfOne.withContraint( desc => desc.lenght >0)
// Error! Will not compile. Patterns can only be mapped over.
const OneDescLenght = DescriptionOfOne.map(decs => desc.lenght);
// totally valid though.
I think it makes sense to map them further to produce different pipelines.
But if we use Records extensively for matching we have perf issues :( "check" checks all props recursively.
const TaggedOne = Tag(One, 'tag'); //new Runtype that remembers which one is the tag
// disables deep tree matching and checks only tags.
// useful for application types (like redux actions)
// It might make sense to introduce Tagged({shape}) and TaggedLiteral('one') for that purpose as well.
Also with typescript 2.8 we will have a proper Diff<T,U>. And if I understand it correctly it opens this
const EmptyOne = One.withConstraint( one => one.descOfOne.length === 0);
//Possible from typescript 2.8 with a help of Diff
const getDescription = matchCase<OneOrTwo | null>()
.of(EmptyOne, _ => 'empty one') // leaves the same type
.guard(One, _ => "one") // removes One, so after we can guard only "Two | Void"
.guard(Two, _ => "two") // leaves just "Void" to check
.guard(Void, ()=> 'nothing'); // "never" after this point
.exhaustive() // can be applied only to "never".
// So it should give compile time error if not all types are ruled out
// .default(()=>'def val') same as of(Always, ()=>{}).
Note probably constrained runtypes cannot be guarded. Otherwise PositiveNumber can rule out Number < 0. Note: guarding on non constrained Patterns should be totally fine.
++ Side note:
Looking through the sources I noticed that "validate" uses try/catch from "check" to produce boolean result. Was there a specific reason for that? I guess if we hit an if statement that "validates" to false often there might be significant overhead for try/catch as well as allocating a Result object.
Does it make sense to have validateInternal(): true | string, aka either passed or failed with message. And then "check" can throw with a proper message.
+++ Side note n2:
You library is brilliant and inspiring. I felt that I love my job even more just thinking about these suggestions :)
I would love to collaborate/contribute if you like these ideas
Perhaps I'm missing something fundamental here about how this library makes assertions, but the following obviously errors in Typescript:
interface ISeries {
name: string;
value: number;
}
const series: ISeries = {
name: 'test',
value: 10,
foo: true,
};
but this check()
does not error with runtypes:
const Series = Record({
name: String,
value: Number,
});
Series.check({
name: 'test',
value: 10,
foo: true
});
Why is the additional key foo
allowed through in this case?
Sometimes we need type-check multiples of multiple parameters which are passed from the external world i.e. there're some callbacks to implement, and appending type-checking code for each param to every one of their function bodies is kind of painful. In such case I write a decorator which reduces keypresses more or less and makes functions' logic innate-looking, for example,
@checked(Runtype1, Runtype2, Runtype3)
method(param1: Static1, param2: Static2, param3: Static3) {
...
}
instead of
method(param1: Static1, param2: Static2, param3: Static3) {
Runtype1.check(param1)
Runtype2.check(param2)
Runtype3.check(param3)
...
}
How do you think about adding this feature to your library?
Within the integrated PowerShell of Visual Studio Code i cannot install Runtypes.
It fails with the error:
PS C:\Source\Ionic\flexApp> npm install runtypes --save
npm ERR! path C:\Source\Ionic\flexApp\node_modules\.staging\runtypes-20e0969c
npm ERR! code EPERM
npm ERR! errno -4048
npm ERR! syscall rename
npm ERR! Error: EPERM: operation not permitted, rename 'C:\Source\Ionic\flexApp\node_modules\.staging\runtypes-20e0969c' -> 'C:\Source\Ionic\flexApp\node_modules\runtypes'
npm ERR! at Error (native)
npm ERR! { Error: EPERM: operation not permitted, rename 'C:\Source\Ionic\flexApp\node_modules\.staging\runtypes-20e0969c' -> 'C:\Source\Ionic\flexApp\node_modules\runtypes'
npm ERR! at Error (native)
npm ERR! cause:
npm ERR! { Error: EPERM: operation not permitted, rename 'C:\Source\Ionic\flexApp\node_modules\.staging\runtypes-20e0969c' -> 'C:\Source\Ionic\flexApp\node_modules\runtypes'
npm ERR! at Error (native)
npm ERR! errno: -4048,
npm ERR! code: 'EPERM',
npm ERR! syscall: 'rename',
npm ERR! path: 'C:\\Source\\Ionic\\flexApp\\node_modules\\.staging\\runtypes-20e0969c',
npm ERR! dest: 'C:\\Source\\Ionic\\flexApp\\node_modules\\runtypes' },
npm ERR! stack: 'Error: EPERM: operation not permitted, rename \'C:\\Source\\Ionic\\flexApp\\node_modules\\.staging\\runtypes-20e0969c\' -> \'C:\\Source\\Ionic\\flexApp\\node_modules\\r
untypes\'\n at Error (native)',
npm ERR! errno: -4048,
npm ERR! code: 'EPERM',
npm ERR! syscall: 'rename',
npm ERR! path: 'C:\\Source\\Ionic\\flexApp\\node_modules\\.staging\\runtypes-20e0969c',
npm ERR! dest: 'C:\\Source\\Ionic\\flexApp\\node_modules\\runtypes',
npm ERR! parent: 'flexapp' }
npm ERR!
npm ERR! Please try running this command again as root/Administrator.
Whereas the same command (without Admin privileges!) is running successfully in the normal Windows commandline:
C:\Source\Ionic\flexApp>npm install runtypes --save
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: ts-jest@19.0.6 (node_modules\runtypes\node_modules\ts-jest):
npm WARN enoent SKIPPING OPTIONAL DEPENDENCY: ENOENT: no such file or directory, rename 'C:\Source\Ionic\flexApp\node_modules\runtypes\node_modules\ts-jest' -> 'C:\Source\Ionic\flexApp\node_modules\runtypes\node_modules\.ts-jest.DELETE'
+ runtypes@0.10.1
added 427 packages in 83.487s
Any Idea whats going wrong in Powershell within VS Code?
Hi @pelotom ,
I'm developing a module to bridge the safety gap across HTTP APIs, with first-class support for runtypes.
The module is called rest.ts and it lets you write API definitions which you can bind to client and servers in order to benefit from type safety on both ends of the API.
When used with runtypes, the server-side module validates all incoming data automatically.
I thought you might like this module because basically it is to HTTP APIs what runtypes is to basic types. It's almost like a natural extension of your work.
Please give it a try if you can and let me know what you think of it, and if you think it's worthy of the "related libraries" section of the readme.
Not sure whether this in scope of the library, but for example io-ts
, joi
, yup
all etc. can sanitize values (i.e. coerce them within certain boundaries) which is actually useful in some use cases. It would be nice if runtypes could do that too. joi
and yup
all do sanitization for some of their built in types and in io-ts
one can do sanitization in the validator function of custom types by passing the sanitized value to t.success(sanitizedValue)
.
I was actually looking at all the above mentioned libraries incl. runtypes for the use case of API validation. While I really wanted to use io-ts or runtypes (I use typescript everywhere) but I decided against the use of runtypes because of the lack of sanitization and it wasn't clear to me how to create custom types, and against io-ts because of the lack of an ability to create validation specific error messages.
Hi there,
this library looks amazing and seems like what i am currently missing in native typescript.
But there is one thing, i hadn't success to achieve with this library: nested recursive types.
I need to define the structure types for an object literal like:
type item = {
Prop1: string,
Prop2: number
}
type group = {
Prop3: string,
Items: Array<item | group>
}
const JSON: group = {
Prop3: "Root",
Items: [
{
Prop1: "Item 1",
Prop2: 123
},
{
Prop3: "Nested Group 1",
Items: [
/*
... and so on
*/
]
}
]
}
If i try to achieve this with runtypes the following way:
const item = Record({
Prop1: String,
Prop2: Number
});
const group = Record({
Prop3: String,
Items: Array(Union(item, group))
});
it gives a compiler error "block-scoped variable 'group' is used before its declaration".
If i replace const group with var group the compiler error disappears but typescript recognizes group only as type any..
Is there any way to describe the desired structure?
Thanks for any hint on this...
I am trying to convert an existing codebase to use runtypes.
However I am struggling to work out how to represent the arguments
key here with a typescript any
type:
export type Description = {
name: string;
type: 'fanout' | 'topic' | 'direct';
durable?: boolean;
internal?: boolean;
autoDelete?: boolean;
alternateExchange?: string;
arguments?: any;
};
So far I have this:
Record({
name: String,
type: Union(Literal('fanout'), Literal('topic'), Literal('direct'))
}).And(
Partial({
alternateExchange: String,
autoDelete: Boolean,
durable: Boolean,
internal: Boolean
//arguments: Any // Any is not exported from runtypes
})
)
Runtime type checking works:
Description.check({
name:'foo',
type: 'fanout',
arguments: {
ding:'pop'
}
}); // no errors
However the following causes type errors in editor:
const myDescription: Static<typeof Description> ={
name:'foo',
type: 'fanout',
arguments: { // shows error
ding:'pop'
}
}
The error is
Object literal may only specify known properties, and 'arguments' does not exist in type...
How do I infer this property should be any
?
It seems that #51 has regressed in later versions.
In 2.0.5 I get error messages like
Expected restEndpoint to be string
but in 2.1.6 they are just
Expected string, but was undefined
Reproduce with
const Options = rt.Record({
restEndpoint: rt.String,
userId: rt.String,
});
Options.check({userId: ""});
I have the following TypeScript code
import * as admin from "firebase-admin";
export enum Parser {
STANDARD = "standard",
FIVE_FILTERS = "fiveFilters",
}
export enum Status {
DRAFT = "draft",
PUBLISHED = "published",
}
export interface IWriter {
feedName: string;
writerRef: admin.firestore.DocumentReference;
}
export interface IEntryDocument {
cronExpression: string;
forceTeams: boolean;
forceWriters: boolean;
id: string;
name: string;
parser: Parser;
status: Status;
url: string;
writers: IWriter[];
}
I need to validate the data that comes from the DB so I need to perform validation based on the IEntryDocument
interface typings.
I started on converting these interfaces into runtypes format but I'm stuck with enums and external libraries' types.
This is where I reached so far:
import * as admin from "firebase-admin";
import {Boolean, String, Static, Array, Record} from "runtypes";
export enum Parser {
STANDARD = "standard",
FIVE_FILTERS = "fiveFilters",
}
export enum Status {
DRAFT = "draft",
PUBLISHED = "published",
}
const Writer = Record({
feedName: String,
// writerRef: admin.firestore.DocumentReference,
});
export interface IWriter {
feedName: string;
writerRef: admin.firestore.DocumentReference;
}
const EntryDocument = Record({
cronExpression: String,
forceTeams: Boolean,
forceWriters: Boolean,
id: String,
name: String,
// parser: Parser,
// status: Status,
url: String,
writers: Array(Writer),
});
export interface IWriter extends Static<typeof Writer> {}
export interface IEntryDocument extends Static<typeof EntryDocument> {}
Have a look at the lines that are commented out. I'm stuck with 2 issues:
admin.firestore.DocumentReference
Parser
and Status
enumsIs there any runtypes equivalent of following type definition? I don't want to specify key strings but check if the type of their values is string.
type Json = { [key: string]: string };
Hi,
I tried the following:
const OptionalString = String.Or(Undefined);
type OptionalString = Static<typeof OptionalString>;
const IOrganization = Record({
id: NonEmptyString,
name: NonEmptyString,
description: OptionalString,
link: OptionalString,
});
type IOrganization = Static<typeof IOrganization>;
if I call
const o: IOrganization = {
id: "a",
name: "the name",
};
IOrganization.check(o);
The check
function throws an error! There is a way to avoid the throwing if an optional field is missing in the argument object?
Suggestion : support for optional properties on Record
Use case : OpenAPI schemas and Typescript support optional properties, thus such support in runtypes would help in handling request bodies on Web api calls. Same for JSON data coming from no-SQL databases, where all fields are not always present.
I have a type like the following specified:
const ConfigType = rt.Record({})
.And(rt.Partial({
x: rt.String,
}));
Unfortunately running the following code succeeds without error:
ConfigType.check([])
Would it be possible for validate/check
to return all errors instead of just the first?
This would be useful for validating user input, giving the user all errors instead of just one.
I have the following code:
import { Boolean, Number, String, Literal, Array, Tuple, Record, Union } from 'runtypes';
const Asteroid = Record({
type: Literal('asteroid'),
location: Vector,
mass: Number,
});
Is there any easy way to create an object of type Asteroid
? Ideally, I'd like to use something like:
Asteroid.create({type: 'asteroid', location: [], mass: 1}); //valid
Asteroid.create({}); // error - the args supplied don't meet the contraints
Is this possible? I noticed that none of the examples is creating objects at all.
Sometimes I'm expecting an instance of a particular class. Is there a way to make a constraint field based on instanceof check or other typeguard?
I want to express something like this ...
class PopulationDistribution { ... }
const Planet = Record({
type: Literal('planet'),
location: Vector,
mass: Number,
population: InstanceOf(PopulationDistribution),
habitable: Boolean
})
Love this library. We're thinking about using it to type and validate our api responses. One problem we've encountered though is that for very large response objects validation isn't fast enough (can take up to a few seconds in some cases). A possible solution we've come up with is using web workers to perform this validation since it isn't essential we know right away if the response json is invalid. Problem is getting the runtype into the web worker. If we could serialize/deserialize the runtype we'd be able to pass it to the worker. It seems like it'd be pretty trivial to serialize/deserialize a runtype provided it's representing json. Thoughts on this? Would serialization of runtypes be a feature you'd be willing to include in this library?
Using this package for 3 months already in production projects, who would've thought it can be such a game changer, thank you!
Hi,
at first i didn't know, how to declare nullable properties in a Record and if it is possible at all.
The only way i found so far is:
const A = Record({
T: String,
N: Number
}).And(Partial({
Z: Boolean
}));
which results in a type:
type AType = {
T: string;
N: number;
} & {
Z?: boolean;
}
Wouldn't it be more easy and intuitive if one could do:
const A = Record({
T: String,
N: Number,
Z: Nullable(Boolean) // or Boolean.AsNullable()
/*
or even better as this is the normal typescript syntax?:
Z?: Boolean
*/
});
Something like that shouldn't be that hard, or do i miss something?
Thanks and regards..
There's a fairly common pattern for using union types called tagged unions. Using the current Union
runtype for these is functionally complete, however when a .check()
fails, it's usually near impossible to work out why. I'd like to propose a new TaggedUnion
runtype. You could use it as follows:
const AB = TaggedUnion("tag", {
a: Record({ aprop: String }),
b: Record({ bprop: Number }),
});
AB.check({ tag: "a", aprop: "foo" }); // success.
AB.check({ tag: "b", bprop: 10 }); // success.
AB.check({ tag: "c", cprop: "bar" }); // Error: Unknown tag: Expected "a" | "b", but was "c"
AB.check({ tag: "a", bprop: 10 }); // Error: Expected { tag: "a"; aprop: string; }, but was object.
AB.checkTag({ tag: "a" }); // true
AB.check({ tag: "a" }); // Error: Expected { tag: "a"; aprop: string; }, but was object.
AB.checkTag({ tag: "c", cprop: "bar" }); // false
Having a seperate checkTag
is often useful for deserialising input from changing upstream providers such as webhooks where they may add new event types which you want to discard or log differently because you typically care less about them, but you do care if you get an event you thought you knew about which has a structure other than you were expecting.
As an example of why I feel this is necessary, the current approach leaves me with errors like the following while trying to deserialise Mailgun webhooks (if there is an error):
Error: Expected (((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { envelope: { sender?: string; sending-ip?: string; targets?: string; transport?: string; }; flags: { is-authenticated?: boolean; is-routed?: boolean; is-system-test?: boolean; is-test-mode?: boolean; }; message: { attachments: {}[]; size: number; }; }) & { delivery-status: { attempt-no: number; code: number; description: string; message: string; session-seconds: number; } & { certificate-verified?: boolean; mx-host?: string; retry-seconds?: number; tls?: boolean; utf8?: boolean; }; storage: { key: string; url: string; }; }) & { event: "delivered"; }) | ((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?:string; }; } & { recipient-domain?: string; }) & { client-info: { client-name: string; client-os: string; client-type: string; device-type: string; user-agent: string; }; geolocation: { city: string; country: string; region: string; }; ip: string; }) & { event: "opened"; }) | ((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { client-info: { client-name: string; client-os: string; client-type: string; device-type: string; user-agent: string; }; geolocation: { city: string; country: string; region: string; }; ip: string; }) & { event: "clicked"; url: string; }) | ((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { envelope: { sender?: string; sending-ip?: string; targets?: string; transport?: string; }; flags: { is-authenticated?: boolean; is-routed?: boolean; is-system-test?: boolean; is-test-mode?: boolean; }; message: { attachments: {}[]; size: number; }; }) & { event: "complained"; }) | ((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { client-info: { client-name: string; client-os: string; client-type: string; device-type: string; user-agent: string; }; geolocation: { city: string; country: string; region: string; }; ip: string; }) & { event: "unsubscribed"; }) | (((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { envelope: { sender?: string; sending-ip?: string; targets?: string; transport?: string; }; flags: { is-authenticated?: boolean; is-routed?: boolean; is-system-test?: boolean; is-test-mode?: boolean; }; message: { attachments: {}[]; size: number; }; }) & { delivery-status: { attempt-no: number; code: number; description: string; message: string; session-seconds: number; } & { certificate-verified?: boolean; mx-host?: string; retry-seconds?: number; tls?: boolean; utf8?: boolean; }; storage: { key: string; url: string; }; }) & { event: "failed"; reason: string; severity: "temporary"; }) | (((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { envelope: { sender?: string; sending-ip?: string; targets?: string; transport?: string; }; flags: { is-authenticated?: boolean; is-routed?: boolean; is-system-test?: boolean; is-test-mode?: boolean; }; message: { attachments: {}[]; size: number; }; }) & { delivery-status: { attempt-no: number; code: number; description: string; message: string; session-seconds: number; } & { certificate-verified?: boolean; mx-host?: string; retry-seconds?: number; tls?: boolean; utf8?: boolean; }; storage: { key: string; url: string; }; }) & { event: "failed"; reason: string; severity: "permanent"; }), but was object
TypeScript
interface Foo {
[s: string]: number;
}
Is there any way to declare a runtype for the same? The following does't work. I assume there is a way that I don't know.
const Foo = Record({
[s: string]: Number,
})
It would be pretty nice if runtypes supported the ability to make a tagged Union from a runtype Union by adding an _type field not present in the original data with appropriate value ... It would be useful when validating weird/legacy json structures that you don't control ...
Something like
const Asteroid = Record({
location: Vector,
mass: Number,
})
const Planet = Record({
location: Vector,
mass: Number,
population: Number,
habitable: Boolean,
})
const SpaceObject = TaggedUnion(Asteroid, Planet)
const asteroid = { ...}
const planet = { ... }
const asteroidOrPlanet = SpaceObject.check(asteroid)
console.log(asteroidOrPlanet._type) // "Asteroid"
console.log(SpaceObject.check(planet))._type // "Planet"
Can you add checker for Symbol type?
I am curious if that's possible somehow? You see, I am using library mobx-state-tree which is also checked at runtime, but currently, it's impossible to use Typescript types for modeling.
type State = 'Unknown' | 'Online' | 'Offline'
const Model = types.model({
connState: types.enumeration(['Unknown', 'Online', 'Offline'])
})
The first step should be probably to support Symbol.iterator and then I can do this.
const State = Union(
Literal('Unknown'),
Literal('Online'),
Literal('Offline')
)
const Model = types.model({
connState: types.enumeration(Array.from(State))
})
However, that's not enough since it would give an array of Literal objects. I am not how to proceed there to keep the ease of use. Perhaps some custom method like State.toArray()
could be more feasible?
Hi Tom,
How would you model {[k: string]: any}
, if possible ?
I tried
RT.Record({
[RT.String as any as string]: RT.String
})
It typechecks correctly to RT.RunType<[x: string]: string;>
, but runtime checks does not work as expected.
First off thanks for a really useful library. I've already used it very successfully on a small project.
However, now I've hit a problem with typing a Date object. I'm probably missing something fundamental here.
With the following:
import { Number, Record, InstanceOf, Static } from 'runtypes'
const Coords = Record({
latitude: Number,
longitude: Number,
time: InstanceOf(Date)
})
type Coords = Static<typeof Coords>
let data: Coords = {
latitude: 1.0,
longitude: 1.0,
time: new Date(2017, 0, 1, 10, 0, 0)
}
I get the type error:
...
Types of property 'time' are incompatible.
Type 'Date' is not assignable to type 'DateConstructor'.
What am I missing?
if this is not the correct way to type a Date object, what is?
Hi,
When I open record.ts
in Visual Studio Code with TypeScript 2.8.1, the following error is shown:
Current definition:
export interface Record<O extends { [_ in never]: Runtype }>
extends Runtype<{ [K in keyof O]: Static<O[K]> }> {
tag: 'record';
fields: O;
}
Isn't the following more appropriate? If not, I would appreciate if you explain me why! I don't understand the current constraint for the generic type.
export interface Record<O extends { [_: string]: Runtype }> // [_: string] instead of [_ in never]
extends Runtype<{ [K in keyof O]: Static<O[K]> }> {
tag: 'record';
fields: O;
}
Thank you in advance.
The check
method of a runtype with constraint returns a value of the type without constraint, so sometimes I need to use branded types like this:
const A = String.withConstraint(x => x === 'a');
type A = Static<typeof A> & { __brandA: any };
const x: string = 'a';
const a: A = A.check(x); // Type 'string' is not assignable to type 'A'.
const a: A = A.check(x) as A; // OK but looks verbose
Then maybe it's good that check
or some another (defined anew?) method can return some user defined type, for example:
let A = String.withConstraint(x => x === 'a');
type A = Static<typeof A> & { __brandA: any };
A = Brand<A>(A);
const x: string = 'a';
const a: A = A.verify(x); // Should be OK
I think this is still ugly but absolutely better than doing as A
whenever A.check(x)
.
How do you think?
I see https://github.com/pelotom/runtypes/blob/master/src/index.ts#L611-L636
So it looks like there's a limit to the arg list. Is there no other way?
Hi, Just reaching out to get some thoughts on potentially generating JSONschema from runtypes type representations, and if such a library exists to achieve this.
Just for some background, we currently have a project where we have several dozen json endpoints and for each endpoint, we have a TRequest
and TResponse
TypeScript type as well as a corresponding JSONSchema type (for both request, response). As you can imagine, this is quite the duplication, and we feel this duplication could potentially be removed with the use of runtypes
.
One of our considerations however is we do like the idea of publishing TRequest
/ TResponse
schema information to our clients (similar to WSDL). In fact our preference is to publish JSONSchema as a standard JSON schematic (analogous to XSD, WSDL) but to do so, we would need to map runtypes to JSONSchema somehow.
So wondering if there is functionality to do something similar to the following....
const Asteroid = Record({
type: Literal('asteroid'),
location: Vector,
mass: Number,
});
// runtypes representation
// Asteroid = { tag: 'record',
// fields:
// { type:
// { tag: 'literal',
// value: 'asteroid',
// check: [Function],
const AsteroidSchema = Asteroid.toJsonSchema()
// json schema representation
// AsteroidSchema = {
// type: 'object',
// properties: {
// type: {type: 'string'},
// location: {
// x: { type: 'number'},
// y: { type: 'number'},
// ...
From this, we would be able to integrate runtypes with our existing json schema validators (we are currently using the ajv
json schema validator configured for draft 6).
Any information you can provide would be great.
Kind Regards
Sinclair
Very minor nitpick, but coerce means forcing something to happen, or to change. Does runtime's coerce
method do anything apart from checking that an object is already conforming to the type?
(Otherwise, very interesting library!)
Considering
const Errors = RT.Dictionary(RT.Array(RT.String))
type ErrorsType = RT.Static<typeof Errors>
0.7.0
correct
type ErrorsType = {
[_: string]: string[];
}
0.8.0
incorrect
type ErrorsType = {
[_: string]: Arr<String>;
}
Do you know intuitively what is causing this or would you like me to investigate further on?
Not urgent, I can stick on 0.7.0 for a while, it works perfectly for me.
Thanks again.
Noticed that with the latest version v2.1.6 the error message format has been changed (https://github.com/pelotom/runtypes/pull/61/files#diff-92c6b06e4d1db78667de9db352d5cc7bL30) and doesn't contain key
in the error message. It would be still really useful to have the key name also in error message.
It might be convenient if the match(...) method on union types were able to support functions which return different types ...
Maybe it be possible to implement .match() in such a way as to support a signature like this ...?
export interface Match2<A extends Rt, B extends Rt> {
<Z1, Z2>(a: Case<A, Z1>, b: Case<B, Z2>): Matcher2<A, B, Z1, Z2>;
Instead of:
export interface Match2<A extends Rt, B extends Rt> {
<Z>(a: Case<A, Z>, b: Case<B, Z>): Matcher2<A, B, Z>;
I took a stab at it with below PR ... Kind of ugly but seems to work? Is this a better type for .match() maybe? Without it the constraints on the functions provided to .match(...) are very tight ...
Straw-man example of what doesn't work without this change ...:
const Type1 = rt.Record({ status: rt.Literal('failure'), msg: rt.String });
const Type2 = rt.Record({
status: rt.Literal('success'),
username: rt.String,
authenticationState: rt.String,
});
const Response = rt.Union(Type1, Type2);
const extracted = Response.match(
failure => {
return { status: failure.status, authenticationState: 'not_logged_in', data: { ...failure } };
},
success => {
return {
status: success.status,
authenticationState: success.authenticationState,
data: { ...success },
};
},
);
Hi! Kudos for sharing this library. I'm doing some validation on incoming data from http API, and this made it so much easier, compared to implementing types, and then also validators.
I also found that another project io-ts seem to do very similar thing. I wonder if there are any fundamental difference to the taken approach?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.