Skip to content

Purity, hope, and the strength of Krypton in one package

License

Notifications You must be signed in to change notification settings

aminnairi/kryptonian

Repository files navigation

kryptonian

Purity, hope, and the strength of Krypton in one package

npm Coveralls branch npm type definitions npm bundle size NPM

Summary

Features

TypeScript

Benefit from robust TypeScript support with intelligent code completion, type inference, and error checking. The library provides a seamless development experience by leveraging TypeScript's static typing capabilities, catching potential issues during development rather than at runtime.

Back to summary

Functional Programming

Adopt a functional programming paradigm within your validation logic, promoting immutability and avoiding mutations. This approach ensures that the state of your data remains predictable and maintainable, contributing to a more reliable codebase.

Back to summary

Treeshakeable

Experience optimized bundles through the library's tree-shaking capabilities. This feature allows you to eliminate unused code during the build process, resulting in smaller production bundles and improved application performance.

Back to summary

Extensible

Enjoy a comprehensive set of TypeScript types that enhance the overall developer experience. The library provides rich type definitions, ensuring maximum code quality and facilitating a smooth integration process within TypeScript projects.

Back to summary

Custom Error Messages

Provide custom error messages for rules and schemas, enhancing user-facing error feedback. Tailor error messages to better communicate validation issues, improving the overall user experience.

Back to summary

Client & Server-Side Support

Seamlessly integrate the validation library into both client and server-side projects. Whether you're building a web application or a server-side API, the library offers universal compatibility, empowering you to maintain consistent validation logic across different environments.

Back to summary

Zero Dependencies

Minimize project dependencies with the library's commitment to a lightweight footprint. By avoiding external dependencies, you can maintain a streamlined project structure and reduce potential compatibility issues, contributing to a more efficient development process.

Back to summary

Inspired by Zod

Draw inspiration from Zod's design principles, incorporating best practices for validation. The library takes cues from Zod to provide a well-designed and reliable validation solution that aligns with industry standards.

Back to summary

Open-Source

Participate in the open-source community by contributing to the project through bug reports, feature requests, or pull requests. The library encourages collaboration and welcomes input from developers worldwide, fostering a community-driven approach to continuous improvement.

Back to summary

Installation

npm install kryptonian

Back to summary

Usage

import * as Kryptonian from "kryptonian";

export const routes = Kryptonian.Jorel.createRoutes({
  getKryptonians: {
    request: Kryptonian.Kalel.empty({
      message: "Request should be void or undefined"
    }),
    response: Kryptonian.Kalel.array({
      message: "Response should be an array",
      rules: [],
      schema: Kryptonian.Kalel.object({
        message: "Response should be an object",
        fields: {
          createdAt: Kryptonian.Kalel.date({
            message: "Response object should have a property createdAt that is a date",
            rules: []
          }),
          name: Kryptonian.Kalel.string({
            message: "Response object should have a property name that is a string",
            rules: []
          })
        }
      })
    })
  }
});
import * as Kryptonian from "kryptonian"
import * as Http from "http";
import { routes } from "@template/shared";
import { createExpressServer } from "./adapters/createExpressServer";

const router = Kryptonian.Jorel.createServerRouter({
  routes,
  implementations: {
    getKryptonians: async () => {
      return [
        {
          name: "Kalel",
          createdAt: new Date()
        },
        {
          name: "Jorel",
          createdAt: new Date()
        },
        {
          name: "Zorel",
          createdAt: new Date()
        }
      ];
    }
  }
});

const server = createExpressServer({
  router,
  clients: ["http://localhost:8000"]
});

server.listen(8000, "0.0.0.0", () => {
  console.log("Server launched and ready for communications");
});
import * as Kryptonian from "kryptonian";
import { routes } from "@template/shared";

const client = Kryptonian.Jorel.createClientRoutes({
  server: "http://localhost:8000",
  routes
});

client.getKryptonians({
  parameters: null,
  options: {}
}).then(kryptonians => {
  console.log(kryptonians);
}).catch(error => {
  console.error(error);
})

Back to summary

API

createProtector

Create a protection function helping you validate data according to the schema passed as argument. Unless you type check that the success property is true or false, you do not get access to the data property. This prevents unintentional access when there might be an error, protecting your from making mistakes in your source-code.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.string({
  message: "This is not a string",
  rules: []
}));

const data: unknown = "Hello, world!";
const protection = protect(data);

if (protection.success) {
  console.log(protection.data);
} else {
  console.log(protection.errors);
}
"Hello, world!"

Back to summary

literal

literal is a function that will create a LiteralSchema<Value> that can validate values that are exactly equal to the value that you'll pass. Beware, literal values works well for scalar data types that can be compared with each others like string or boolean, but not so well for array and object since they are compared by references.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.literal({
  message: "This should be true",
  value: true as const
}));

const goodData: unknown = true;
const badData: unknown = false;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(badData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
true
[
  {
    "path": "",
    "message": "This should be true"
  }
]

Beware, if you use this in a server, you should use the as const keyword in order to create literal values instead of plain values.

import * as Kryptonian from "kryptonian";
import { routes } from "./routes";

export const router = Kryptonian.Jorel.createServerRouter({
  getKryptonians: async () => {
    return [
      {
        success: true as const, // Instead of just "true"
        name: "Kalel"
      }
    ];
  }
});

Back to summary

boolean

Boolean is a schema representing a value that can either be true or false.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.boolean({
  message: "This is not a boolean"
}));

const goodData: unknown = true;
const badData: unknown = [];

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(badData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
true
[
  {
    "path": "",
    "message": "This is not a boolean"
  }
]

Back to summary

none

None is a schema representing a value that can be null.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.none({
  message: "This is not null"
}));

const goodData: unknown = null;
const badData: unknown = undefined;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(badData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
null
[
  {
    "path": "",
    "message": "This is not null"
  }
]

Back to summary

notDefined

NotDefined is a schema representing a value that can be undefined.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.notDefined({
  message: "This is not undefined"
}));

