Platform APIs

March 19, 2026 ยท View on GitHub

Coi provides type-safe access to browser APIs through the WebCC toolchain. These APIs are automatically generated from the WebCC schema.

Type System

All platform types use a consistent pattern:

  • type: Defines a handle type (like Canvas, Image, DOMElement)
  • shared def: Static/factory methods called on the type itself
  • def: Instance methods called on an instance
// Static method
Image photo = Image.load("photo.png");

// Instance method
canvas.setSize(800, 600);
ctx = canvas.getContext2d();

No-Copy Types

Platform types (handles to browser resources) cannot be copied - they can only be moved or referenced:

// ERROR: Cannot copy a Canvas
mut Canvas canvas1 = Canvas.createCanvas("c1", 800.0, 600.0);
mut Canvas canvas2 = canvas1;  // Error!

// OK: Move ownership
mut Canvas canvas1 = Canvas.createCanvas("c1", 800.0, 600.0);
mut Canvas canvas2 := canvas1;  // canvas1 is now invalid

// OK: Reference (borrow)
mut Canvas canvas1 = Canvas.createCanvas("c1", 800.0, 600.0);
mut Canvas& canvasRef = canvas1;  // Both valid (& is part of type, not value)

// OK: Fresh value from factory method
mut Canvas canvas = Canvas.createCanvas("c1", 800.0, 600.0);  // Not a copy

This applies to all platform types:

  • Canvas, CanvasContext2D
  • DOMElement, Image, Audio
  • WebSocket, FetchRequest
  • WebGLContext, WGPUContext, etc.

Arrays of these types also cannot be copied:

// ERROR: Cannot copy Audio[]
mut Audio[] sounds = [Audio.load("a.mp3"), Audio.load("b.mp3")];
mut Audio[] backup = sounds;  // Error!

// OK: Move the array
mut Audio[] backup := sounds;  // sounds is now invalid

Canvas

2D drawing, paths, text, images, and transformations.

Canvas Methods

MethodDescription
Canvas.createCanvas(dom_id, width, height)Create a new canvas element
canvas.getContext2d()Get 2D rendering context
canvas.getContextWebgl()Get WebGL rendering context
canvas.getContextWebgpu()Get WebGPU rendering context
canvas.setSize(width, height)Set canvas dimensions
canvas.logCanvasInfo()Log canvas information

CanvasContext2D Methods

Shapes

MethodDescription
fillRect(x, y, w, h)Fill a rectangle
strokeRect(x, y, w, h)Stroke a rectangle outline
clearRect(x, y, w, h)Clear a rectangular area

Paths

MethodDescription
beginPath()Start a new path
closePath()Close the current path
moveTo(x, y)Move to point
lineTo(x, y)Draw line to point
arc(x, y, radius, startAngle, endAngle)Draw an arc
arcTo(x1, y1, x2, y2, radius)Draw arc between points
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)Cubic bezier curve
quadraticCurveTo(cpx, cpy, x, y)Quadratic bezier curve
rect(x, y, w, h)Add rectangle to path
ellipse(x, y, rx, ry, rotation, start, end, ccw)Draw ellipse
fill()Fill the current path
stroke()Stroke the current path
clip()Clip to current path

Styles

MethodDescription
setFillStyle(r, g, b)Set fill color (RGB 0-255)
setFillStyleStr(color)Set fill color (CSS string)
setStrokeStyle(r, g, b)Set stroke color (RGB 0-255)
setStrokeStyleStr(color)Set stroke color (CSS string)
setLineWidth(width)Set line width
setLineCap(cap)Set line cap ("butt", "round", "square")
setLineJoin(join)Set line join ("miter", "round", "bevel")
setGlobalAlpha(alpha)Set global transparency (0.0-1.0)
setShadow(blur, offX, offY, color)Set shadow effect

Text

MethodDescription
setFont(font)Set font (e.g., "16px Arial")
setTextAlign(align)Set alignment ("left", "center", "right")
setTextBaseline(baseline)Set baseline ("top", "middle", "bottom")
fillText(text, x, y)Draw filled text
strokeText(text, x, y)Draw stroked text
measureTextWidth(text)Get text width in pixels

