# Skir Skir is a declarative language for defining data types, constants, and APIs. Write your schema once in a .skir file and generate idiomatic, type-safe code in TypeScript, Python, Java, C#, Kotlin, C++, Dart, Swift, Go, Rust, Zig, and Gleam. For full documentation in one file, see: https://skir.build/llms-full.txt ## Syntax example ```skir // shapes.skir struct Point { x: int32; y: int32; label: string; } struct Shape { points: [Point]; /// A short string describing this shape. label: string; } const TOP_RIGHT_CORNER: Point = { x: 600, y: 400, label: "top-right corner", }; /// Returns true if no part of the shape's boundary curves inward. method IsConvex(Shape): bool = 12345; ``` Skir compiles these definitions into native, type-safe code: ```python # my_project.py from skirout.shapes_skir import Point # Python module generated by Skir point = Point(x=3, y=4, label="P") # Round-trip serialization to JSON restored = Point.serializer.from_json(Point.serializer.to_json(point)) assert(restored == point) ``` ## 60-second setup 1. Initialize: ```bash npx skir init ``` 2. Configure generators in `skir.yml` (one or many targets): ```yaml generators: - mod: skir-typescript-gen outDir: ./frontend/skirout config: {} - mod: skir-python-gen outDir: ./backend/skirout config: {} ``` 3. Generate code: ```bash npx skir gen ``` 4. Keep things clean in CI: ```bash npx skir format --ci npx skir snapshot --ci ``` ## Language reference ### struct A record with named fields identified by number (not name) during serialization. Field numbers can be explicit or implicit (based on declaration order). ```skir struct Point { x: int32; // = 0 y: int32; // = 1 label: string; // = 2 } ``` With explicit numbering: ```skir struct Point { x: int32 = 0; y: int32 = 1; label: string = 2; } ``` ### enum Sum types (like in Rust, Swift, or Zig). Each variant is either a constant (`SUCCESS;`) or a wrapper carrying a value (`error: string;`). An implicit `UNKNOWN` variant (number 0) always exists. ```skir enum OperationStatus { SUCCESS; // = 1 error: string; // = 2 } ``` ### method Defines an RPC method signature. The numeric ID (e.g. `= 12345`) is stable across renames and drives routing. No two methods can share the same ID. ```skir method GetUserProfile( GetUserProfileRequest ): GetUserProfileResponse = 12345; ``` Request and response types can also be defined inline: ```skir method GetUserProfile( struct { user_id: int32; } ): struct { profile: UserProfile?; } = 12345; ``` ### Inline records Structs and enums can be defined inline inside a field type. The compiler infers the type name from the field name (snake_case → PascalCase): ```skir struct Notification { metadata: struct { sent_at: timestamp; sender_id: string; } payload: enum { APP_LAUNCH; message: struct { body: string; title: string; } } } ``` This is equivalent to defining `Metadata` and `Payload` as named nested records. Inline records cannot be wrapped in `[...]` or `...?` — define a named nested record instead. ### const Shared typed constants compiled into every target language. Syntax is JSON-like but with unquoted keys and optional trailing commas. ```skir const TOP_LEFT: Point = { x: 0, y: 0, label: "origin" }; ``` ### Data types Primitive types: `bool`, `int32`, `int64`, `hash64`, `float32`, `float64`, `string`, `bytes`, `timestamp`. Composite types: - `[T]` — array of T - `[T|key_field]` — keyed array; generated code exposes O(1) key-based lookup - `T?` — optional (nullable) ### Removed fields/variants When removing a field or variant, mark its number with `removed` to prevent accidental reuse: ```skir struct User { name: string; // = 0 removed; // field 1 was removed email: string; // = 2 } ``` Or with explicit numbering: ```skir struct User { name: string = 0; email: string = 2; removed 1; } ``` ### Imports ```skir import { Point } from "geometry/geometry.skir"; import * as color from "color.skir"; ``` Paths are relative to the Skir source root. ## Compatibility rules you should remember Safe changes: - Add struct fields. - Add enum variants. - Rename types/fields/variants (wire format uses numeric IDs). - Convert constant variant -> wrapper variant. - Some widening type changes (for example `int32 -> int64`). Unsafe changes (breaking): - Change field/variant numbers, or reorder when using implicit numbering. - Reuse removed numbers. - Incompatible type changes. - Convert wrapper variant -> constant variant. Skir’s `snapshot` command detects accidental breaking changes automatically. ## Serialization formats - Dense JSON (recommended): compact, persistable, rename-safe. - Readable JSON: human-friendly, debugging only (not safe for persistence). - Binary: most compact, useful when you need max performance/size efficiency. Using `Point(x=3, y=4, label="P")` as an example: Dense JSON — structs are serialized as arrays (array index = field number): ```json [3,4,"P"] ``` Readable JSON — structs are serialized as objects with field names: ```json { "x": 3, "y": 4, "label": "P" } ``` Because dense JSON uses positional indexes rather than field names, you can freely rename fields in your schema without breaking compatibility with persisted data. Readable JSON does not have this guarantee. ## SkirRPC in one line Define methods in schema, implement handlers in your web framework, and call them with generated typed clients. Contracts stay in sync by construction. ## Dependencies Skir can import schemas directly from GitHub repos via `dependencies` in `skir.yml`, including transitive dependencies. ```yaml # skir.yml dependencies: "@gepheum/skir-fantasy-game-example": v1.0.0 "@my-org/user-service-skir": v3.5.0 ``` The key is `@owner/repo` and the value is the Git tag. Then import types in your `.skir` files: ```skir import { Quest } from "@gepheum/skir-fantasy-game-example/fantasy_game.skir"; ``` Run `npx skir gen` to fetch dependencies (cached in `skir-external/`, add to `.gitignore`).