const goodData: unknown = undefined;
const badData: unknown = null;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(badData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"undefined"
[
  {
    "path": "",
    "message": "This is not undefined"
  }
]

Back to summary

string

string is a schema representing a string.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.string({
  message: "This is not a string",
  rules: []
}));

const goodData: unknown = "Hello, world!";
const badData: unknown = 123;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(badData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"Hello, world!"
[
  {
    "path": "",
    "message": "This is not a string"
  }
]

Back to summary

length

Validate that a string has exactly a given length.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.string({
  message: "This is not a string",
  rules: [
    Kryptonian.Kalel.String.length({
      length: 13,
      message: "This should be a string of 10 characters"
    })
  ]
}));

const goodData: unknown = "Hello, world!";
const wrongData: unknown = "Hello";

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"Hello, world!"
[
  {
    "path": "",
    "message": "This should be a string of 10 characters"
  }
]

Back to summary

minimumLength

Validate that a string has a minimum length.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.string({
  message: "This is not an array",
  rules: [
    Kryptonian.Kalel.String.minimumLength({
      minimum: 10,
      message: "This should be a string of at least 10 characters"
    })
  ]
}));

const goodData: unknown = "Hello, world!";
const wrongData: unknown = "Hello";

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"Hello, world!"
[
  {
    "path": "",
    "message": "This should be a string of at least 10 characters"
  }
]

Back to summary

includes

Validate that a string is included in another one.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.string({
  message: "This is not an array",
  rules: [
    Kryptonian.Kalel.String.includes({
      string: "type",
      message: "This should be a string with the word type"
    })
  ]
}));

const goodData: unknown = "typescript";
const wrongData: unknown = "javascript";

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"typescript"
[
  {
    "path": "",
    "message": "This should be a string with the word type"
  }
]

Back to summary

startsWith

Validate that a string is starting with another one.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.string({
  message: "This is not an array",
  rules: [
    Kryptonian.Kalel.String.startsWith({
      string: "type",
      message: "This should be a string starting with the word type"
    })
  ]
}));

const goodData: unknown = "typescript";
const wrongData: unknown = "javascript";

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"typescript"
[
  {
    "path": "",
    "message": "This should be a string starting with the word type"
  }
]

Back to summary

endsWith

Validate that a string is ending with another one.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.string({
  message: "This is not an array",
  rules: [
    Kryptonian.Kalel.String.endsWith({
      string: "script",
      message: "This should be a string ending with the word script"
    })
  ]
}));

const goodData: unknown = "typescript";
const wrongData: unknown = "typeform";

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"typescript"
[
  {
    "path": "",
    "message": "This should be a string ending with the word script"
  }
]

Back to summary

email

Validate that a string is a valid email.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.string({
  message: "This is not a string",
  rules: [
    Kryptonian.Kalel.String.email({
      message: "This should be a valid email"
    })
  ]
}));

const goodData: unknown = "kalel@krypton.io";
const wrongData: unknown = "kalel@krypton";

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"kalel@krypton.io"
[
  {
    "path": "",
    "message": "This should be a valid email"
  }
]

Back to summary

uniformResourceLocator

Validate that a string is a valid url.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.string({
  message: "This is not a string",
  rules: [
    Kryptonian.Kalel.String.uniformResourceLocator({
      message: "This should be a valid URL"
    })
  ]
}));

const goodData: unknown = "https://krypton.dev";
const wrongData: unknown = "https:/krypton.dev";

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"https://krypton.dev"
[
  {
    "path": "",
    "message": "This should be a valid URL"
  }
]

Back to summary

internetProtocolVersion4

Validate that a string is a valid Internet Protocol version 4 format.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.string({
  message: "This is not an array",
  rules: [
    Kryptonian.Kalel.String.internetProtocolVersion4({
      message: "This should be a valid IPv4 address"
    })
  ]
}));

const goodData: unknown = "1.2.3.4";
const wrongData: unknown = "1.2.3";

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"1.2.3.4"
[
  {
    "path": "",
    "message": "This should be a valid IPv4 address"
  }
]

Back to summary

internetProtocolVersion4WithClasslessInterDomainRouting

Validate that a string is a valid Internet Protocol version 4 with classless inter-domain routing format.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.string({
  message: "This is not an array",
  rules: [
    Kryptonian.Kalel.String.internetProtocolVersion4WithClasslessInterDomainRouting({
      message: "This should be a valid IPv4 address with CIDR"
    })
  ]
}));

const goodData: unknown = "1.2.3.4/16";
const wrongData: unknown = "1.2.3/32";

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"1.2.3.4/16"
[
  {
    "path": "",
    "message": "This should be a valid IPv4 address with CIDR"
  }
]

Back to summary

number

number is a schema representing a number.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: []
}));

const goodData: unknown = 123;
const wrongData: unknown = "Hello, world!";

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
123
[
  {
    "path": "",
    "message": "This is not a number"
  }
]

Back to summary

between

Validate that a number is between two values.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.between({
      minimum: 10,
      maximum: 20,
      message: "This should be a number between 10 & 20"
    })
  ]
}));

const goodData: unknown = 15;
const wrongData: unknown = 172;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
15
[
  {
    "path": "",
    "message": "This should be a number between 10 & 20"
  }
]

Back to summary

divisibleBy

Validate that a number can be divided by another number without remaining value.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.divisibleBy({
      divisor: 5,
      message: "This should be a number divisible by 5"
    })
  ]
}));

const goodData: unknown = 15;
const wrongData: unknown = 172;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
15
[
  {
    "path": "",
    "message": "This should be a number divisible by 5"
  }
]

Back to summary

notDivisibleBy

Validate that a number cannot be divided by another number without remaining value.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.notDivisibleBy({
      divisor: 2,
      message: "This should be a number not divisible by 2"
    })
  ]
}));

const goodData: unknown = 15;
const wrongData: unknown = 172;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
15
[
  {
    "path": "",
    "message": "This should be a number not divisible by 2"
  }
]