Images

MethodDescription
drawImage(image, x, y)Draw image at position
drawImageScaled(image, x, y, w, h)Draw image scaled
drawImageFull(img, sx, sy, sw, sh, dx, dy, dw, dh)Draw image with source rect

Transforms

MethodDescription
save()Save current state
restore()Restore saved state
translate(x, y)Move origin
rotate(angle)Rotate (radians)
scale(x, y)Scale
setTransform(a, b, c, d, e, f)Set transform matrix
resetTransform()Reset to identity

Setup Example

Note: Canvas.createCanvas() has been removed. Use <canvas &={canvas}> in your view and call canvas.setSize() in the mount {} block. See CHANGES.md for migration details.

component CanvasApp {
    mut Canvas canvas;
    mut CanvasContext2D ctx;
    
    mount {
        canvas.setSize(800, 600);
        ctx = canvas.getContext2d();
    }
    
    view {
        <canvas &={canvas}></canvas>
    }
}

Example: Bouncing Ball

component AnimatedBall {
    mut Canvas canvas;
    mut CanvasContext2D ctx;
    mut float x = 100.0;
    mut float y = 100.0;
    mut float dx = 3.0;
    mut float dy = 2.0;

    mount {
        canvas.setSize(800, 600);
        ctx = canvas.getContext2d();
    }

    tick(float dt) {
        ctx.clearRect(0, 0, 800, 600);
        ctx.setFillStyle(66, 133, 244);
        ctx.beginPath();
        ctx.arc(x, y, 20, 0, 6.28318);
        ctx.fill();

        x += dx;
        y += dy;
        if (x < 20 || x > 780) dx = -dx;
        if (y < 20 || y > 580) dy = -dy;
    }

    view {
        <canvas &={canvas}></canvas>
    }
}

Image

Load images for canvas rendering.

Methods

MethodDescription
Image.load(string src)Load image from URL (static)

Example

component Gallery {
    mut Canvas canvas;
    mut CanvasContext2D ctx;
    Image photo;

    mount {
        canvas.setSize(400, 300);
        ctx = canvas.getContext2d();
        photo = Image.load("photo.png");
    }

    def draw() : void {
        ctx.drawImage(photo, 0, 0);
    }

    view {
        <div>
            <canvas &={canvas}></canvas>
            <button onclick={draw}>Draw</button>
        </div>
    }
}

Storage

Local storage for persisting data.

Methods

MethodDescription
Storage.setItem(string key, string value)Store a key-value pair
Storage.removeItem(string key)Remove item by key
Storage.clear()Clear all stored items

Example

component Settings {
    mut string theme = "light";

    def saveTheme() : void {
        Storage.setItem("theme", theme);
    }

    def toggleTheme() : void {
        theme = theme == "light" ? "dark" : "light";
        saveTheme();
    }

    view {
        <button onclick={toggleTheme}>Theme: {theme}</button>
    }
}

Audio

Audio playback with volume, looping, and playback position.

Methods

MethodDescription
Audio.load(string src)Create audio from URL (static)
play()Start playback
pause()Pause playback
setVolume(float vol)Set volume (0.0 to 1.0)
setLoop(int loop)Enable/disable looping (1/0)
getCurrentTime()Get current playback position (seconds)
getDuration()Get total duration (seconds)

Example

component MusicPlayer {
    mut Audio music;
    mut bool playing = false;
    mut float progress = 0;

    mount {
        music = Audio.load("song.mp3");
        music.setVolume(0.8);
    }

    tick(float dt) {
        if (playing) {
            float duration = music.getDuration();
            if (duration > 0) {
                progress = (music.getCurrentTime() / duration) * 100;
            }
        }
    }

    def toggle() : void {
        if (playing) {
            music.pause();
        } else {
            music.play();
        }
        playing = !playing;
    }

    view {
        <div>
            <button onclick={toggle}>
                {playing ? "Pause" : "Play"}
            </button>
            <div style="width: ${progress}%"></div>
        </div>
    }
}

System

Logging, page title, time, random numbers, and URL navigation.

Methods

