Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Basic D1 Backed Model

In this section, we will explore the basic properties of a D1 backed Model in Cloesce. Cloudflare D1 is a serverless SQL database built on SQLite for Workers.

Defining a Model

Note

Models do not have constructors as they should not be manually instantiated. Instead, use the ORM functions to create, retrieve, and update Model instances. For tests, consider using Object.assign() to create instances of Models with specific property values.

Tip

Using the @PrimaryKey decorator is optional if your primary key property is named id or <className>Id (in any casing, i.e., snake case, camel case, etc). Cloesce will automatically treat a property named id as the primary key.

Compilation in Cloesce consists of three phases: Extraction, Analysis, and Code Generation.

During Extraction, Cloesce scans your source files (designated with *.cloesce.ts) for Model definitions. Models are defined using the @Model() decorator.

import { Model, Integer, PrimaryKey } from "cloesce/backend";

@Model("db")
export class User {
    @PrimaryKey
    id: Integer;

    name: string;
}

The above code defines a Model “User” stored in the D1 database db, with several properties:

PropertyDescription
UserCloesce infers from the class attributes that this Model is backed by a D1 table User
idInteger property decorated with @PrimaryKey, indicating it is the Model’s primary key.
nameString property representing the user’s name; stored as a regular column in the D1 database.

Supported D1 Column Types

Cloesce supports a variety of column types for D1 Models. These are the supported TypeScript types and their corresponding SQLite types:

TypeScript TypeSQLite TypeNotes
IntegerINTEGERRepresents an integer value
stringTEXTRepresents a string value
booleanINTEGER0 for false, 1 for true
DateTEXTStored in ISO 8601 format
numberREALRepresents a floating-point number
Uint8ArrayBLOBRepresents binary data

All of these types by themselves are NOT NULL by default. To make a property nullable, you can use a union with null, e.g., property: string | null;. undefined is reserved for Navigation Properties and cannot be used to indicate nullability.

Notably, an Integer primary key is automatically set to AUTOINCREMENT in D1, so you don’t need to manually assign values to it when creating new records (useful for the ORM functions).

Fluent API

Some column configurations cannot be cleanly expressed through TypeScript decorators alone. For these cases, Cloesce provides a Fluent API that can called in cloesce.config.ts to further customize the D1 schema. For example, to make a column unique:

import { defineConfig } from "cloesce/config";
import { Weather } from "./src/data/models.cloesce";

const config = defineConfig({
    // ...
});

config.model(Weather, builder => {
    builder.unique("dateTime", "location");
});

Additionally, Cloesce exposes a method to modify the AST after extraction:

config.rawAst((ast) => {
    // modify the raw AST here
});

Migrating the Database

Important

Any change in a D1 backed Model definition (adding, removing, or modifying properties; renaming Models) requires a new migration to be created.

The migration command will generate a new migration file in the migrations/ directory.

The standard Cloesce compilation command does not perform database migrations. To create or update the D1 database schema based on your Model definitions, you need to run the migration command:

npx cloesce compile # load the latest Model definitions
npx cloesce migrate <d1-binding> <migration name>

Finally, these generated migrations must be applied to the actual D1 database using the Wrangler CLI:

npx wrangler d1 migrations apply <d1-binding>