Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
name: field("name", string),
// $ExpectError
aye: field("age", number),
// ^^^^^^^^^^^^^^^^^^^^^^
// Type '{ name: string; aye: number; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'aye' does not exist in type 'Person'. ts(2322)
})
);
greet(personDecoder4(testPerson));
/*
* EXTRA PROPERTY
*/
// TypeScript allows passing extra properties, so without type annotations there are no errors:
const personDecoder5 = record(field => ({
name: field("name", string),
age: field("age", number),
extra: field("extra", string),
}));
const personDecoder5Auto = autoRecord({
name: string,
age: number,
extra: string,
});
greet(personDecoder5(testPerson));
greet(personDecoder5Auto(testPerson));
// Adding `Decoder` does not seem to help TypeScript find any errors:
const personDecoder6: Decoder = record(field => ({
name: field("name", string),
age: field("age", number),
active: true,
country: undefined,
// $ExpectError
type: "nope",
// ^^^^^^^^^
// Type '"nope"' is not assignable to type '"user"'. ts(2322)
};
/*
* MAKING A TYPE FROM THE DECODER – CAVEATS
*/
// Let’s say we need to support two types of users – anonymous and registered ones.
// Unfortunately, TypeScript doesn’t infer the type you might have expected:
// $ExpectType Decoder<{ type: string; sessionId: number; id?: undefined; name?: undefined; } | { type: string; id: number; name: string; sessionId?: undefined; }>
const userDecoder3 = record((field, fieldError) => {
const type = field("type", string);
switch (type) {
case "anonymous":
return {
type: "anonymous",
sessionId: field("sessionId", number),
};
case "registered":
return {
type: "registered",
id: field("id", number),
name: field("name", string),
};
extra: field("extra", string),
}));
const personDecoder7Auto = autoRecord({
name: string,
age: number,
// $ExpectError
extra: string,
// ^^^^^^^^^^
// Argument of type '{ name: (value: unknown) => string; age: (value: unknown) => number; extra: (value: unknown) => string; }' is not assignable to parameter of type '{ name: Decoder; age: Decoder; }'.
// Object literal may only specify known properties, and 'extra' does not exist in type '{ name: Decoder; age: Decoder; }'. ts(2345)
});
greet(personDecoder7(testPerson));
greet(personDecoder7Auto(testPerson));
// Luckily, the last type annotation style for `record` does produce an error!
const personDecoder8 = record(
(field): Person => ({
name: field("name", string),
age: field("age", number),
// $ExpectError
extra: field("extra", string),
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// Type '{ name: string; age: number; extra: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'extra' does not exist in type 'Person'. ts(2322)
})
);
greet(personDecoder8(testPerson));
// See these TypeScript issues for more information:
// https://github.com/microsoft/TypeScript/issues/7547
// https://github.com/microsoft/TypeScript/issues/18020
/*
// }
//
// Those extra fields are just noisy. They exist because of this:
// https://github.com/microsoft/TypeScript/pull/19513
//
// But can we get rid of them?
}
// Turns out we can get rid of the extra properties by using the good old
// `identity` function! It’s just a function that returns whatever is passed to
// it. A bit of an ugly workaround, but it works.
const id = (x: T): T => x;
// And when wrapping each `return` in `id(...)` the extra properties vanish!
// $ExpectType Decoder<{ type: "anonymous"; sessionId: number; } | { type: "registered"; id: number; name: string; }>
const userDecoder5 = record((field, fieldError) => {
const type = field("type", string);
switch (type) {
case "anonymous":
return id({
type: "anonymous" as const,
sessionId: field("sessionId", number),
});
case "registered":
return id({
type: "registered" as const,
id: field("id", number),
name: field("name", string),
});
active: boolean,
id: either(string, number),
})
);
verifyUser(
record(field => ({
extra: field("extra", string),
extra2: field("extra2", () => undefined),
name: field("name", string),
age: field("age", number),
active: field("active", boolean),
id: field("id", either(string, number)),
}))
);
verifyUser(
record(field => ({
extra: field("extra", string),
extra2: field("extra2", () => undefined),
name: field("name", string),
age: field("age", number),
active: field("active", boolean),
id: field("id", either(string, number)),
}))
);
// Misspelled field ("naem" instead of "name"):
verifyUser(
// $ExpectError
autoRecord({
naem: string,
age: number,
active: boolean,
}));
// $ExpectError
const personDecoder2Auto: Decoder = autoRecord({
// ^^^^^^^^^^^^^^^^^^
// Type 'Decoder<{ name: string; aye: number; }>' is not assignable to type 'Decoder'.
// Property 'age' is missing in type '{ name: string; aye: number; }' but required in type 'Person'. ts(2322)
name: string,
aye: number,
});
greet(personDecoder2(testPerson));
greet(personDecoder2Auto(testPerson));
// Here’s a shorter way of writing the above – which also gives better error
// messages!
// $ExpectError
const personDecoder3 = record(field => ({
name: field("name", string),
aye: field("age", number),
}));
// ^
// Property 'age' is missing in type '{ name: string; aye: number; }' but required in type 'Person'.ts(2741)
const personDecoder3Auto = autoRecord({
name: string,
// $ExpectError
aye: number,
// ^^^^^^^^
// Argument of type '{ name: (value: unknown) => string; aye: (value: unknown) => number; }' is not assignable to parameter of type '{ name: Decoder; age: Decoder; }'.
// Object literal may only specify known properties, and 'aye' does not exist in type '{ name: Decoder; age: Decoder; }'. ts(2345)
});
greet(personDecoder3(testPerson));
greet(personDecoder3Auto(testPerson));
type: constant("registered"),
id: number,
name: string,
});
default:
throw new TypeError(`Unknown user type: ${repr(type)}`);
}
}
// This “decodes” the "type" field using `getUserDecoder`. Remember that a
// decoder is allowed to return whatever it wants. `getUserDecoder` returns a
// _new_ decoder, which we immediately call. I haven’t found a nicer way to do
// this so far.
// $ExpectType Decoder<{ type: "anonymous"; sessionId: number; } | { type: "registered"; id: number; name: string; }>
const userDecoder6 = record((field, _fieldError, obj, errors) =>
field("type", getUserDecoder)(obj, errors)
);
// Finally, there’s one last little detail to know about: How optional fields
// are inferred.
// $ExpectType Decoder<{ title: string; description: string | undefined; }>
const itemDecoder = autoRecord({
title: string,
description: optional(string),
});
// As you can see above, fields using the `optional` decoder are always inferred
// as `key: T | undefined`, and never as `key?: T`. This means that you always
// have to specify the optional fields:
type Item = ReturnType;
// $ExpectError
id: string | number;
}
const verifyUser = (decoder: (value: unknown) => User): User =>
decoder(undefined);
const userDecoder: (value: unknown) => User = autoRecord({
name: string,
age: number,
active: boolean,
id: either(string, number),
});
verifyUser(userDecoder);
const userDecoder2: (value: unknown) => User = record(field => ({
name: field("name", string),
age: field("age", number),
active: field("active", boolean),
id: field("id", either(string, number)),
}));
verifyUser(userDecoder2);
// `id: string` also satisfies `string | number`.
verifyUser(
autoRecord({
id: string,
name: string,
age: number,
active: boolean,
})
export function makeOptionsDecoder(defaults: Options): Decoder {
return record(field => ({
chars: field("chars", map(string, validateChars), {
default: defaults.chars,
}),
autoActivate: field("autoActivate", boolean, {
default: defaults.autoActivate,
}),
overTypingDuration: field("overTypingDuration", decodeUnsignedInt, {
default: defaults.overTypingDuration,
}),
css: field("css", string, {
default: defaults.css,
}),
logLevel: field("logLevel", map(string, decodeLogLevel), {
default: defaults.logLevel,
}),
useKeyTranslations: field("useKeyTranslations", boolean, {
import { type Box, decodeUnsignedFloat } from "../shared/main";
export type FrameMessage =
| {
type: "FindElements",
token: string,
types: ElementTypes,
viewports: Array,
}
| {
type: "UpdateElements",
token: string,
viewports: Array,
};
export const decodeFrameMessage: Decoder = record(
(field, fieldError) => {
const type = field("type", string);
switch (type) {
case "FindElements":
return {
type: "FindElements",
token: "",
types: field("types", decodeElementTypes),
viewports: field("viewports", decodeViewports),
};
case "UpdateElements":
return {
type: "UpdateElements",
token: "",