Gathering detailed insights and metrics for kryo-qs
Gathering detailed insights and metrics for kryo-qs
Gathering detailed insights and metrics for kryo-qs
Gathering detailed insights and metrics for kryo-qs
npm install kryo-qs
Typescript
Module System
Min. Node Version
TypeScript (99.99%)
JavaScript (0.01%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
280 Commits
1 Forks
2 Watchers
6 Branches
2 Contributors
Updated on Mar 14, 2025
Latest Version
0.16.0
Package Id
kryo-qs@0.16.0
Unpacked Size
35.52 kB
Size
7.43 kB
File Count
18
Published on
Mar 13, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
3
Kryo is a library to represent data types at runtime. For example, it lets you test if a value
matches a type or safely serialize data between various formats such as JSON
or BSON
.
Here are other examples of problems that Kryo was written to solve:
The public functions of your library check the validity of their arguments (arguments are not trusted). Having runtime types allows this verification to be explicit and easier to maintain.
Deserializing the JSON string '{createdAt: "2017-10-15T17:10:08.218Z"}'
requires type
information to convert the value to a Date
instance: Kryo simplifies it so you don't have to
manually handle type conversions.
The convention in Javascript/Typescript is to use camelCase
for property names but you consume
serialized data (like JSON
) with snake_case
and don't want to manually maintain name
rewrites.
You can install the latest stable version of Kryo with yarn (recommended) or npm:
1# Using yarn: 2yarn add kryo 3# Usin npm: 4npm install --save kryo
The package includes Typescript type defintion files (.d.ts
) and source maps.
You can install the preview of the next version. This version is continuously deployed from the
master
branch of the repo (it is the most up-to-date version).
yarn add kryo@next
npm install --save kryp@next
Note: The documentation is in the process of being rewritten. Some sections are drafts or outdated.
A Kryo Type represents a set of valid values. Kryo provides you multiple builtin types and utilities to easily create your own types.
It is simply an object with 3 methods to:
.test
).equals
).clone
)If you like fancy words, it defines a setoid.
There are more advanced types with additional methods and operators (for example to support
serialization) but any object implementing the Type
interface
is compatible with Kryo.
For example, the builtin type $Uint8
represents an unsigned integer in the inclusive range
[0, 255]
.
1import { $Uint8 } from "kryo/builtins/uint8"; 2 3$Uint8.test(15); // `true` 4$Uint8.test(Math.PI); // `false` 5$Uint8.test("Hello, World!"); // `false` 6$Uint8.equals(6, 6); // `true` 7$Uint8.clone(255); // `255`
Kryo provides type constructors for common types. They let you instanciate types using a configuration object.
For example you may want to define a $Percentage
type representing unsigned integers in the
inclusive range [0, 100]
. You can implement it from scratch, or use the IntegerType
type
constructor.
1import { IntegerType } from "kryo/types/integer"; 2 3const $Percentage = new IntegerType({min: 0, max: 100}); 4// You can also define `$Uint8` yourself 5const $Uint8 = new IntegerType({min: 0, max: 255}); 6 7$Percentage.test(50); // `true` 8$Percentage.test(101); // `false`
One of the most important type constructors is DocumentType
. It lets you describe the interface
of Javascript objects by composing types for each property.
1import { DocumentType } from "kryo/types/document"; 2import { Ucs2StringType } from "kryo/types/ucs2-string"; 3 4const $DisplayName = new Ucs2StringType({maxLength: 32}); 5 6/** 7 * Typescript interface describing our values: a user id 8 * an optional (can be undefined) display name. 9 */ 10interface User { 11 id: number; 12 displayName?: string; 13} 14 15const $PriceTag = new DocumentType<PriceTag>({ 16 properties: { 17 id: {type: $Uint8}, 18 displayName: {type: $DisplayName, optional: true}, 19 } 20});
One of the most powerful type constructors provided by Kryo is TaggedUnionType
. It allows to
combine multiple document types and differentiate them using the value of a "tag" property. This
property must describe Typescript-like enum: each document type is associated with a distinct
value of the enum.
This is the analogue of Typescript's discriminated unions.
For example, Github's repository owners can either be users or organizations. We can define it as follow:
1// 1. We create an enum used to differentiate the possible values. 2enum RepositoryOwnerType { 3 User = "user", 4 Organization = "organization", 5} 6const $RepositoryOwnerType = new TsEnumType({enum: RepositoryOwnerType}); 7 8// 2. We define the User document 9interface User { 10 type: RepositoryOwnerType.User; 11 id: number; 12 username: string; 13} 14const $User = new DocumentType({ 15 properties: { 16 type: {type: new LiteralType({itemType: $RepositoryOwnerType, value: RepositoryOwnerType.User})}, 17 id: {type: $Uint32}, 18 username: {type: $Username}, 19 } 20}); 21 22// 3. We define the Organization document 23interface Organization { 24 type: RepositoryOwnerType.Organization; 25 id: number; 26 members: any[]; 27} 28const $Organization = new DocumentType({ 29 properties: { 30 type: {type: new LiteralType({itemType: $RepositoryOwnerType, value: RepositoryOwnerType.Organization})}, 31 id: {type: $Uint32}, 32 members: {type: new ArrayType({itemType: $Any})}, 33 } 34}); 35 36// 4. We create the union type 37type RepositoryOwner = User | Organization; 38const $RepositoryOwner = new TaggedUnionType({variants: [User, Organization], tag: "type"});
You can define custom types, for example we can define a Prime
type describing prime numbers.
1// By convention, the names of type objects are prefixed by `$` 2const $Prime: Type<number> = { 3 testError(value: number): Error | undefined { 4 if (typeof value !== "number") { 5 // Notice the error is returned and not thrown: 6 // this is not a logic problem but an expected error. 7 return new Error("Expected `number`"); 8 } 9 if (value <= 1 || Math.floor(value) !== value) { 10 return new Error("Expected a positive integer strictly greater than 1"); 11 } 12 for (let divisor: number = 2; divisor <= Math.sqrt(value); divisor++) { 13 if (value % divisor === 0) { 14 return new Error(`Divisible by ${divisor}`); 15 } 16 } 17 return undefined; // No error 18 }, 19 test(value: number): boolean { 20 return this.testError(value) === undefined; 21 // You can either reuse `testError` as above or provide a new implementation without error messages: 22 // ```typescript 23 // if (typeof value !== "number" || value <= 1 || Math.floor(value) !== value) { 24 // return false; 25 // } 26 // for (let divisor: number = 2; divisor <= Math.sqrt(value); divisor++) { 27 // if (value % divisor === 0) { 28 // return false; 29 // } 30 // } 31 // return true; 32 // ``` 33 }, 34 equal(left: number, right: number): boolean { 35 return left === right; 36 }, 37 clone(value: number): number { 38 return value; 39 }, 40}
You can already use your type ($Prime.test(7)
for example) but Kryo helps you to create common
types with its type constructors and then compose these types to describe more advanced structures.
Suppose that we are building a database of prime numbers: each record is a number with its
discovery date. We can use the provided DocumentType
constructor to create our record type
from $Prime
and the builtin $Date
type:
1import { $Date } from "kryo/builtins/date"; 2import { DocumentType } from "kryo/types/document"; 3 4const $Record = new DocumentType({ 5 properties: { 6 discoveryDate: {type: $Date}, 7 value: {type: $Prime}, 8 } 9}); 10 11$Record.test({}); // `false` (missing required properties) 12$Record.test({discoveryDate: new Date(), value: 10}); // `false` (not a prime) 13$Record.test({discoveryDate: null, value: 10}); // `false` (invalid value for `discoveryDate`) 14$Record.test({discoveryDate: new Date(), value: 11}); // `true`
$Date
and instances of DocumentType
implement not only the Type
interface but also the
IoType
interface: they support serialization.
The IoType
interface has two additional methods:
1interface IoType { 2 write<W>(writer: Writer<W>, value: T): W; 3 read<R>(reader: Reader<R>, raw: R): T; 4}
The signature is more complex but it lets you write a single serialization and deserialization
method to handle most general-pupose formats: the format details are abstracted by the writer
and reader
objects.
1const $Prime: IoType<number> = { 2 testError(value: number): Error | undefined { ... }, 3 test(value: number): boolean { ... }, 4 equal(left: number, right: number): boolean { ... }, 5 clone(value: number): number { ... }, 6 write<W>(writer: Writer<W>, value: number): W { 7 return writer.writeFloat64(value); 8 }, 9 read<R>(reader: Reader<R>, raw: R): number { 10 return reader.readFloat64(raw, readVisitor({ 11 fromFloat64: (input: number): number => { 12 const error: Error | undefined = reader.trustInput ? undefined : this.testError(input); 13 if (error !== undefined) { throw error; } 14 return input; 15 }, 16 })); 17 }, 18}
The reader and writer objects use an intermediate model for types: rich enough to represent the types of most popular general-purpose languages but simpler than the runtime types.
TODO: Serialization needs way more explanations.
Now that all of our objects support serialization, we can use it to read to and from JSON despite
the Date
type (used by discoveryDate
but not supported by JSON):
1const reader: JsonReader = new JsonReader(); 2const writer: JsonReader = new JsonWriter(); 3 4const record = $Record.read(reader, '{"discoveryDate": "2017-10-15T17:10:08.218Z", value: 5}'); 5// Notice that the Date type is not supported by JSON but we properly deserialized it thanks to the 6// runtime description: 7console.log(record.discoveryDate instanceof Date); // `true` 8const record.value = 11; 9console.log($Record.write(writer, record)); // `'{"discoveryDate": "2017-10-15T17:10:08.218Z", value: 11}'`
THE DOCUMENTATION IS IN THE PROCESS OF BEING REWRITTEN, THIS SECTION IS OUTDATED
Kryo differentiates three level of types.
qs
library).You can import all the types from the main module, but it is recommended to import them them directly from their module (to allow better dead-code elimination):
1import { Ucs2StringType } from "kryo/types/ucs2-string"; 2import { DateType } from "kryo/types/date"; 3 4// Use the type constructor to create a specialized string 5const $Identifier: Ucs2StringType = new Ucs2StringType({minLength: 1, maxLength: Infinity, pattern: /[_a-zA-Z][_a-zA-Z0-9]/}); 6// Instantiate the date type, see next section to use a builtin instead of creating a new instance 7const $Date: DateType = new DateType(); 8 9// Both $identifier and $date are versioned type: they implement the methods of all the levels. 10 11// Use the methods provided by simple type 12$Identifier.test("abc"); // true 13$Identifier.test("$#!+"); // false 14$Date.testError(NaN); 15const now: Date = new Date(); 16const nowCopy = $Date.clone(now); 17console.log(now === nowCopy); // false: `clone` performs a deep copy on objects 18$Date.equals(now, nowCopy); // Tests the "equivalence" of the values: it's not the same object but they hold the same value 19 20// Use the methods provided by serializable types 21$Date.write("json", now); // Returns the ISO string "2017-10-15T17:10:08.218Z" 22$Date.read("json", "2017-10-15T17:10:08.218Z"); // Returns a `Date` instance 23$Identifier.read("json", "$#!+"); // Throws error: you can use Kryo to read untrusted input and validate it 24$Date.readTrusted("json", "2017-10-15T17:10:08.218Z"); // If you know what you are doing, you can only perform conversion and ignore validation 25 26// Use the methods provided by versionned types 27const diff1: number | undefined = $Date.diff(now, nowCopy); // undefined: both are equal 28const diff2: number | undefined = $Date.diff(new Date(10), new Date(30)); // 20: the diff of `DateType` is the difference in ms 29const diff3: number | undefined = $Date.diff(new Date(30), new Date(60)); // 30 30const diff4: [string, string] | undefined = $Identifier.diff("foo", "bar"); // ["foo", "bar"], strings return a simple [old, new] tuple 31$Date.patch(new Date(10), diff2); // applies the diff: returns a value equivalent to `new Date(30)` 32$Date.squash(diff2, diff3); // 50: merge the two diffs in a single diff representing the whole change 33$Date.reverseDiff(diff3); // -30: Diffs are symetric: you can always reverse them 34$Date.patch($Date.reverseDiff(diff3)); // 30: Apply the reverse diff to retrieve `30` from `60`
THE DOCUMENTATION IS IN THE PROCESS OF BEING REWRITTEN, THIS SECTION IS OUTDATED
Kryo provides both constructors to let you instantiate types by providing a minimal configuration but also builtins
for common types. By convention, the names of actual types start with $
while the names of type constructors end
with Type
.
Example: Kryo provides the class IntegerType
to produce types for integers in a given range. Kryo also provide
the built-in $Uint32
type: an instance of IntegerType
already configured for the common range of unsigned
32-bit integers ([0, 2**32 - 1]
).
1import { IntegerType } from "kryo/types/integer"; 2import { $Uint32 } from "kryo/builtins/uint32"; 3 4/** Represents the type of the outcome of throwing two D6 dices and summing their value */ 5const $twoDicesOutcome: Type<number> = new IntegerType({min: 2, max: 36}); 6$twoDicesOutcome.test(6); // true 7$twoDicesOutcome.test("three"); // false 8$twoDicesOutcome.test(NaN); // false 9$twoDicesOutcome.test(1); // false 10 11// You can directly use `$Uint32`, no need to configure / instantiate it: 12$Uint32.test(1); // true 13$Uint32.test(1.5); // false 14$Uint32.test(2 ** 32); // false 15$Uint32.test(2 ** 32 - 1); // true
THE DOCUMENTATION IS IN THE PROCESS OF BEING REWRITTEN, THIS SECTION IS OUTDATED
Kryo currently provide support for the following types:
null
true
or false
It is also fully compatible with your own type if you implement the correct interface.
Please check the documentation of each type to see that options are available and which builtins are provided out of the box.
Here is an example to define the type of a point in a RGBa color-space with 8-bit depth:
1import {$Uint8} from "kryo/integer"; 2
THE DOCUMENTATION IS IN THE PROCESS OF BEING REWRITTEN, THIS SECTION IS OUTDATED
Kryo supports circular type definitions, this allows to describe recursive values such as trees. Note that circular values are not supported (yet) by Kryo: you have to break and recreate the cycles in the values yourself.
Here is an example of values with a recursive structure:
1const v0 = {} 2const v1 = {foo: {}} 3const v2 = {foo: {bar: {}}} 4const v3 = {foo: {bar: {foo: {}}}} 5const v4 = {foo: {bar: {foo: {bar: {}}}}}
At each level, the value is a document with an optional key alternating between foo
and bar
.
The value is not circular, but we see that to describe it we need the types to be self-referential.
A simple (invalid) approach would be to define it as follow:
1// The document at each even level can have an optional key `foo` of type `$oddLevel` 2const $evenLevel = new DocumentType({ 3 properties: { 4 foo: {type: $oddLevel, optional: true}, 5 } 6}); 7 8// This is the corresponding type for the documents at an odd depth 9const $oddLevel = new DocumentType({ 10 properties: { 11 bar: {type: $evenLevel, optional: true}, 12 } 13}); 14 15$evenLevel.test(v4); // Error
The issue with the code above is that during the declaration of $evenLevel
, $oddLevel
is not yet defined so the
received properties object is {foo: {type: undefined, optional: true}}
.
To solve this problem, Kryo's types use a simple trick: you can pass the options lazily. Instead of providing
the options object directly, you can use a function returning this function object. This function will be called
only once, but not at the creation of the instance but at its first use.
1// Note that the options were wrapped in a lambda 2const $evenLevel = new DocumentType(() => ({ 3 properties: { 4 foo: {type: $oddLevel, optional: true}, 5 } 6})); 7 8const $oddLevel = new DocumentType(() => ({ 9 properties: { 10 bar: {type: $evenLevel, optional: true}, 11 } 12})); 13 14// At this point, both `$evenLevel` and `$oddLevel` is defined but none of the lambda were called 15// because the types were not used: there were no attribute read or method call. 16 17// When call a method for the first time, `$evenLevel` initializes itself by calling the lambda 18// When testing `v4`, `$oddLevel` will also be used so it will be initialized at this point. 19$evenLevel.test(v4); // Success: returns `true`
Type
methods are defined as static methods on the
corresponding class.No vulnerabilities found.
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
2 existing vulnerabilities detected
Details
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
Found 0/26 approved changesets -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
Last Scanned on 2025-07-07
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More