Skip to main content
The Sprincul class is the entry point for the framework. All methods are static — you never instantiate it directly. You use it to register your model classes, run initialization, listen for the ready event, and interact with the global key/value store. Everything must be registered before you call Sprincul.init().

Registration

Sprincul.register(name, modelClass)

Registers a single model class under a name. The name you provide here must match the value you set in data-model attributes in your HTML. Call this before Sprincul.init().
name
string
required
The identifier for this model. Must match the data-model attribute value in your HTML.
modelClass
SprinculModelConstructor
required
A class that extends SprinculModel. Sprincul calls new modelClass(element) for each matching element it finds.
import { Sprincul } from 'sprincul';
import Counter from './Counter.js';

Sprincul.register('Counter', Counter);
Sprincul.init();

Sprincul.registerAll(models)

Registers multiple model classes at once from a plain object. Each key becomes the data-model name for that class. Use this when you have several models to register — it keeps your entry point tidy.
models
Record<string, SprinculModelConstructor>
required
An object whose keys are model names and whose values are classes that extend SprinculModel.
import { Sprincul } from 'sprincul';
import Counter from './Counter.js';
import UserProfile from './UserProfile.js';
import ShoppingCart from './ShoppingCart.js';

Sprincul.registerAll({
  Counter,
  UserProfile,
  ShoppingCart,
});

Sprincul.init();
The object keys are used verbatim as the model names. If you name the key Counter, your HTML must use data-model="Counter" — casing matters.

Initialization

Sprincul.init(options?)

Scans the DOM for all elements with a [data-model] attribute, creates a model instance for each one, wires up bindings and event listeners, and runs the lifecycle hooks (beforeInit, then afterInit) on each instance. Page-level data-cloaked elements (those not on a data-model element itself) are uncloaked immediately after all afterInit hooks are called. Calling init() more than once is safe — elements that have already been initialized are skipped. This means you can inject new markup and call init() again to hydrate only the new roots.
options
object
Optional configuration object.
options.devMode
boolean
default:"false"
When true, Sprincul logs console warnings for misconfigured bindings and handlers, and includes the live model instance on each SprinculModelInfo object in the ready callback and sprincul:ready event. Disable in production — exposing instances allows console access to internal state.
// Production
Sprincul.init();

// Development
Sprincul.init({ devMode: true });
// Hydrating dynamically injected markup
document.body.insertAdjacentHTML('beforeend', '<div data-model="Counter">...</div>');
Sprincul.init(); // already-initialized roots are skipped
Model registrations and onReady callbacks must be set up before calling Sprincul.init(). Anything registered after init() runs will not be picked up in that cycle.

Sprincul.onReady(callback)

Registers a function to be called once all models’ afterInit hooks have been invoked. Use this to run logic that depends on the entire page being wired up — for example, reading initial model state or connecting cross-model integrations. onReady callbacks are one-shot per init cycle. After they fire, the internal list is cleared. If you call Sprincul.init() again (for example, after injecting new markup), you must re-register any callbacks you want to run in the new cycle.
callback
(models: SprinculModelInfo[]) => void
required
A function that receives an array of SprinculModelInfo objects — one for each model root that was initialized in this cycle.
import { Sprincul } from 'sprincul';
import Counter from './Counter.js';

Sprincul.register('Counter', Counter);

Sprincul.onReady((models) => {
  console.log(`Initialized ${models.length} model(s)`);
  models.forEach(({ name, element }) => {
    console.log(`Model "${name}" on:`, element);
  });
});

Sprincul.init();
In devMode, each item in the array also contains instance:
Sprincul.onReady((models) => {
  models.forEach(({ name, element, instance }) => {
    console.log(`"${name}" instance:`, instance);
  });
});

Sprincul.init({ devMode: true });
You can also listen for the sprincul:ready DOM event directly if you prefer not to use the helper:
document.addEventListener('sprincul:ready', ({ detail }) => {
  const { models } = detail;
  console.log(`Initialized ${models.length} model(s)`);
});

Global store

Sprincul.store is a lightweight, reactive key/value store shared across all model instances. You can seed it before init(), read it from inside models, and subscribe to changes anywhere on the page. It uses nanostores atoms under the hood, so updates are efficient and subscription callbacks fire synchronously after each change.

Sprincul.store.get<T>(key)

Returns the current value for a key, or undefined if no value has been set.
key
string
required
The store key to read.
const theme = Sprincul.store.get('theme'); // 'dark' | undefined

Sprincul.store.set<T>(key, value)

Sets a value for a key. Creates the entry if it does not exist yet. All active subscribers for this key are notified immediately.
key
string
required
The store key to write.
value
T
required
The value to store. Can be any serializable type.
Sprincul.store.set('theme', 'dark');
Sprincul.store.set('currentUser', { id: 1, name: 'Jane Doe' });

Sprincul.store.subscribe<T>(key, callback)

Subscribes to changes for a key. The callback fires after a value is set — it does not fire with the current value at subscription time. To read the current value immediately, call store.get() first or seed a default with store.set(). Returns an unsubscribe function. Call it to stop receiving updates.
key
string
required
The store key to watch.
callback
(value: T | undefined) => void
required
Called with the new value whenever the key is updated. Receives undefined if the key is cleared.
// Read current value first, then subscribe to future changes
const currentTheme = Sprincul.store.get('theme') ?? 'light';
applyTheme(currentTheme);

const unsubscribe = Sprincul.store.subscribe('theme', (value) => {
  applyTheme(value ?? 'light');
});

// Later, when you no longer need updates:
unsubscribe();

Sprincul.store.clear()

Removes all keys and their subscribers from the store. Useful in tests or when tearing down a page section entirely.
Sprincul.store.clear();

Exported types

These types are exported from the sprincul package and available for use in TypeScript projects.

SprinculModelInfo

Describes a single model instance returned by onReady callbacks and the sprincul:ready event.
name
string
required
The model name as registered with Sprincul.register() or Sprincul.registerAll().
element
HTMLElement
required
The DOM element marked with data-model.
instance
SprinculModel
The live model instance. Only present when devMode: true is passed to Sprincul.init().
import type { SprinculModelInfo } from 'sprincul';

Sprincul.onReady((models: SprinculModelInfo[]) => {
  models.forEach(({ name, element, instance }) => {
    console.log(name, element, instance);
  });
});

SprinculModelConstructor

The constructor signature that Sprincul.register() and Sprincul.registerAll() expect. Any class that extends SprinculModel satisfies this type automatically.
import type { SprinculModelConstructor } from 'sprincul';

// Equivalent to: new (element: HTMLElement) => SprinculModel
const registry: Record<string, SprinculModelConstructor> = {
  Counter,
  Profile,
};

Sprincul.registerAll(registry);