MethodDescription
System.log(string msg)Log message to console
System.warn(string msg)Log warning to console
System.error(string msg)Log error to console
System.setTitle(string title)Set page title
System.reload()Reload the page
System.openUrl(string url)Open URL in new tab
System.navigate(string path)Navigate to route (client-side routing)
System.getTime()Get time in seconds (float64)
System.getDateNow()Get milliseconds since epoch (float64)
System.getVisibilityState()Get document visibility state ("visible", "hidden", etc.)
System.isHidden()Check if document is hidden (true hidden, false visible)
System.random()Random float between 0.0 and 1.0
System.random(int seed)Seeded random (for reproducibility)

Example

// Logging
System.log("Debug message");
System.warn("Warning!");
System.error("Error occurred");

// Page title
System.setTitle("My App");

// Time
float now = System.getTime();       // Seconds (high precision)
float epoch = System.getDateNow();  // Milliseconds since epoch

// Page visibility
string visibility = System.getVisibilityState();
bool hidden = System.isHidden();

// Random numbers
float r = System.random();          // 0.0 to 1.0

// URL navigation
System.openUrl("https://example.com");  // Opens in new tab
System.navigate("/dashboard");           // Client-side navigation

Input

Keyboard input handling.

Methods

MethodDescription
Input.isKeyDown(int keyCode)Check if key is currently pressed
Input.isKeyUp(int keyCode)Check if key is currently released
Input.exitPointerLock()Exit pointer lock mode

Common Key Codes

KeyCode
Left Arrow37
Up Arrow38
Right Arrow39
Down Arrow40
Space32
Enter13
Escape27
W/A/S/D87/65/83/68

Example

component Game {
    mut Canvas canvas;
    mut CanvasContext2D ctx;

    mut float x = 400;
    mut float y = 300;
    mut float speed = 300;
    
    float width = 800;
    float height = 600;
    float radius = 20;

    mount {
        canvas.setSize(width, height);
        ctx = canvas.getContext2d();
    }

    tick(float dt) {
        // Movement
        if (Input.isKeyDown(37)) x -= speed * dt;  // Left
        if (Input.isKeyDown(39)) x += speed * dt;  // Right
        if (Input.isKeyDown(38)) y -= speed * dt;  // Up
        if (Input.isKeyDown(40)) y += speed * dt;  // Down

        ctx.clearRect(0, 0, width, height);
        
        // Background
        ctx.setFillStyleStr("#1a1a1a");
        ctx.fillRect(0, 0, 800, 600);
        // Player
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, 6.28);
        ctx.setFillStyleStr("#4ade80");
        ctx.fill();
    }


    view {
            <canvas &={canvas}></canvas>
    }
}

DOMElement

Reference handle for browser APIs and measurements.

DOMElement is used as a reference handle to interact with browser APIs that cannot be expressed declaratively in the view. Use the &={element} binding to capture a reference to an element.

Note: Direct DOM manipulation methods (.appendChild(), .setInnerHtml(), .addClass(), etc.) have been removed. Define your UI structure declaratively in the view {} block instead. For rendering HTML strings, use the <raw> element (see View Syntax). See CHANGES.md for migration details.

Methods

MethodDescription
requestFullscreen()Enter fullscreen mode
requestPointerLock()Request pointer lock
scrollToTop()Scroll element to top

Example

component VideoPlayer {
    mut DOMElement videoEl;

    def enterFullscreen() {
        videoEl.requestFullscreen();  // Browser API
    }

    view {
        <div>
            <video &={videoEl} src="video.mp4"></video>
            <button onclick={enterFullscreen}>Fullscreen</button>
        </div>
    }
}

Fetch

HTTP requests with callback-based response handling. Returns a FetchRequest handle.

Methods

MethodDescription
FetchRequest.get(url, headers="", &onSuccess=..., &onError=...)Make a GET request (static)
FetchRequest.post(url, body, headers="", &onSuccess=..., &onError=...)Make a POST request (static)
FetchRequest.patch(url, body, headers="", &onSuccess=..., &onError=...)Make a PATCH request (static)

headers is a JSON object string (for example: "{\"Authorization\":\"Bearer token\",\"Content-Type\":\"application/json\"}").

