Skip to main content
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.
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:
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:
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:
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.
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 });
Never enable devMode in production. Model instances exposed through the ready event give anyone with console access direct visibility into your internal state.

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:
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.
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.