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 🙏

© 2026 – Pkg Stats / Ryan Hefner

osury

v0.17.3

Published

Generate ReScript types with Sury schemas from OpenAPI specifications

Readme

osury

Generate ReScript types with Sury schemas from OpenAPI specifications.

Acknowledgements

Huge thanks to the ReScript team for an amazing language, and special thanks to @DZakh for the incredible Sury library that made this project possible.

Early Stage Warning

This project is in a very early stage of development and is tailored to my specific needs. It comes with no guarantees of stability, correctness, or completeness.

Suggestions and contributions are very welcome! Feel free to open issues or submit PRs.

Features

  • OpenAPI 3.x → ReScript types
  • @schema annotations for Sury PPX validation
  • @genType for TypeScript interop
  • Union types extracted as proper variants with @tag("_tag")
  • Automatic deduplication of identical union structures
  • Generates module S = Sury alias (required by sury-ppx)
  • Generates helper shims for TypeScript interop (Dict.gen.ts, Nullable.shim.ts)
  • Proper JSON null support via Nullable.t<T> (maps to T | null in TypeScript)
  • Path response types generation from paths.*.responses

Installation

npm install -D osury

Usage

CLI

# Generate to default ./Generated.res + shims
npx osury openapi.json

# Generate to specific directory
npx osury openapi.json src/API.res
# Creates: src/API.res, src/Dict.gen.ts, src/Nullable.res, src/Nullable.shim.ts

# With explicit output flag
npx osury generate openapi.json -o src/Schema.res

Full Example: OpenAPI → ReScript → TypeScript

OpenAPI Input:

{
  "components": {
    "schemas": {
      "User": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "email": { "type": "string" },
          "status": { "$ref": "#/components/schemas/Status" },
          "role": {
            "anyOf": [
              { "$ref": "#/components/schemas/Admin" },
              { "$ref": "#/components/schemas/Guest" }
            ]
          }
        },
        "required": ["id", "email"]
      },
      "Status": {
        "type": "string",
        "enum": ["pending", "active", "blocked"]
      },
      "Admin": {
        "type": "object",
        "properties": {
          "_tag": { "type": "string", "const": "Admin" },
          "permissions": { "type": "array", "items": { "type": "string" } }
        }
      },
      "Guest": {
        "type": "object",
        "properties": {
          "_tag": { "type": "string", "const": "Guest" },
          "expiresAt": {
            "anyOf": [{ "type": "string" }, { "type": "null" }]
          }
        }
      }
    }
  }
}

Generated ReScript (Schema.res):

module S = Sury

@genType
@schema
type status = [#pending | #active | #blocked]

@genType
@schema
type admin = {
  permissions: option<array<string>>
}

@genType
@schema
type guest = {
  expiresAt: @s.null Nullable.t<string>
}

@genType
@tag("_tag")
@schema
type adminOrGuest = Admin({
  permissions: option<array<string>>
}) | Guest({
  expiresAt: @s.null Nullable.t<string>
})

@genType
@schema
type user = {
  id: int,
  email: string,
  status: option<status>,
  role: option<adminOrGuest>
}

Generated TypeScript (Schema.gen.ts via genType):

import type {t as Nullable_t} from './Nullable.gen';

export type status = "pending" | "active" | "blocked";

export type admin = { readonly permissions: (undefined | string[]) };

export type guest = { readonly expiresAt: Nullable_t<string> };

export type adminOrGuest =
    { _tag: "Admin"; readonly permissions: (undefined | string[]) }
  | { _tag: "Guest"; readonly expiresAt: Nullable_t<string> };

export type user = {
  readonly id: number;
  readonly email: string;
  readonly status: (undefined | status);
  readonly role: (undefined | adminOrGuest)
};

The library uses sury-ppx for code-first approach — @schema annotation automatically generates runtime validators from type definitions.

Demo Playground

Try it online: osury-production.up.railway.app

This repository also includes a local demo for development:

Run locally

npm run demo

Open http://localhost:4173/demo/.

What it supports

  • Upload OpenAPI JSON as a file
  • Paste OpenAPI JSON into a text area
  • Formatted ReScript output
  • Formatted TypeScript output (derived from osury AST and matching generated ReScript structures)

Helper Files

Also generates helper files:

Dict.gen.ts — TypeScript shim for dictionaries:

export type t<T> = { [key: string]: T };

Nullable.res — ReScript nullable type:

@genType.import(("./Nullable.shim.ts", "t"))
type t<'a> = option<'a>

Nullable.shim.ts — TypeScript shim for nullable:

export type t<T> = T | null;

Generated Annotations

| Annotation | Purpose | |------------|---------| | @genType | TypeScript type generation | | @schema | Sury PPX validation schema | | @tag("_tag") | Discriminated union support (Effect TS compatible) | | @s.null | Field-level JSON null support | | @unboxed | Primitive-only union optimization | | @as("name") | Reserved keyword field mapping |

Requirements

For the generated code to compile, your project needs:

Type Mapping

| OpenAPI | ReScript | |---------|----------| | string | string | | number | float | | integer | int | | boolean | bool | | null | unit | | array | array<T> | | object | { field: T } | | $ref | type reference | | enum | poly variant [#A \| #B] | | const | single-value enum (for _tag) | | anyOf (nullable) | Nullable.t<T>T \| null in TS | | anyOf (union) | variant type with @tag("_tag") | | oneOf (discriminated) | poly variant with _tag.const extraction | | allOf | merged object type | | additionalProperties | Dict.t<T> | | default value | field becomes required |

Path Responses

Types are also generated from path responses:

{
  "paths": {
    "/users": {
      "get": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/UserList" }
              }
            }
          }
        }
      }
    }
  }
}

Generates: type getUsersResponse = userList

License

MIT