Callback Signatures

CallbackSignatureDescription
onSuccessdef handler(string data) : voidCalled with response data
onErrordef handler(string error) : voidCalled on request error

Example

component DataLoader {
    mut string result = "Click to load data";
    mut bool loading = false;

    def handleSuccess(string data) : void {
        loading = false;
        result = data;
    }

    def handleError(string error) : void {
        loading = false;
        result = "Error: " + error;
    }

    def loadData() : void {
        loading = true;
        result = "Loading...";
        FetchRequest.get(
            "https://api.example.com/data",
            &onSuccess = handleSuccess,
            &onError = handleError
        );
    }

    view {
        <div>
            <button onclick={loadData}>Load</button>
            <p>{result}</p>
        </div>
    }
}

POST Example

component FormSubmit {
    mut string status = "";

    def handleSuccess(string response) : void {
        status = "Submitted!";
    }

    def handleError(string error) : void {
        status = "Failed: " + error;
    }

    def submit() : void {
        status = "Submitting...";
        FetchRequest.post(
            "https://api.example.com/submit",
            "{\"name\": \"test\"}",
            "{\"Content-Type\":\"application/json\"}",
            &onSuccess = handleSuccess,
            &onError = handleError
        );
    }

    view {
        <div>
            <button onclick={submit}>Submit</button>
            <p>{status}</p>
        </div>
    }
}

PATCH Example

component ProfileUpdate {
    mut string status = "";

    def ok(string response) : void {
        status = "Updated";
    }

    def fail(string error) : void {
        status = "Failed: " + error;
    }

    def updateProfile() : void {
        FetchRequest.patch(
            "https://api.example.com/profile",
            "{\"displayName\":\"Eric\"}",
            "{\"Authorization\":\"Bearer token\",\"Content-Type\":\"application/json\"}",
            &onSuccess = ok,
            &onError = fail
        );
    }

    view {
        <div>
            <button onclick={updateProfile}>Update</button>
            <p>{status}</p>
        </div>
    }
}

JSON

Type-safe JSON parsing with compile-time schema validation and presence tracking.

Methods

MethodDescription
Json.parse(Type, json)Parse JSON object and return a result for match
Json.parse(Type[], json)Parse JSON array and return a result for match
Json.stringify(value)Convert pod type to JSON string (static)

Defining Pod Types

JSON parsing requires a pod definition that describes the expected structure:

pod Address {
    string street;
    string city;
    int zipcode;
}

pod User {
    string name;
    int age;
    string email;
    Address address;      // Nested objects
    string[] tags;        // Arrays
    Friend[] friends;     // Arrays of objects
}

Result Pattern

Json.parse(...) is consumed through match using Success(...) and Error(...) arms:

string res = match (Json.parse(User, json)) {
    Success(User data, Meta meta) => {
        // use data + field presence
        yield data.name;
    };
    Error(string message) => {
        yield message;
    };
};

For arrays:

int len = match (Json.parse(User[], jsonArray)) {
    Success(User[] data, UserMeta[] metas) => data.length();
    Error(string message) => 0;
};

Meta Structs (Presence Checking)

Success(..., Meta meta) provides presence checking for the parsed pod fields. This handles optional/nullable fields gracefully:

match (Json.parse(User, payload)) {
    Success(User u, Meta meta) => {
        if (meta.has(User.name)) {
            System.log("Name: " + u.name);
        }
        if (meta.has(User.age)) {
            System.log("Age: ${u.age}");
        }
    };
    Error(string error) => {
        System.log("Parse error: " + error);
    };
}

Example: Fetch + Parse

component UserLoader {
    mut string status = "Ready";
    mut User user;

    def handleSuccess(string data) : void {
        match (Json.parse(User, data)) {
            Success(User u, Meta meta) => {
                user = u;
                status = "Loaded: " + u.name;
            };
            Error(string error) => {
                status = "Parse error: " + error;
            };
        };
    }

    def handleFetchError(string error) : void {
        status = "Fetch error: " + error;
    }

    def loadUser() : void {
        status = "Loading...";
        FetchRequest.get(
            "https://api.example.com/user/1",
            &onSuccess = handleSuccess,
            &onError = handleFetchError
        );
    }

    view {
        <div>
            <button onclick={loadUser}>Load User</button>
            <p>{status}</p>
        </div>
    }
}