Back to summary

even

Validate that a number is even.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.even({
      message: "This should be an even number"
    })
  ]
}));

const goodData: unknown = 14;
const wrongData: unknown = 173;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
14
[
  {
    "path": "",
    "message": "This should be an even number"
  }
]

Back to summary

odd

Validate that a number is odd.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.odd({
      message: "This should be an odd number"
    })
  ]
}));

const goodData: unknown = 15;
const wrongData: unknown = 172;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
15
[
  {
    "path": "",
    "message": "This should be an odd number"
  }
]

Back to summary

positive

Validate that a number is positive.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.positive({
      message: "This should be a positive number"
    })
  ]
}));

const goodData: unknown = 15;
const wrongData: unknown = -172;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
15
[
  {
    "path": "",
    "message": "This should be a positive number"
  }
]

Back to summary

negative

Validate that a number is negative.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.negative({
      message: "This should be a negative number"
    })
  ]
}));

const goodData: unknown = -15;
const wrongData: unknown = 172;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
-15
[
  {
    "path": "",
    "message": "This should be a negative number"
  }
]

Back to summary

integer

Validate that a number is integer

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.integer({
      message: "This should be an integer number"
    })
  ]
}));

const goodData: unknown = 15;
const wrongData: unknown = 17.2;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
15
[
  {
    "path": "",
    "message": "This should be an integer number"
  }
]

Back to summary

greater

Validate that a number is greater than another value.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.greater({
      number: 5,
      message: "This should be greater than 5"
    })
  ]
}));

const goodData: unknown = 15;
const wrongData: unknown = 2;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
15
[
  {
    "path": "",
    "message": "This should be greater than 5"
  }
]

Back to summary

lower

Validate that a number is lower than another value.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.lower({
      number: 5,
      message: "This should be lower than 5"
    })
  ]
}));

const goodData: unknown = 2;
const wrongData: unknown = 15;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
2
[
  {
    "path": "",
    "message": "This should be lower than 5"
  }
]

Back to summary

greaterOrEqual

Validate that a number is greater or equal to another value.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.greaterOrEqual({
      number: 5,
      message: "This should be greater or equal to 5"
    })
  ]
}));

const goodData: unknown = 5;
const wrongData: unknown = 2;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
5
[
  {
    "path": "",
    "message": "This should be greater or equal to 5"
  }
]

Back to summary

lowerOrEqual

Validate that a number is greater or equal to another value.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.lowerOrEqual({
      number: 5,
      message: "This should be lower or equal to 5"
    })
  ]
}));

const goodData: unknown = 5;
const wrongData: unknown = 7;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
5
[
  {
    "path": "",
    "message": "This should be lower or equal to 5"
  }
]

Back to summary

finite

Validate that a number is finite.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.number({
  message: "This is not a number",
  rules: [
    Kryptonian.Kalel.Number.finite({
      message: "This should be finite"
    })
  ]
}));

const goodData: unknown = 5;
const wrongData: unknown = Infinity;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
5
[
  {
    "path": "",
    "message": "This should be finite"
  }
]

Back to summary

date

Date is a schema representing a date object.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.date({
  message: "This should be a date",
  rules: []
}));

const goodData: unknown = new Date(2023, 1, 2, 3, 4, 5);
const alsoGoodData: unknown = "2023-11-19T13:32:34.479Z";
const badData: unknown = NaN;

const protectionGoneRight = protect(goodData);
const protectionGoneRightAgain = protect(alsoGoodData);
const protectionGoneWrong = protect(badData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneRightAgain.success) {
  console.log(protectionGoneRightAgain.data);
} else {
  console.log(protectionGoneRightAgain.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"Thu Feb 02 2023 03:04:05 GMT+0100"
"Sun Nov 19 2023 14:32:34 GMT+0100"
[
  {
    "path": "",
    "message": "This should be a date"
  }
]

Back to summary

between

Validate that a date is between two dates.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.date({
  message: "This should be a date",
  rules: [
    Kryptonian.Kalel.Date.between({
      minimum: new Date(2021, 0, 1, 0, 0, 0),
      maximum: new Date(2024, 0, 1, 1, 1, 1),
      message: "This should be a date between 01/01/2021 & 01/01/2024"
    })
  ]
}));

const goodData: unknown = new Date(2023, 0, 1, 0, 0, 0);
const badData: unknown = new Date(2025, 0, 1, 0, 0, 0);
const alsoBadData: unknown = new Date(2020, 0, 1, 0, 0, 0);

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(badData);
const protectionGoneWrongAgain = protect(alsoBadData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}

if (protectionGoneWrongAgain.success) {
  console.log(protectionGoneWrongAgain.data);
} else {
  console.log(protectionGoneWrongAgain.errors);
}
"Sun Jan 01 2023 00:00:00 GMT+0100"
[
  {
    "path": "",
    "message": "This should be a date between 01/01/2021 & 01/01/2024"
  }
]
[
  {
    "path": "",
    "message": "This should be a date between 01/01/2021 & 01/01/2024"
  }
]

Back to summary

before

Validate that a date is before a given date.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.date({
  message: "This should be a date",
  rules: [
    Kryptonian.Kalel.Date.before({
      date: new Date(2025, 0, 1, 0, 0, 0),
      message: "This should be a date before 01/01/2025"
    })
  ]
}));

const goodData: unknown = new Date(2023, 0, 1, 0, 0, 0);
const badData: unknown = new Date(2028, 0, 1, 0, 0, 0);

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(badData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"Sun Jan 01 2023 00:00:00 GMT+0100"
[
  {
    "path": "",
    "message": "This should be a date before 01/01/2025"
  }
]

Back to summary

after

Validate that a date is after a given date.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.date({
  message: "This should be a date",
  rules: [
    Kryptonian.Kalel.Date.after({
      date: new Date(2021, 0, 1, 0, 0, 0),
      message: "This should be a date after 01/01/2021"
    })
  ]
}));

