> ## Documentation Index
> Fetch the complete documentation index at: https://sprincul.sinfullycoded.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript Support and Typed State in Sprincul Models

> Sprincul ships TypeScript types out of the box. Extend SprinculModel with a typed state interface to get full autocomplete and type safety.

Sprincul is written in TypeScript and ships its type declarations alongside the package. You do not need to install a separate `@types/` package. Whether you use TypeScript directly or write plain JavaScript in an editor that understands JSDoc, you can get full type safety and autocomplete for your models.

## Typing your model's state

By default, `SprinculModel` declares `state` as `Record<string, any>`. To get typed access to your state properties, define an interface for your state shape and declare `state!` with that type on your model class. The `!` is a TypeScript definite assignment assertion — it tells the compiler that `state` is assigned by the parent constructor.

```ts theme={null}
import { SprinculModel } from 'sprincul';

interface MyState {
  count: number;
  name: string;
}

class MyModel extends SprinculModel {
  state!: MyState;

  beforeInit() {
    this.state.count = 0;
    this.state.name = 'Hello World';
  }

  afterInit() {
    // Optional lifecycle hook
  }
}
```

With `state!: MyState` in place, TypeScript will catch typos in property names, flag incorrect value types, and provide autocomplete when you type `this.state.`.

## Exported types

Sprincul exports two utility types you can import alongside the main classes.

### `SprinculModelInfo`

Describes the object Sprincul passes to `onReady` callbacks and the `sprincul:ready` event. Its shape is:

```ts theme={null}
interface SprinculModelInfo {
  name: string;
  element: HTMLElement;
  instance?: SprinculModel; // only present when devMode is enabled
}
```

Use this type when you write an `onReady` callback and want the argument typed explicitly:

```ts theme={null}
import { Sprincul } from 'sprincul';
import type { SprinculModelInfo } from 'sprincul';

Sprincul.onReady((models: SprinculModelInfo[]) => {
  models.forEach(({ name, element }) => {
    console.log(`Model "${name}" mounted on`, element);
  });
});

Sprincul.init();
```

### `SprinculModelConstructor`

The constructor signature that `Sprincul.register` and `Sprincul.registerAll` expect. You rarely need to reference this directly, but it is useful when building abstractions around model registration:

```ts theme={null}
import { Sprincul } from 'sprincul';
import type { SprinculModelConstructor } from 'sprincul';

function registerModels(map: Record<string, SprinculModelConstructor>) {
  Sprincul.registerAll(map);
}
```

## `instance` in `onReady` callbacks

When you call `Sprincul.init({ devMode: true })`, each `SprinculModelInfo` object in the ready callback also includes an `instance` property — the live model instance. This gives you direct access to the object for debugging in the browser console.

```ts theme={null}
import { Sprincul } from 'sprincul';
import type { SprinculModelInfo } from 'sprincul';

Sprincul.onReady((models: SprinculModelInfo[]) => {
  models.forEach(({ name, element, instance }) => {
    // instance is defined because devMode: true
    console.log(name, instance);
  });
});

Sprincul.init({ devMode: true });
```

<Warning>
  Never enable `devMode` in production. Model instances exposed through the ready event give anyone with console access direct visibility into your internal state.
</Warning>

## IntelliSense for JavaScript users

If you write plain JavaScript and do not want to set up a TypeScript build step, you can still get IntelliSense and parameter hints in editors like VS Code by using JSDoc annotations. Sprincul's own examples use this pattern:

```js theme={null}
import { SprinculModel } from 'sprincul';

export default class Counter extends SprinculModel {
  beforeInit() {
    this.state.count = 0;
  }

  /** @param {HTMLElement} el */
  showCount(el) {
    el.value = this.state.count;
  }

  /** @param {InputEvent} e */
  handleInput(e) {
    this.state.count = Number(e.target.value);
  }
}
```

The `@param` annotations tell your editor the type of each argument, so you get autocomplete on `el` and `e` even without a TypeScript compiler.

<Tip>
  You can add a `// @ts-check` comment at the top of any `.js` file to enable TypeScript's type checker in JavaScript files. Combined with JSDoc annotations, this gives you many of the same safety benefits without a build step.
</Tip>