Example: Array Parsing

Parsing JSON arrays returns a vector of pod types with corresponding meta structs:

component ShowList {
    pod Show {
        string title;
        int id;
    }
    
    mut Show[] shows = [];
    mut string status = "Ready";
    
    mount {
        // Use template strings for clean JSON with interpolation
        string favShow = "Better Call Saul";
        string jsonData = `
        [
            {"title": "Breaking Bad", "id": 1},
            {"title": "{favShow}", "id": 2}
        ]`;

        match (Json.parse(Show[], jsonData)) {
            Success(Show[] parsedShows, ShowMeta[] metas) => {
                shows = parsedShows;
                status = "Loaded {parsedShows.length()} shows";
                // Each element has its own meta
                for (int i = 0; i < metas.length(); i++) {
                    if (metas[i].has_title()) {
                        // shows[i].title was present
                    }
                }
            };
            Error(string error) => {
                status = "Parse error: " + error;
            };
        };
    }
    
    view {
        <div>
            <p>{status}</p>
            <for show in shows>
                <div>{show.title} (ID: {show.id})</div>
            </for>
        </div>
    }
}

Supported Types

TypeJSONNotes
string"text"Handles escape sequences
int12332-bit signed integer
float3.1464-bit double
booltrue/false
Type{...}Nested pod types
string[][...]Array of strings
int[][...]Array of integers
Type[][...]Array of nested objects

Null Handling

JSON null values are handled gracefully - the field is simply not marked as present in the meta struct:

// JSON: {"name": "Alice", "age": null}
match (Json.parse(User, payload)) {
    Success(User u, Meta meta) => {
        meta.has(User.name);  // true
        meta.has(User.age);   // false (was null)
    };
    Error(string error) => {
        yield 0;
    };
}

WebSocket

Real-time bidirectional communication with WebSocket servers. The handle is automatically invalidated when the connection closes or errors.

Methods

MethodDescription
WebSocket.connect(url, &onMessage=..., &onOpen=..., &onClose=..., &onError=...)Create connection with callback handlers
send(string msg)Send message (only when connected)
close()Close connection
isConnected()Check if WebSocket is connected (handle is valid)

Callback Signatures

CallbackSignatureDescription
onMessagedef handler(string msg) : voidCalled when message received
onOpendef handler : voidCalled when connection opens
onClosedef handler : voidCalled when connection closes
onErrordef handler : voidCalled on connection error

Example

component Chat {
    mut WebSocket ws;
    mut string[] messages;
    mut bool connected = false;

    def handleMessage(string msg) : void {
        messages.push(msg);
    }

    def handleOpen() : void {
        connected = true;
        System.log("Connected!");
        ws.send("Hello from Coi!");
    }

    def handleClose() : void {
        connected = false;
        System.log("Disconnected");
    }

    mount {
        ws = WebSocket.connect(
            "wss://chat.example.com",
            &onMessage = handleMessage,
            &onOpen = handleOpen,
            &onClose = handleClose
        );
    }

    def sendMessage(string text) : void {
        if (connected) {
            ws.send(text);
        }
    }

    view {
        <div>
            <p>{connected ? "๐ŸŸข Connected" : "๐Ÿ”ด Disconnected"}</p>
            <for msg in messages key={msg}>
                <p>{msg}</p>
            </for>
        </div>
    }
}

Available APIs

ModuleDescription
Canvas2D drawing, paths, text, images, transformations
ImageImage loading for canvas rendering
AudioAudio playback, volume, looping, playback position
StorageLocal storage (setItem, removeItem, clear)
SystemLogging, page title, time, random, URL navigation
InputKeyboard input, pointer lock
DOMElementDirect DOM manipulation
WebGLWebGL context and rendering
WGPUWebGPU support
FetchRequestHTTP GET/POST requests
WebSocketWebSocket connections
JsonType-safe JSON parsing and serialization

Next Steps