const goodData: unknown = new Date(2023, 0, 1, 0, 0, 0);
const badData: unknown = new Date(2020, 0, 1, 0, 0, 0);

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(badData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
"Sun Jan 01 2023 00:00:00 GMT+0100"
[
  {
    "path": "",
    "message": "This should be a date after 01/01/2021"
  }
]

Back to summary

object

object is a schema representing an object.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.object({
  message: "This is not an object",
  fields: {
    email: Kryptonian.Kalel.string({
      message: "This is not a string",
      rules: []
    })
  }
}));

const goodData: unknown = {
  email: "kalel@krypton.io"
};

const wrongData: unknown = "Hello, world!";

const anotherWrongData: unknown = {
  email: 123
}

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);
const protectionGoneWrongAgain = protect(anotherWrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}

if (protectionGoneWrongAgain.success) {
  console.log(protectionGoneWrongAgain.data);
} else {
  console.log(protectionGoneWrongAgain.errors);
}
{
  "email": "kalel@krypton.io"
}
[
  {
    "path": "",
    "message": "This is not an object"
  }
]
[
  {
    "path": ".email",
    "message": "This is not a string"
  }
]

Back to summary

array

array is a schema representing an array

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.array({
  message: "This is not an array",
  rules: [],
  schema: Kryptonian.Kalel.string({
    message: "This is not a string",
    rules: []
  })
}));

const goodData: unknown = [ "Hello", "world!" ];

const wrongData: unknown = "Hello, world!";

const anotherWrongData: unknown = [ "Hello", 123 ];

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);
const protectionGoneWrongAgain = protect(anotherWrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}

if (protectionGoneWrongAgain.success) {
  console.log(protectionGoneWrongAgain.data);
} else {
  console.log(protectionGoneWrongAgain.errors);
}
[ "Hello", "world!" ]
[
  {
    "path": "",
    "message": "This is not an array"
  }
]
[
  {
    "path": "[1]",
    "message": "This is not a string"
  }
]

Back to summary

length

Validate the length of a array.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.array({
  message: "This is not an array",
  rules: [
    Kryptonian.Kalel.Array.length({
      length: 3,
      message: "This should be an array of 3 elements"
    })
  ],
  schema: Kryptonian.Kalel.string({
    message: "This is not a string",
    rules: []
  })
}));

const goodData: unknown = [ "Hello", "world", "!" ];

const wrongData: unknown = "Hello, world!";

const anotherWrongData: unknown = [ "Hello", "world!" ];

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);
const protectionGoneWrongAgain = protect(anotherWrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}

if (protectionGoneWrongAgain.success) {
  console.log(protectionGoneWrongAgain.data);
} else {
  console.log(protectionGoneWrongAgain.errors);
}
[ "Hello", "world", "!" ]
[
  {
    "path": "",
    "message": "This is not an array"
  }
]
[
  {
    "path": "",
    "message": "This should be an array of 3 elements"
  }
]

Back to summary

lengthBetween

Validate that the length of a array is between a range.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.array({
  message: "This is not an array",
  rules: [
    Kryptonian.Kalel.Array.lengthBetween({
      minimum: 2,
      maximum: 3,
      message: "This should be an array of 2 to 3 elements"
    })
  ],
  schema: Kryptonian.Kalel.string({
    message: "This is not a string",
    rules: []
  })
}));

const goodData: unknown = [ "Hello", "world", "!" ];

const wrongData: unknown = [ "Hello" ];

const anotherWrongData: unknown = [ "Well", "hello", "world", "!" ];

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);
const protectionGoneWrongAgain = protect(anotherWrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}

if (protectionGoneWrongAgain.success) {
  console.log(protectionGoneWrongAgain.data);
} else {
  console.log(protectionGoneWrongAgain.errors);
}
[ "Hello", "world", "!" ]
[
  {
    "path": "",
    "message": "This should be an array of 2 to 3 elements"
  }
]
[
  {
    "path": "",
    "message": "This should be an array of 2 to 3 elements"
  }
]

Back to summary

minimumLength

Validate that the length of a array is above a value.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.array({
  message: "This is not an array",
  rules: [
    Kryptonian.Kalel.Array.minimumLength({
      minimum: 2,
      message: "This should be an array of at least 2 elements"
    })
  ],
  schema: Kryptonian.Kalel.string({
    message: "This is not a string",
    rules: []
  })
}));

const goodData: unknown = [ "Hello", "world" ];

const wrongData: unknown = [ "Hello" ];

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
[ "Hello", "world" ]
[
  {
    "path": "",
    "message": "This should be an array of at least 2 elements"
  }
]

Back to summary

maximumLength

Validate that the length of a array is above a value.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.array({
  message: "This is not an array",
  rules: [
    Kryptonian.Kalel.Array.maximumLength({
      maximum: 2,
      message: "This should be an array of at most 2 elements"
    })
  ],
  schema: Kryptonian.Kalel.string({
    message: "This is not a string",
    rules: []
  })
}));

const goodData: unknown = [ "Hello", "world" ];

const wrongData: unknown = [ "Hello", "world", "!" ];

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
[ "Hello", "world" ]
[
  {
    "path": "",
    "message": "This should be an array of at most 2 elements"
  }
]

Back to summary

nonEmpty

Validate that the length of a array is above 0.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.array({
  message: "This is not an array",
  rules: [
    Kryptonian.Kalel.Array.nonEmpty({
      message: "This should be an array of at least 1 element"
    })
  ],
  schema: Kryptonian.Kalel.string({
    message: "This is not a string",
    rules: []
  })
}));

const goodData: unknown = [ "Hello", "world" ];

const wrongData: unknown = [];

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
[ "Hello", "world" ]
[
  {
    "path": "",
    "message": "This should be an array of at least 1 element"
  }
]

Back to summary

document

document is a schema that represents a Kryptonian.Jorel.Document that you can use to validate files. This is tailored to work well with Jorel, our client/server architecture solution.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.document({
  message: "This should be a Document"
}));

