@statedelta/core
v0.2.0
Published
Core interfaces and types for StateDelta packages
Downloads
204
Maintainers
Readme
@statedelta/core
Core interfaces and types for StateDelta packages.
Overview
Provides:
- IEngine - Minimal deterministic engine contract for Assembler/Persist
- Traceable/IStore/ISession - Base interfaces for stores
- Capabilities - Registry for providers, middlewares, plugins
- Document Protocol - Header/entry format for state documents
Installation
pnpm add @statedelta/coreIEngine (Deterministic Contract)
Minimal interface that any engine must implement to work with Assembler.
Core principle: Effects are the ONLY external input. Same Effects + Same Tick = Same State.
interface IEngine {
// State
readonly tick: number;
readonly locked: boolean;
// Effects (determinism)
applyEffect(type: string, payload: unknown): void; // Gameplay - SAVES
recoverEffect(type: string, payload: unknown): void; // Replay - NOT saved
getEffects(): Effect[];
clearEffects(): void;
// Execution
step(): StepResult;
// Snapshot
toSnapshot(): IEngineSnapshot;
recovery(snapshot: IEngineSnapshot): void;
// Lifecycle
reset(): void;
destroy(): void;
}Effect
External input that changes the deterministic world (the "butterfly"):
interface Effect {
tick: number; // When applied
type: string; // 'player:move', 'seed', 'timestamp'
payload: unknown; // Data
ts: number; // Timestamp
}Recovery Flow
// Gameplay - effects are SAVED
engine.applyEffect('seed', 12345);
engine.applyEffect('player:move', { dx: 1, dy: 0 });
engine.step();
// Recovery - effects are NOT saved (already in replay)
for (const effect of snapshot.effects) {
engine.recoverEffect(effect.type, effect.payload);
while (engine.tick < effect.tick && !engine.locked) {
engine.step();
}
}Traceable
Base interface for all runtime-addable entities.
interface Traceable {
id: string;
creator: string; // Who created (immutable, audit)
owner: string; // Who controls (mutable, ACL)
createdAt?: string;
lifetime?: Lifetime; // 'eternal' | 'session' | 'stage' | 'step' | '$signal'
enabled?: boolean | '$signal';
namespace?: string; // 'game.combat', 'shop.cart'
tags?: string[];
description?: string;
}IStore
Base interface for all stores with consistent CRUD and queries.
interface IStore<T extends Traceable> {
// CRUD
add(entity: T): T;
addMany(entities: T[]): T[];
get(id: string): T | undefined;
has(id: string): boolean;
all(): T[];
size: number;
update(id: string, updates: Partial<T>): T;
remove(id: string): boolean;
removeMany(ids: string[]): number;
clear(): void;
// Queries O(1)
getByOwner(owner: string): T[];
getByCreator(creator: string): T[];
getByLifetime(lifetime: Lifetime): T[];
getByNamespace(pattern: string): T[]; // supports glob
getByTags(tags: string[], mode?: 'all' | 'any'): T[];
query(q: BaseQuery): T[];
// Lifecycle
cleanup(lifetime: Lifetime): number;
cleanupAll(): number;
enable(id: string): boolean;
disable(id: string): boolean;
// Session
createSession(options?: SessionOptions): ISession<T>;
}ISession
Scoped view of a store for specific contexts.
interface ISession<T extends Traceable> {
id: string;
// Read (from parent + temporary)
get(id: string): T | undefined;
has(id: string): boolean;
all(): T[];
size: number;
// Queries (on session indexes)
getByOwner(owner: string): T[];
getByNamespace(pattern: string): T[];
query(q: BaseQuery): T[];
// Temporary entities
addTemporary(entity: T, lifetime?: Lifetime): T;
removeTemporary(id: string): boolean;
isTemporary(id: string): boolean;
// Lifecycle
cleanup(lifetime: Lifetime): number;
destroy(): void;
}EventEmitter
Type-safe event emitter used by all stores.
class MyStore extends EventEmitter<StoreEvents<MyEntity>> {
add(entity: MyEntity) {
// ...
this.emit('added', entity);
}
}Patch & DispatchResult
JSON Patch (RFC 6902) types for state mutations.
interface Patch {
op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test';
path: string;
value?: unknown;
from?: string;
}
interface DispatchResult<T = unknown> {
success: boolean;
value?: T;
patches?: Patch[];
events?: DomainEvent[];
error?: string;
}Query Helpers
// Namespace matching (supports glob)
matchesNamespace('game.combat', 'game.*') // true
matchesNamespace('game.combat.damage', 'game.**') // true
// Tag matching
matchesTags(['a', 'b'], { $all: ['a'] }) // true
matchesTags(['a', 'b'], { $any: ['c'] }) // false
matchesTags(['a'], { $none: ['b'] }) // trueLicense
MIT
