Skip to content

soundscript-lang/soundscript

soundscript

soundscript logo

soundscript is a sound checker and language tooling layer for TypeScript projects.

Prebuilt macOS, Linux, and Windows CLI downloads are attached to GitHub releases.

The intended stable v1 surface is the checker, mixed .ts / .sts adoption story, the VS Code extension, a very small stdlib, and a narrow macro surface. Broader macro work and Wasm remain experimental.

For the fastest orientation on the builtin surface and the recommended coding patterns, see docs/reference/builtin-modules.md and docs/guides/idiomatic-soundscript.md and docs/guides/common-rewrites.md.

It adds a second file type, .sts, for code checked under a stricter rule set. Ordinary .ts files keep normal TypeScript behavior. The goal is incremental adoption: move the parts of a codebase you care about into .sts, keep the rest as .ts, and make interop explicit.

Status

The release-facing v1 surface is:

  • .sts for sound code
  • mixed .ts / .sts projects
  • soundscript check
  • LSP support
  • import-scoped macro authoring through the compiler-provided sts:macros builtin module
    • user-authored macro modules are .macro.sts compile-time plugin modules, not ordinary .sts, .ts, or .js scripts
  • compiler-owned builtin modules under sts:*, with the stable v1 surface centered on sts:prelude, sts:result, sts:match, sts:failures, sts:url, sts:fetch, sts:text, sts:random, sts:json, sts:compare, sts:hash, sts:decode, sts:encode, sts:codec, sts:derive, sts:async, sts:hkt, sts:typeclasses, and sts:macros
  • the canonical runtime/toolchain npm package @soundscript/soundscript, with emitted runtime and TS interop imports under @soundscript/soundscript/*

This repository also contains broader implemented experimental work beyond the stable v1 contract. That includes the soundscript compile path, experimental builtin surfaces such as sts:numerics, sts:value, and sts:experimental/*, #[newtype] and #[value], proof and framework macros like #sql, #css, #graphql, and #component, and the broader public Wasm target/runtime matrix work. Those surfaces exist in the repo, but they are not the stable release-facing v1 contract.

Quick start

Build the CLI:

deno task build
./bin/soundscript --help

Start a new project:

./bin/soundscript init
./bin/soundscript check

That creates src/main.sts and a tsconfig.json that includes both src/**/*.ts and src/**/*.sts.

Add soundscript to an existing TypeScript project:

./bin/soundscript init --mode existing
./bin/soundscript check --project tsconfig.soundscript.json

That creates a tsconfig.soundscript.json overlay so you can start adding .sts files without rewriting the rest of the project.

For CI or tooling:

./bin/soundscript check --project tsconfig.soundscript.json --format json
./bin/soundscript check --project tsconfig.soundscript.json --format ndjson
./bin/soundscript deno run src/main.sts
./bin/soundscript explain SOUND1002

For local Node execution, use @soundscript/register:

node --import @soundscript/register src/main.sts

You can also opt selected .ts / .tsx / .mts / .cts files into local soundscript behavior without renaming them:

{
  "soundscript": {
    "include": ["src/**/*.ts", "src/**/*.tsx"]
  }
}

@soundscript/register and soundscript deno expect @soundscript/soundscript to be installed in the current project or an ancestor workspace, because the transformed graph imports the runtime package.

For published libraries, the intended package shape is:

  • ordinary ESM js + d.ts for TypeScript and runtime consumers
  • shipped authored .sts source under package.json#soundscript.exports
  • soundscript build as the canonical package emit flow
  • @soundscript/register and soundscript deno as local runtime wrappers for mixed .ts/.sts apps
  • explicit adapter packages for local source-driven apps and bundlers: @soundscript/register, @soundscript/bun-plugin, @soundscript/vite, and @soundscript/webpack-loader
  • source maps back to original .sts so stack traces and debuggers stay on authored source

The repository split is now:

  • soundscript for the checker, CLI, LSP, runtime package, and generic project-transform support used by first-party adapters
  • website for the public docs site and mirrored reference pages
  • adapters for the explicit adapter packages and their host-specific implementations
  • editors for editor clients such as the VS Code extension and @soundscript/tsserver-plugin

The public website and docs now live in the standalone website repository: soundscript-lang/website.

The leaf adapter packages are no longer vendored in this repo. Use the standalone adapters repository: soundscript-lang/adapters.

The editor packages are no longer vendored in this repo. Use the standalone editors repository: soundscript-lang/editors.

For package emit:

./bin/soundscript build --project tsconfig.soundscript.json

The intended platform design is Deno-inspired:

  • prefer Web-standard APIs first
  • keep stdlib modules small and composable
  • expose portable globals and leaf modules such as fetch, URL, and text encoding where the current runtime supports them
  • keep host-specific behavior behind explicit wrappers instead of promising extra stable runtime modules

This package model is designed for external macro-authored libraries and frameworks. The soundscript repo keeps only small generic fixture coverage for packaged macro resolution and runtime materialization; framework implementations now live in their own repositories.

Exit codes are:

  • 0 for success with no blocking diagnostics
  • 1 for project diagnostics or unsupported-code findings
  • 2 for CLI usage, project setup/configuration failures, or unexpected internal tool errors

Release automation

The canonical release version comes from src/cli/cli.ts.

