npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

skir

v0.0.5

Published

[![npm](https://img.shields.io/npm/v/skir)](https://www.npmjs.com/package/skir) [![build](https://github.com/gepheum/skir/workflows/Build/badge.svg)](https://github.com/gepheum/skir/actions)

Readme

npm build

Skir

Like Protocol Buffer, but better.

Skir is a language for representing data types, constants and RPC interfaces. Skir files have the .skir extension.

// shapes.skir

struct Point {
  x: int32;
  y: int32;
  label: string;
}

struct Polyline {
  points: [Point];
  label: string;
}

const TOP_RIGHT_CORNER: Point = {
  x = 600,
  y = 400,
  label = "top-right corner",
};

// Method of an RPC interface
method IsPalindrome(string): bool;

The Skir compiler takes in a set of .skir files and generates source files containg the definition of the data types and constants in various programming languages. It also generates functions for serializing the data types to either JSON or a more compact binary format.

from skirout import shapes  # source file generated by the Skir compiler

point = shapes.Point(x=3, y=4, label="P")
json = shapes.Point.SERIALIZER.to_json(point)
point = shapes.Point.SERIALIZER.from_json(json)
assert(point == shapes.Point(x=3, y=4, label="P"))

As of November 2025, Skir has official plugins for:

Other official and unofficial plugins will come.

Why Skir

  • Generates code in different languages. This makes Skir ideal for systems where different services are written in different languages but need to exchange structured data.
  • Supports serialization to JSON or binary format.
  • Serialize now, deserialize in 100 years. Skir is designed with backward and forward compatibility in mind. You can evolve your data schemas by adding new fields or renaming fields. You will still be able to deserialize old values, and you won't break existing applications that use older versions of the schema.
  • Generates code that makes no compromise with type safety.
  • Allows you to define typesafe interfaces between your services or your backend and your frontend.
  • No boilerplate code when you adding a new method to a service.

Language reference

Records

There are two types of records: structs and enums.

Structs

Use the keyword struct to define a struct, which is a collection of fields of different types.

The fields of a struct have a name, but during serialization they are actually identified by a number, which can either be set explicitly:

struct Point {
  x: int = 0;
  y: int = 1;
  label: string = 2;
}

or implicitly:

struct Point {
  x: int;  // implicitly set to 0
  y: int;  // implicitly set to 1
  label: string;  // implicitly set to 2
}

If you're not explicitly specifying the field numbers, you must be careful not to change the order of the fields or else you won't be able to deserialize old values.

// BAD: you can't reorder the fields and keep implicit numbering
struct Point {
  label: string;
  x: int;
  y: int;
}

// GOOD
struct Point {
  label: string = 2;

  // Fine to rename fields
  x_coordinate: int = 0;
  y_coordinate: int = 1;

  // Fine to add new fields
  color: Color = 3;
}

Enums

Enums in Skir are similar to enums in Rust. An enum value is one of several possible variants, and each variant can optionally have data associated with it.

// Indicates whether an operation succeeded or failed.
enum OperationStatus {
  SUCCESS;  // a constant field
  error: string;  // a wrapper field
}

In this example, an OperationStatus is one of these 3 things:

  • the SUCCESS constant
  • an error with a string value
  • UNKNOWN: a special implicit variant common to all enums

If you need a variant to hold multiple values, wrap them inside a struct:

struct MoveAction {
  x: int32;
  y: int32;
}

enum BoardGameTurn {
  PASS;
  move: MoveAction;
}

Like structs, the fields of an enum have a number, and the numbering can be explicit or implicit.

enum ExplicitNumbering {
  // The numbers don't need to be consecutive.
  FOO = 10;
  bar: string = 2;
}

enum ImplicitNumbering {
  // Implicit numbering is 1-based.
  // 0 is reserved for the special UNKNOWN variant.

  FOO;  // = 1
  bar: string;  // = 2
}

The fields numbers are used for identifying the variants in the serialization format (not the field names). You must be careful not to change the number of a field, or you won't be able to deserialize old values. For example, if you're using implicit numbering, you must not reorder the fields.

It is always fine to rename an enum, rename the fields of an enum, or add new fields to an enum.

Nesting records

You can define a record (struct or enum) within the definition of another record. This is simply for namespacing, and it can help make your .skir files more organized.

enum Status {
  OK;

  struct Error {
    message: string;
  }
  error: Error;
}

struct Foo {
  // Note the dot notation to refer to the nested record.
  error: Status.Error;
}

Removed fields

When removing a field from a struct or an enum, you must mark the removed number in the record definition using the removed keyword. The syntax is different whether you're using explicit or implicit numbering:

struct ExplicitNumbering {
  a: string = 0;
  b: string = 1:
  d: string = 3;
  removed 2, 4;
}

struct ImplicitNumbering {
  a: string;
  b: string:
  removed;
  d: string;
  removed;
}

Data types

Primitive types

  • bool: true or false
  • int32: a signed 32-bits integer
  • int64: a signed 64-bits integer
  • uint64: an unsigned 64-bits integer
  • float32: a 32-bits floating point number
  • float64: a 64-bits floating point number
  • string: a Unicode string
  • bytes: a sequence of bytes
  • timestamp: a specific instant in time represented as an integral number of milliseconds since the Unix epoch, from 100M days before the Unix epoch to 100M days after the Unix epoch

Array type

Wrap the item type inside square brackets to represent an array of items, e.g. [string] or [User].

Keyed arrays

If the items are structs and one of the struct fields can be used to identify every item in the array, you can add the field name next to a pipe character: [Item|key_field].

Example:

struct User {
  id: uint64;
  name: string;
}

struct UserRegistry {
  users: [User|id];
}

Language plugins will generate methods allowing you to perform key lookups in the array using a hash table. For example, in Python:

user = user_registry.users.find(user_id)
if user:
    do_something(user)

If the item key is nested within another struct, you can chain the field names like so: [Item|a.b.c].

The key type must be a primitive type of an enum type. If it's an enum type, add .kind at the end of the key chain:

enum Weekday {
  MONDAY;
  TUESDAY;
  WEDNESDAY;
  THURSDAY;
  FRIDAY;
  SATURDAY;
  SUNDAY;
}

struct WeekdayWorkStatus {
  weekday: Weekday;
  working: bool;
}

struct Employee {
  weekly_schedule: [WeekdayWorkStatus|weekday.kind];
}

Optional type

Add a question mark at the end of a non-optional type to make it optional. An other_type? value is either an other_type or null.

Constants

You can define constants of any type with the const keyword. The syntax for representing the value is similar to JSON, with the following differences:

  • object keys must not be quoted
  • trailing commas are allowed and even encouraged
  • strings can be single-quoted or double-quoted
  • strings can span multiple lines by escaping new line characters
const PI: float64 = 3.14159;

const LARGE_CIRCLE: Circle = {
  center: {
    x: 100,
    y: 100,
  },
  radius: 100,
  color: {
    r: 255,
    g: 0,
    b: 255,
    label: "fuschia",
  },
};

const MULTILINE_STRING: string = 'Hello\
world\
!';

const SUPPORTED_LOCALES: [string] = [
  "en-GB",
  "en-US",
  "es-MX",
];

// Use strings for enum constants.
const REST_DAY: Weekday = "SUNDAY";

// Use { kind: ..., value: ... } for enum variants holding a value.
const NOT_IMPLEMENTED_ERROR: OperationStatus = {
  kind: "error",
  value: "Not implemented",
};

Methods

The method keyword allows you to define the signature of a remote method.

struct GetUserProfileRequest {
  // ...
}

struct GetUserProfileResponse {
  // ...
}

method GetUserProfile(GetUserProfileRequest): GetUserProfileResponse;

The request and response can have any type.

Imports

The import statement allows you to import types from another module. You can either specify the names to import, or import the whole module with an alias using the as keyword.

import Point, Circle from "geometry/geometry.skir";
import * as color from "color.skir";

struct Rectangle {
  top_left: Point;
  bottom_right: Point;
}

struct Disk {
  circle: Circle;
  fill_color: color.Color;  // the type is defined in the "color.skir" module
}

The path is always relative to the root of the Skir source directory.

Serialization formats

When serializing a data structure, you can chose one of 3 formats.

JSON, dense flavor

This is the serialization format you should chose in most cases.

Structs are serialized as JSON arrays, where the field numbers in the index definition match the indexes in the array. Enum constants are serialized as numbers.

struct User {
  user_id: int;
  removed;
  name: string;
  rest_day: Weekday;
  pets: [Pet];
  nickname: string;
}

const JOHN_DOE = {
  user_id: 400,
  name: "John Doe",
  rest_day: "SUNDAY",
  pets: [
    { name: "Fluffy" },
    { name: "Fido" },
  ],
  nickname: "",
}

The dense JSON representation of JOHN_DOE is:

[400,0,"John Doe",7,[["Fluffy"],["Fido"]]]

A couple observations:

  • Removed fields are replaced with zeros
  • Trailing fields with default values (nickname in this example) are omitted

This format is not very readable, but it's compact and it allows you to rename fields in your struct definition without breaking backward compatibility.

JSON, readable flavor

Structs are serialized as JSON objects, and enum constants are serialized as strings.

The readable JSON representation of JOHN_DOE is:

{
  "user_id": 400,
  "name": "Johm Doe",
  "rest_day": "SUNDAY",
  "pets": [
    { "name": "Fluffy" },
    { "name": "Fido" }
  ]
}

This format is more verbose and readable, but it should not be used if you need persistence, because Skir allows fields to be renamed in record definitions. In other words, never store a readable JSON on disk or in a database.

Binary format

This format is a bit more compact than JSON, and serialization/deserialization can be faster in languages like C++. Only prefer this format over JSON when the small performance gain is likely to matter, which should be rare.

Skir services

Calling a method with cURL

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"method": "MethodName", "request": {"foo": 3, "bar": []}}' \
  http://localhost:8787/myapi