const goodData: unknown = Kryptonian.Jorel.Document.fromFile(new File([], "good.txt", {
  type: "text/plain"
}));

const badData: unknown = new File([], "bad.txt");

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(wrongData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
{
  "bytes": "",
  "name": "good.txt",
  "mimeType": "text/plain"
}
[
  {
    "path": "",
    "message": "This should be a Document"
  }
]

Back to summary

unknown

Unknown is a schema representing a TypeScript unknown value.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.unknown());

const goodData: unknown = "Hello, world!";
const alsoGoodData: unknown = 42;

const protectionGoneRight = protect(goodData);
const alsoGoodProtection = protect(alsoGoodData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (alsoGoodProtection.success) {
  console.log(alsoGoodProtection.data);
} else {
  console.log(alsoGoodProtection.errors);
}
"Hello, world!"
42

Back to summary

any

Unknown is a schema representing a TypeScript any value.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.any());

const goodData: unknown = "Hello, world!";
const alsoGoodData: unknown = 42;

const protectionGoneRight = protect(goodData);
const alsoGoodProtection = protect(alsoGoodData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (alsoGoodProtection.success) {
  console.log(alsoGoodProtection.data);
} else {
  console.log(alsoGoodProtection.errors);
}
"Hello, world!"
42

Back to summary

empty

empty is a schema representing the void value in TypeScript. Any value that is undefined is allowed in this schema. Also, if validating function arguments, void represent the absence of value, so you can also omit the value in this schema.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.empty({
  message: "This should be empty (void or undefined)"
}));

const goodData: unknown = undefined;
const badData: unknown = null;

const protectionGoneRight = protect(goodData);
const protectionGoneWrong = protect(badData);

if (protectionGoneRight.success) {
  console.log(protectionGoneRight.data);
} else {
  console.log(protectionGoneRight.errors);
}

if (protectionGoneWrong.success) {
  console.log(protectionGoneWrong.data);
} else {
  console.log(protectionGoneWrong.errors);
}
undefined
[
  {
    "path": "",
    "message": "This should be empty (void or undefined)"
  }
]

Back to summary

oneOf

oneOf is a function that returns a OneOfSchema<Schema> that helps you validate a union of multiple things, very useful to return multiple types at once and have the client validate them.

For instance, you may want to return multiple business errors without the fear of changing anything and desynchronizing your client application and your server application. Returning business errors this way is a very powerful way of discriminating errors and preventing logic errors.

import * as Kryptonian from "kryptonian";

const protect = Kryptonian.Kalel.createProtector(Kryptonian.Kalel.oneOf([
  Kryptonian.Kalel.object({
    message: "This should be an object",
    fields: {
      success: Kryptonian.Kalel.literal({
        value: true as const,
        message: "This should be true"
      }),
      message: Kryptonian.Kalel.string({
        message: "This should be a string",
        rules: []
      })
    }
  }),
  Kryptonian.Kalel.object({
    message: "This should be an object",
    fields: {
      success: Kryptonian.Kalel.literal({
        value: false as const,
        message: "This should be false"
      }),
      error: Kryptonian.Kalel.string({
        message: "This should be a string",
        rules: []
      })
    }
  })
]));

const data: unknown = {
  success: true,
  message: "Successfully added the user in database"
};

const anotherData: unknown = {
  success: false,
  error: "Username is already taken"
}

const protection = protect(data);
const anotherProtection = protect(anotherData);

if (protection.success) {
  if (protection.data.success) {
    console.log(protection.data.message);
  } else {
    protection.data.error;
  }
} else {
  console.log(protection.errors);
}

if (anotherProtection.success) {
  if (anotherProtection.data.success) {
    console.log(anotherProtection.data.message);
  } else {
    console.log(anotherProtection.data.error)
  }
} else {
  console.log(anotherProtection.errors);
}
"Successfully added the user in database"
"Username is already taken"

Back to summary

Jorel

Jorel is the name of the client/server technology that is inherent to the Kryptonian library. With it, you can define a server and a client that sends data to each other in a pure, functional and safe way.

Back to summary

createRoutes

createRoutes is a function that will help you define the shape of your server using a validation schema.

import * as Kryptonian from "kryptonian";

const routes = Kryptonian.Kalel.Jorel.createRoutes({
  createKryptonian: {
    request: Kryptonian.Kalel.object({
      message: "This should be a object",
      fields: {
        name: Kryptonian.Kalel.string({
          message: "Name is not a string",
          rules: []
        })
      }
    }),
    response: Kryptonian.Kalel.string({
      rules: [],
      message: "Expected a string as the response"
    })
  },
  getKryptonians: {
    request: Kryptonian.Kalel.none({
      message: "Expected nothing except null"
    }),
    response: Kryptonian.Kalel.array({
      message: "Response is not a array",
      rules: [],
      schema: Kryptonian.Kalel.string({
        message: "Response is not a array of string",
        rules: []
      })
    })
  }
});

Back to summary

createRoute

When your application grows in complexity and number of routes, you can use the createRoute function to create routes in their own files, allowing you to scale your application easily. It also helps reducing the amount of lines of file could have if you declared all of your routes inside the createRoutes function.

import * as Kryptonian from "kryptonian";

export const createKryptonian = Kryptonian.Jorel.createRoute({
  request: Kryptonian.Kalel.object({
    message: "This should be a object",
    fields: {
      name: Kryptonian.Kalel.string({
        message: "Name is not a string",
        rules: []
      })
    }
  }),
  response: Kryptonian.Kalel.string({
    rules: [],
    message: "Expected a string as the response"
  })
});

Back to summary

createServerRouter

createServerRouter is a function that will take as input your server, and will let your define an implementation for the latter. Once it has been created, it must be fed to an adapter that can turn an abstract response into a concrete HTTP response.