For a normal public release, bump VERSION there, push main, then run the Publish Release GitHub Actions workflow. It will:

  • run the release checks
  • prepare and publish the npm package set
  • create and push the v<version> tag
  • create the GitHub release
  • attach the platform CLI archives plus checksums

The workflow is set up for npm trusted publishing from GitHub Actions. npm must be configured to trust the publish-release.yml workflow file in this repository for every package in the release set:

  • @soundscript/cli-darwin-arm64
  • @soundscript/cli-darwin-x64
  • @soundscript/cli-linux-arm64
  • @soundscript/cli-linux-x64
  • @soundscript/cli-win32-x64
  • @soundscript/soundscript
  • soundscript

If npm returns a 404 during npm publish, the usual cause is that the package's trusted-publisher settings have not been added yet. The workflow also needs GitHub's id-token: write permission, which is already configured in the workflow file.

You can bulk-configure the package set from a shell after logging in to npm with an account that has write access to all seven packages:

for package in \
  @soundscript/cli-darwin-arm64 \
  @soundscript/cli-darwin-x64 \
  @soundscript/cli-linux-arm64 \
  @soundscript/cli-linux-x64 \
  @soundscript/cli-win32-x64 \
  @soundscript/soundscript \
  soundscript
do
  npm trust github "$package" \
    --repo soundscript-lang/soundscript \
    --file publish-release.yml \
    --yes
  sleep 2
done

If you need to reattach CLI archives to an existing release, use the separate Backfill CLI Assets workflow.

What .sts means

.sts files are checked in soundscript.

.ts files are left alone.

When .sts code imports ordinary TypeScript, JavaScript, or declaration-only packages, the import needs // #[interop]:

// #[interop]
import { readConfig } from './legacy.ts';

From the other direction, .ts can import .sts without any annotation. It sees a projected public TypeScript surface for the .sts module.

That is the adoption model. Existing TypeScript stays where it is. New or critical code can move into .sts.

Remaining rough edges

soundscript removes a large set of TypeScript unsoundness paths, but it still lives inside JS/TS runtime semantics. The main remaining rough edges are:

  • null vs undefined, especially at JSON, regex, and trusted-interop boundaries
  • arbitrary foreign throws and rejections; expansion-enabled source normalizes catch (error) and built-in Promise rejection handlers to plain Error, but other boundaries still require explicit normalization such as sts:failures.normalizeThrown(...)
  • stable v1 still defaults to ordinary JS number behavior, including NaN, Infinity, and -0; the repo also contains experimental machine numerics work under sts:numerics, but that is outside the stable v1 contract
  • stable v1 still relies heavily on structural typing for same-shape interface and object values; class nominality plus #[newtype] and #[value] exist in the repo, but the broader nominal and value-semantics story remains outside the stable v1 contract
  • there is no active effort to remove raw null from the language; it remains part of the honest platform model

The release-facing contract for those boundaries is in docs/reference/v1-user-contract.md.

Example

// src/math.sts
export function area(radius: number): number {
  return Math.PI * radius * radius;
}

const raw = JSON.parse('{"radius": 3}');

if (
  typeof raw === 'object' &&
  raw !== null &&
  'radius' in raw &&
  typeof raw.radius === 'number'
) {
  console.log(area(raw.radius));
}

There is no new syntax here. The difference is the checker.

Commands

The main commands are:

  • soundscript init
  • soundscript check
  • soundscript build
  • soundscript expand
  • soundscript deno
  • soundscript explain
  • soundscript lsp

The repo also ships soundscript compile. compile and the broader compiler / Wasm surface remain experimental; the checker is still the main entry point.

Editor support

The language server runs over stdio:

soundscript lsp

There is also a VS Code client in the separate editors repo:

git clone https://github.com/soundscript-lang/editors.git ../editors
cd ../editors/packages/vscode
npm install
npm run compile

Then:

  1. open the editors repo root in VS Code
  2. run the Run soundscript extension launch configuration
  3. in the spawned Extension Development Host, use File -> Open Folder... to open the workspace you want to test, for example soundscript-example
  4. open an .sts file to confirm the soundscript language mode and TextMate grammar are active

Notes:

  • the VS Code extension and @soundscript/tsserver-plugin live in soundscript-lang/editors
  • work from ../editors/packages/vscode when developing the extension locally
  • the extension prefers a workspace-installed soundscript binary and falls back to PATH unless you override soundscript.server.command
  • soundscript.server.* settings only configure how the already-loaded extension launches the language server; those settings do not make VS Code discover the extension
  • custom grammar-based syntax highlighting currently applies only to .sts; .ts and .tsx use the built-in TypeScript grammar and only pick up LSP features from soundscript

Current LSP support includes diagnostics, hover, signature help, definition, references, rename, completions, document symbols, formatting, semantic tokens, and code actions / quick fixes.

Repository layout

The repo also contains:

  • checker and CLI
  • .ts / .sts interop
  • LSP
  • macro work
  • compiler / Wasm work

You do not need macros or Wasm to use soundscript as a checker.

Development

The maintainer toolchain is Deno:

deno task build
deno task check
deno task fmt
deno task lint
deno task test
deno task verify

Docs

Start here:

About

Sound checker and language tooling for TypeScript.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages