basepower

Lightweight EventEmitter & Serializable primitives for TypeScript

Installation

npm install basepower
import { EventEmitter, Serializable } from 'basepower';

EventEmitter

A minimal event system.

Constructor

const emitter = new EventEmitter();

on(event, callback)

Register a listener for an event.

emitter.on('data', (value) => {
  console.log(value);
});

once(event, callback)

Register a listener that fires only once.

emitter.once('init', () => {
  console.log('initialized');
});

off(event, callback?)

Remove a specific listener, or all listeners for an event if no callback is provided.

emitter.off('data', myHandler); // remove specific
emitter.off('data');             // remove all

emit(event, args?)

Emit an event. Arguments are spread to each listener.

emitter.emit('data', [42, 'hello']);

hasEvent(event)

Returns true if there are listeners for the given event.

emitter.hasEvent('data'); // boolean

Interactive Demo


      

Serializable

A serialization framework that extends EventEmitter. Define fields with getter/setter pairs, then serialize and deserialize them.

Constructor

const obj = new Serializable();
console.log(obj.uuid); // auto-generated UUID

field(path, getter, setter?, opt?)

Register a field. If no setter is provided, the field is read-only. Use { export: false } to exclude from serialize().

let hp = 100;
obj.field('hp', () => hp, (v) => { hp = v; });

// read-only field (still included in serialize)
obj.field('name', () => 'Player1');

// excluded from serialize output
obj.field('debugVal', () => val, (v) => { val = v; }, { export: false });

fieldDir(name, opt?)

Create a group for organizing fields hierarchically.

const stats = obj.fieldDir('stats');
stats.field('hp', () => hp, (v) => { hp = v; });
// accessible as 'stats/hp'

getField(path)

Get the current value of a field.

obj.getField('hp'); // 100

setField(path, value)

Set the value of a field. Emits fields/update and fields/update/<path> events.

obj.setField('hp', 50);

serialize()

Serialize fields to a flat key-value object. Fields with export: false are excluded.

const data = obj.serialize();
// { hp: 100, name: 'Player1' }

deserialize(props)

Restore field values from a serialized object.

obj.deserialize({ hp: 75 });

getSchema()

Get all fields as a nested tree structure based on /-separated paths. Includes all fields regardless of export setting.

const tree = obj.getSchema();
// { type: 'group', childs: { stats: { type: 'group', childs: { hp: { type: 'field', value: 100 } } } } }

Interactive Demo


      

React Integration

Hooks for binding Serializable fields to React components. Install with basepower and import from basepower/react.

import { useSerializableField, useWatchSerializable } from 'basepower/react';

useSerializableField(serializable, fieldPath)

Returns a [value, setter] tuple that stays in sync with a single field. The component re-renders only when that specific field changes.

function HpEditor({ obj }: { obj: Serializable }) {
  const [hp, setHp] = useSerializableField<number>(obj, 'stats/hp');

  return <input
    type="range" min={0} max={200}
    value={hp ?? 0}
    onChange={(e) => setHp(Number(e.target.value))}
  />;
}

useWatchSerializable(serializable, deps?)

Returns all serialized fields and re-renders when any (or specified) field changes. Pass a deps array to limit which fields trigger re-renders.

// re-render on ANY field change
const allFields = useWatchSerializable(obj);

// re-render only when 'stats/hp' changes
const fields = useWatchSerializable(obj, ['stats/hp']);

Interactive Demo

Edit the fields below — both serialized views update reactively. The right panel only watches stats/hp, so it re-renders only when HP changes.

Type Reference

SelectList

type SelectList = ({ value: any; label: string } | string)[]

ValueOpt

type ValueOpt = {
  label?: string;
  readOnly?: boolean;
  step?: number;
  disabled?: boolean;
}

FieldOpt

type FieldOpt = {
  isFolder?: boolean;
  export?: boolean;
  hidden?: boolean | ((value: FieldValue) => boolean);
  format?: FieldFormat;
} & ValueOpt

FieldFormat

type FieldFormat =
  | { type: 'vector' }
  | { type: 'select'; list: SelectList | (() => SelectList) }
  | { type: 'array'; labels?: (value: FieldValue, index: number) => string }

SerializedFields

interface SerializedFields {
  [key: string]: FieldValue
}

SchemaNode

type SchemaNode = SchemaGroup | SchemaField

interface SchemaGroup {
  type: 'group';
  childs: { [key: string]: SchemaNode };
  opt?: FieldOpt;
}

interface SchemaField {
  type: 'field';
  value: FieldValue;
  opt?: FieldOpt;
}