import * as Kryptonian from "kryptonian";
import { routes } from "@template/shared";
import { getKryptonians } from "./routes/getKryptonians";
import { createKryptonian } from "./routes/createKryptonian";
import { createHttpServer } from "./adapters/createHttpServer";

const router = Kryptonian.Jorel.createServerRouter({
  routes,
  implementations: {
    getKryptonians,
    createKryptonian
  }
});

const server = createHttpServer({
  router,
  clients: ["http://localhost:5173"]
});

server.listen(8000, "0.0.0.0", () => {
  console.log("Server launched and ready for communications");
});

Adapters are function that take a router (just like the createHttpServer function above) and will receive each requests from the client applications. Whenever a request has been made, the adapter transform the request into an abstraction that can be understood by the router, and will get back an abstract response that can be turned into a concrete HTTP response.

You can of course create your own adapter to support your favorite HTTP library. Here is an example of an adapter for the built-in http module.

import * as Kryptonian from "kryptonian";
import * as Http from "http";
import * as Path from "path";

/**
 * Options used to create the HTTP server's router adapter
 */
export type CreateHttpServerOptions = {
  /**
   * The router created using the Kryptonian.Jorel.createServerRouter function
   */
  router: Kryptonian.Jorel.Router,
  /**
   * A list of clients that must be allowed to request the server when in a browser
   */
  clients: Array<string>
}

/**
 * Create an adapter for the Router using the Node.js built-in HTTP module
 */
export const createHttpServer = ({ clients, router }: CreateHttpServerOptions) => {
  const getJsonBody = (request: Http.IncomingMessage) => {
    return new Promise<JSON>((resolve, reject) => {
      let body = "";

      request.on("data", chunk => {
        body += chunk;
      });

      request.on("end", () => {
        try {
          const parsedBody = JSON.parse(body);
          resolve(parsedBody);
        } catch (error) {
          resolve(undefined);
        }
      });

      request.on("error", (error) => {
        reject(new Error(String(error)));
      });
    });
  };

  return Http.createServer(async (request, response) => {
    const url = new URL(Path.join("http://localhost/", request.url ?? ""));
    const origin = request.headers.origin ?? "";
    const path = url.pathname;
    const method = request.method ?? "GET";
    const foundClient = clients.find(client => origin === client) ?? "";

    const baseHeaders = {
      "Content-Type": "application/json",
      "Access-Control-Allow-Headers": "Content-Type",
      "Access-Control-Allow-Methods": "POST,OPTIONS",
      "Access-Control-Allow-Origin": foundClient
    };

    try {
      const body = await getJsonBody(request);

      const routerResponse = await router({
        body,
        origin,
        method,
        path
      });

      const routerResponseHeadersWithBaseHeaders = {
        ...routerResponse.headers,
        ...baseHeaders
      };

      response
        .writeHead(routerResponse.status, routerResponseHeadersWithBaseHeaders)
        .end(JSON.stringify(routerResponse.body));
    } catch (error) {
      response
        .writeHead(500, baseHeaders)
        .end(JSON.stringify({
          success: false,
          errors: [
            {
              path: "",
              message: String(error)
            }
          ]
        }));
    }
  });
};

You can find examples of adapters in the template/server/apdaters folder.

Back to summary

createServerRoute

createServerRoute is a functon that will help you break down the routes implementations into smaller route that you can easily export.

import * as Kryptonian from "kryptonian";

const routes = Kryptonian.Jorel.createRoutes({
  getUsers: {
    request: Kryptonian.Kalel.object({
      message: "Request should be an object",
      fields: {
        since: Kryptonian.Kalel.date({
          message: "Request should contain a property since of type Date",
          rules: []
        })
      }
    }),
    response: Kryptonian.Kalel.array({
      message: "Response should be an array",
      rules: [],
      schema: Kryptonian.Kalel.string({
        message: "Response should be an array of strings",
        rules: []
      })
    })
  }
});
import * as Kryptonian from "kryptonian";
import { routes } from "./routes";

export const getUsers = Kryptonian.Jorel.createServerRoute({
  routes,
  route: "getUsers",
  response: async ({ since }) => {
    return [
      "Kalel",
      "Zorel",
      "Jorel"
    ]
  }
});
import * as Kryptonian from "kryptonian";
import * as Http from "http";
import { routes } from "./routes";
import { getUsers } from "./routes/getUsers";

const router = Kryptonian.Jorel.createServerRouter({
  routes,
  clients: [
    "http://localhost:5173"
  ],
  implementations: {
    getUsers
  }
});

const server = Http.createServer(router);

server.listen(8000, "0.0.0.0", () => {
  console.log("Server launched and ready for communications!");
});

createClientRoutes

createClientRoutes is a function that will help you request informations from the server. It implements the Fetch Web API, and we intend on adding support for more HTTP libraries such as Axios for instance. Each time you request something, it will pick-up the validation schema and forces you to use this schema for all your request, preventing mistakes even if you decide to update the schema. When receiving the body, data validation is also applied, so that you can't mess up manipulating data that is not validated yet.

import * as React from "react";
import * as Kryptonian from "kryptonian";
import { routes } from "./routes";

const client = Kryptonian.Jorel.createClientRoutes({
  server: "http://localhost:8000",
  routes
});

export const Component = () => {
  const [kryptonian, setKryptonian] = useState("");
  const [kryptonians, setKryptonians] = React.useState<Array<string>>([]);

  const updateKryptonian: React.ChangeEventHandler<HTMLInputElement> = React.useCallback(event => {
    setKryptonian(event.target.value);
  }, []);

  const getKryptonians = React.useCallback(() => {
    client.getKryptonians({
      parameters: null,
      options: {}
    }).then(kryptonian => {
      setKryptonians(kryptonian);
    }).catch(() => {
      if (error instanceof Kryptonian.Jorel.BadRequestError) {
        console.log(error.errors);

        return alert("Bad request, please check your form");
      } 

      if (error instanceof Kryptonian.Jorel.BadResponseError) {
        console.log(error.errors);

        return alert("Bad response from the server, please try again later.");
      }

      alert("Unknown error, sorry for the inconvenience!");
    });
  }, []);

  const createKryptonian: React.FormEventHandler = React.useCallback(event => {
    event.preventDefault();

    client.createKryptonian({
      parameters: {
        name: kryptonian
      },
      options: {}
    }).then(() => {
      alert("Kryptonian saved!");
    }).catch(error => {
      alert("An error occurred");
    });
  }, [kryptonian]);

  return (
    <React.Fragment>
      <form onSubmit={createKryptonian}>
        <input
          type="text"
          value={kryptonian}
          onChange={updateKryptonian} />
        <button type="submit">
          Save
        </button>
      </form>
      <ul>
        {kryptonians.map(kryptonian => (
          <li>
            {kryptonian}
          </li>
        ))}
      </ul>
    </React.Fragment>
  );
};

That's it, in a few lines, you just created your own server/client application using React for this example. Note that it works with absolutely any JavaScript framework, and even Vanilla TypeScript since it has no external dependencies. You could of course do the same in Vue.js for instance.

<script lang="ts" setup>
import * as Vue from "vue";
import * as Kryptonian from "kryptonian";
import { routes } from "./routes";

const client = Kryptonian.Jorel.createClientRoutes({
  server: "http://localhost:8000",
  routes
});

const kryptonian = Vue.ref("");
const kryptonians = Vue.ref<Array<string>>([]);

const updateKryptonian = (event: EventTarget) => {
  kryptonian.value = event.target.value;
};

const getKryptonians = () => {
  client.getKryptonians({
    parameters: null,
    options: {}
  }).then(kryptonian => {
    setKryptonians(kryptonian);
  }).catch(error => {
    if (error instanceof Kryptonian.Jorel.BadRequestError) {
      console.log(error.errors);

      return alert("Bad request, please check your form");
    } 

    if (error instanceof Kryptonian.Jorel.BadResponseError) {
      console.log(error.errors);

      return alert("Bad response from the server, please try again later.");
    }

    alert("Unknown error, sorry for the inconvenience!");
  });
};

const createKryptonian = (event: FormEvent) => {
  event.preventDefault();

  client.createKryptonian({
    parameters: {
      name: kryptonian.value
    },
    options: {}
  }).then(() => {
    alert("Kryptonian saved!");
  }).catch(error => {
    alert("An error occurred");
  });
}
</script>

<template>
  <form>
    <input v-model="kryptonian">
  </form>
  <ul>
    <li v-for="kryptonian in kryptonians">
      {{ kryptonian }}
    </li>
  </ul>
</template>

Back to summary

Document

Kryptonian.Jorel.Document is a special class that brings a lot more capabilities than a regular File since it can be serialized and sent easily along with your other regular data. This makes it trivial to send files from a client and implement your business logic around your data on the server.

import * as Kryptonian from "kryptonian";

const routes = Kryptonian.Jorel.createRoutes({
  sendKryptonianFile: {
    request: Kryptonian.Kalel.object({
      message: "Request should be an object",
      fields: {
        file: Kryptonian.Kalel.document({
          message: "file should be a file",
        }),
        message: Kryptonian.Kalel.string({
          message: "message should be a string",
          rules: []
        })
      }
    }),
    response: Kryptonian.Kalel.none({
      message: ""
    })
  }
});
import { routes } from "@template/shared";
import * as Kryptonian from "kryptonian";
import * as FileSystem from "fs/promises";
import * as Path from "path";
import * as Crypto from "crypto";

export const sendKryptonianFile = Kryptonian.Jorel.createServerRoute({
  routes,
  route: "sendKryptonianFile",
  response: async ({ file, message }) => {
    const extension = Path.extname(file.name);
    const uploadFolderPath = "uploads/files";
    const fileName = `${uploadFolderPath}/${Crypto.randomUUID()}${extension}`;

    await FileSystem.mkdir(uploadFolderPath, { recursive: true });
    await FileSystem.writeFile(fileName, file.toBuffer());

    console.log({ message: `Message from the client: ${message}` });

    return null;
  }
});
import * as React from "react";
import * as Kryptonian from "kryptonian";
import { routes } from "@template/shared";

const client = Kryptonian.Jorel.createClientRoutes({
  server: "http://localhost:8000",
  routes
});

export const App = () => {
  const [file, setFile] = React.useState(new File([], ""));
  const [message, setMessage] = React.useState("");

  const updateFile: React.ChangeEventHandler<HTMLInputElement> = React.useCallback(event => {
    if (event.target.files && event.target.files[0] instanceof File) {
      setFile(event.target.files[0]);
    }
  }, []);

  const updateMessage: React.ChangeEventHandler<HTMLInputElement> = React.useCallback(event => {
    setMessage(event.target.value);
  }, []);

  const sendKryptonianFile: React.FormEventHandler = React.useCallback(event => {
    event.preventDefault();

    Kryptonian.Jorel.Document.fromFile(file).then(documentFile => {
      return client.sendKryptonianFile({
        parameters: {
          file: documentFile,
          message
        },
        options: {}
      }).then(response => {
        console.log("Success!");
      }).catch(error => {
        console.error(error);
      });
    }).catch(error => {
      console.error(error);
    });
  }, [file, message]);

  return (
    <form onSubmit={sendKryptonianFile}>
      <label htmlFor="message">
        Message about this file
      </label>
      <input
        id="message"
        type="text"
        value={message}
        onChange={updateMessage} />
      <input
        type="file"
        onChange={updateFile} />
      <button type="submit">
        Send
      </button>
    </form>
  );
};

Important note: this works by converting the file into its base64 representation. This has been tested locally with files over 10mo and a total overhead of 50ms so this would be just fine for images for instances. For larger files, and if you are working on computation-based platform like AWS or GCP, you should probably be better off using a plain Express route to handle files separately for instance.

If you decide to use this solution, you'll need to increase the body size limit of your web server's implementation. For instance, if you are using the createExpressAdapter for your server.

  ...
  // This limit right there ----------------------------------+
  //                                                          |
  //                                                          |
  //                                                          |
  //                                                          v
  server.post("*", bodyParser.json({ strict: false, limit: "100mb" }), async (request, response) => {
    const url = new URL(Path.join("http://localhost:8000", request.url));
    const origin = request.headers.origin ?? "";
    const method = "POST";
    const path = url.pathname;
    const body = request.body;
    ...

Also note that if your Node.js application is behind a reverse-proxy server (NGINX, Apache, etc...), you will probably need to increase the body size limit for your proxy as well.

Back to summary

getting started

You can start writing client & server applications right away by using the template folder.

Here is how you can get this template to get started developing locally.

npx degit aminnairi/kryptonian/template#production my-project
cd my-project
# read the README.md!

Where production is the branch to use. We recommend starting from the production branch since it is the most stable branch for using this template, but you can use any branches from the GitHub repository as well as tags.

Here is an example using the 2.0.0 release tag for demonstration purposes.

npx degit aminnairi/kryptonian/template#2.0.0 my-project
cd my-project
# read the README.md!

Be sure to install the package for all workspaces inside the template using the following command before starting the servers.

Here again, using the 2.0.0 release tag for demonstration purposes.

npm --workspaces install kryptonian@2.0.0

Back to summary

InferType

InferType helps you get the underlying TypeScript type of a schema.

import * as Kryptonian from "kryptonian";

const schema = Kryptonian.Kalel.array({
  message: "This should be an array",
  rules: [],
  schema: Kryptonian.Kalel.string({
    message: "This should be an array of strings",
    rules: []
  })
});

type Schema = Kryptonian.InferType<typeof schema>;
// string[]

const anotherSchema = Kryptonian.Kalel.object({
  message: "This should be an object",
  fields: {
    email: Kryptonian.Kalel.string({
      message: "Field email should be a string"
    }),
    administrator: Kryptonian.Kalel.boolean({
      message: "Field administrator should be a boolean"
    })
  }
});

type AnotherSchema = Kryptonian.Kalel.InferType<typeof anotherSchema>;
// { email: string, administrator: boolean }

Back to summary

Custom rules

A rule is a function that is applied in most functions exposed by this library. Every time you see a function taking a rules properties as its argument, it means that it is accepting an array of rules. In fact, creating custom rules would be exactly the same as the rules defined by this library. Here is for instance the source-code for the Kyrptonian.Kalel.Array.length rule.

export interface LengthOptions {
  length: number,
  message: string
}

export const length = ({ length, message }: LengthOptions): ArrayRule => {
  return {
    message,
    valid: value => value.length === length
  };
};

And here is another example featuring the Kryptonian.Kalel.String.minimumLength rule.

export interface MinimumLengthOptions {
  minimum: number,
  message: string
}

export const minimumLength = ({ minimum, message }: MinimumLengthOptions): StringRule => {
  return {
    message,
    valid: value => value.length >= minimum
  }
}

As you probably guessed, a rule is a function. And those functions must return a rule. There as several rules that you can return and for each type that is exposed (Kryptonian.Kalel.Array, Kryptonian.Kalel.String, ...) there is an associated rule that you can import.

Here is a non-exhaustive list of rules, and many more to come in a near future.

import * as Kryptonian from "kryptonian";

Kryptonian.Kalel.StringRule; // For strings
Kryptonian.Kalel.NumberRule; // For numbers
Kryptonian.Kalel.ArrayRule; // For arrays
Kryptonian.Kalel.DateRule; // For dates

Every rule is a pure function, meaning it should take an argument (you can also choose not to accept any argument such as the Kryptonian.Kalel.String.email rule) and must return a rule. There is no mutation nor effect that is going on in a rule, bringing guarantees and robustness to the library itself. In fact, every function exposed (except for Jorel.createClientRoutes and Jorel.createServerRouter) is a pure function and will not imply side-effects of any kind.

Here is an example of a rule that you might want to create to valide that a user's age is in legal compliance with your business domain.

import * Kryptonian from "kryptonian";

export interface ValidAgeOptions {
  message: string
}

export const validAge = ({ message }: ValidAgeOptions): Kryptonian.Kalel.NumberRule => {
  return {
    message,
    valid: age => age >= 18 && age <= 60
  }
};

As you can see, here we can validate that the age of a user is between 18 and 60 (those are abritrary values, of course this would be different from an application to another).

One important thing to note here is that you don't have to type yourself the value of the age variable in this case, the NumberRule is here to ensure that it is always a number type.

What would happen if you use the wrong rule? Let's find out.

import * as Kryptonian from "kryptonian";

export interface ValidAgeOptions {
  message: string
}

export const validAge = ({ message }: ValidAgeOptions): Kryptonian.Kalel.StringRule => {
  return {
    message,
    valid: age => age >= 18 && age <= 60
    // Operator '>=' cannot be applied to types 'string' and 'number'.ts(2365)
    // Operator '<=' cannot be applied to types 'string' and 'number'.ts(2365)
  }
};

We added a comment to help you understand this code without having to test it yourself (but you are encouraged to do so!). As you can see, there is no way we can make a mistake by comparing a string with a number here since now that we replaced Kryptonian.Kalel.NumberRule with Kryptonian.Kalel.StringRule, the age value is typed as a string, not a number. Hence the error we got in the comment below the comparison.

That's it! There is nothing more to know about custom rules and it is very trivial and easy to create its own.

More validation are yet to be brought with each and every future releases of this library, and if you don't find your use-case in those, you can probably be off creating your own custom rule in no time!

Back to summary

Issues

See issues.

Back to summary

Changelog

See CHANGELOG.md.

Back to summary

Code of conduct

See CODE_OF_CONDUCT.md.

Back to summary

Contributing

See CONTRIBUTING.md.

Back to summary

License

See LICENSE.

Back to summary

Security

See SECURITY.md.

Back to summary