npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@statedelta-apex/record-state

v3.0.0

Published

ApexStore Record primitive - typed key-value state with deep path access

Readme

@statedelta-apex/record-state

Record primitive — armazenamento key-value tipado com acesso deep path, transações aninhadas e imutabilidade por convenção.

Filosofia

State managers tradicionais (Redux, Zustand) armazenam objetos genéricos — o consumer escreve reducers pra mutar e selectors pra ler. O Record inverte isso: é um store completo e autossuficiente que já sabe como armazenar, mutar, fazer transaction/rollback, notificar listeners e serializar seu próprio estado.

O consumer só chama actions tipadas (set, merge, update) e getters tipados (get, pick, keys). Toda mecânica interna (shallow clone, strategy swap, middleware chain) é invisível.

import { createRecord } from "@statedelta-apex/record-state";

const user = createRecord("user", {
  name: "Anderson",
  age: 30,
  role: "admin",
});

user.set("name", "Rosa"); // type-safe: key e value validados via keyof T
user.update("age", (v) => v + 1); // functional update atômico
user.merge({ role: "superadmin" }); // batch: um clone, um notify

user.get("name"); // 'Rosa'
user.pick("name", "age"); // { name: 'Rosa', age: 31 }

Princípios

  • Determinístico — mesmas actions no mesmo estado produzem sempre o mesmo resultado.
  • Imutável por convenção — toda mutação gera uma nova referência via shallow clone. Referências anteriores permanecem intactas.
  • Autossuficiente — funciona standalone ou gerenciado por um Store. Transaction, subscribe, middleware, snapshot — tudo built-in.
  • Zero branches no hot path — o pipeline de mutação usa strategy swap. Quem não usa subscribe/middleware/hook não paga nada.
  • Type-safe — actions e getters top-level são tipados via keyof T. Deep path é via string[] com validação runtime.

Instalação

pnpm add @statedelta-apex/record-state

Requer @statedelta-apex/store como dependência (instalada automaticamente).

Quick Start

import { createRecord } from "@statedelta-apex/record-state";

// Criar
const config = createRecord("config", {
  volume: 80,
  difficulty: "hard",
  muted: false,
});

// Mutar
config.set("volume", 50);
config.merge({ muted: true, difficulty: "easy" });

// Ler
config.get("volume"); // 50
config.keys(); // ['volume', 'difficulty', 'muted']
config.pick("volume", "muted"); // { volume: 50, muted: true }

// Reagir
const unsub = config.subscribe((state, prev) => {
  console.log("config mudou:", state);
});

// Transacionar
config.transaction(() => {
  config.set("volume", 0);
  config.set("muted", false);
  // throw aqui → rollback ambos os sets
});

// Simular (what-if)
const result = config.simulate(() => {
  config.set("volume", 100);
  return config.get("volume");
});
// result === 100, config.get('volume') ainda 50

API

createRecord(id, initialData, config?)

Cria um Record primitive.

function createRecord<T extends Record<string, unknown>>(
  id: string,
  initialData: T,
  config?: { trackDeltas?: boolean },
): RecordPrimitive<T>;

| Parâmetro | Tipo | Descrição | | ------------- | ----------------- | ------------------------------------- | | id | string | Identificador único | | initialData | T | Estado inicial (clonado internamente) | | config | PrimitiveConfig | Configuração opcional |

const record = createRecord("settings", { volume: 80, theme: "dark" });

Actions (mutações)

Toda action produz uma nova referência via shallow clone e dispara o pipeline de mutação (hook → middleware → state → listeners). Uma action = um clone = um notify.

set(key, value)

Define o valor de uma key top-level. Type-safe: key é keyof T, value é T[K].

set<K extends keyof T>(key: K, value: T[K]): void
const user = createRecord("user", { name: "Anderson", age: 30 });

user.set("name", "Rosa");
user.set("age", 31);
// user.set('age', 'string')  // ← TS error: Type 'string' is not assignable to type 'number'
// user.set('invalid', 1)     // ← TS error: Argument of type '"invalid"' is not assignable

setPath(path, value)

Define valor em path aninhado. Path é string[] — sem parsing de dot-notation.

setPath(path: readonly string[], value: unknown): void
const player = createRecord("player", {
  stats: { hp: 100, mp: 50 },
  position: { x: 0, y: 0 },
});

player.setPath(["stats", "hp"], 70);
player.setPath(["position", "x"], 10);

// Path como const para reuso (zero alocação por frame)
const HP_PATH = ["stats", "hp"] as const;
player.setPath(HP_PATH, 50);

update(key, fn)

Functional update atômico. Recebe o valor atual e retorna o novo. Evita o padrão read-then-write.

update<K extends keyof T>(key: K, fn: (prev: T[K]) => T[K]): void
const counter = createRecord("counter", { count: 0, label: "clicks" });

// Atômico — sem read-then-write
counter.update("count", (v) => v + 1);
counter.update("count", (v) => v * 2);
counter.update("label", (v) => v.toUpperCase());

updatePath(path, fn)

Functional update em path aninhado.

updatePath(path: readonly string[], fn: (prev: unknown) => unknown): void
const game = createRecord("game", {
  stats: { hp: 100, mp: 50 },
});

game.updatePath(["stats", "hp"], (v) => (v as number) - 30);
game.updatePath(["stats", "mp"], (v) => (v as number) + 10);

merge(partial)

Shallow merge de múltiplas keys em uma única mutação. Uma clone, um notify.

merge(partial: Partial<T>): void
const settings = createRecord("settings", {
  volume: 80,
  brightness: 100,
  muted: false,
});

// 3 keys alteradas, 1 mutação
settings.merge({ volume: 50, muted: true, brightness: 75 });

delete(key)

Remove uma key top-level. O consumer assume responsabilidade de tipo — o objeto runtime pode não satisfazer T após a remoção.

delete<K extends keyof T>(key: K): void
const data = createRecord("data", { a: 1, b: 2, c: 3 });

data.delete("c");
data.has("c"); // false
data.keys(); // ['a', 'b']

deletePath(path)

Remove valor em path aninhado. Noop se o path não existe.

deletePath(path: readonly string[]): void
const config = createRecord("config", {
  features: { darkMode: true, animations: true, beta: false },
});

config.deletePath(["features", "beta"]);
config.hasPath(["features", "beta"]); // false
config.hasPath(["features", "darkMode"]); // true

replace(data)

Substitui o estado inteiro. O input é clonado internamente.

replace(data: T): void
const state = createRecord("state", { x: 1, y: 2 });

state.replace({ x: 99, y: 99 });
state.getState(); // { x: 99, y: 99 }

// Input é clonado — mutação externa não afeta o record
const newData = { x: 0, y: 0 };
state.replace(newData);
newData.x = 999;
state.get("x"); // 0 (não 999)

reset()

Restaura o estado inicial (passado na criação). Cada reset produz um clone independente.

reset(): void
const record = createRecord("hp", { value: 100, max: 100 });

record.set("value", 30);
record.get("value"); // 30

record.reset();
record.get("value"); // 100

Getters (leitura)

Getters são leituras puras. Sem side effects, sem mutações. Podem ser chamados em qualquer ordem, qualquer número de vezes.

get(key)

Retorna o valor de uma key top-level. Type-safe: retorno é T[K].

get<K extends keyof T>(key: K): T[K]
const user = createRecord("user", { name: "Anderson", age: 30 });

const name: string = user.get("name"); // 'Anderson'
const age: number = user.get("age"); // 30
// const x = user.get('invalid');         // ← TS error

getPath(path)

Retorna valor em path aninhado. Retorna undefined se qualquer nível intermediário não existe.

getPath(path: readonly string[]): unknown
const player = createRecord("player", {
  stats: { hp: 100, mp: 50 },
  inventory: [{ id: "sword" }, { id: "shield" }],
});

player.getPath(["stats", "hp"]); // 100
player.getPath(["inventory", "0", "id"]); // 'sword'
player.getPath(["stats", "nonexistent"]); // undefined
player.getPath(["x", "y", "z"]); // undefined

getOr(key, fallback)

Retorna o valor de uma key, ou o fallback se o valor é undefined.

getOr<K extends keyof T>(key: K, fallback: T[K]): T[K]
const config = createRecord<{ theme?: string; lang?: string }>("config", {});

config.getOr("theme", "light"); // 'light' (key não existe)
config.set("theme", "dark");
config.getOr("theme", "light"); // 'dark' (key existe)

pick(...keys)

Retorna um novo objeto com apenas as keys especificadas.

pick<K extends keyof T>(...keys: K[]): Pick<T, K>
const user = createRecord("user", {
  name: "Anderson",
  age: 30,
  role: "admin",
  email: "[email protected]",
});

user.pick("name", "email");
// { name: 'Anderson', email: '[email protected]' }

user.pick("age");
// { age: 30 }

omit(...keys)

Retorna um novo objeto sem as keys especificadas.

omit<K extends keyof T>(...keys: K[]): Omit<T, K>
const user = createRecord("user", {
  name: "Anderson",
  age: 30,
  password: "secret",
});

user.omit("password");
// { name: 'Anderson', age: 30 }

has(key)

Verifica se uma key top-level existe (via operador in).

has(key: string): boolean
const record = createRecord("r", { a: 1, b: undefined });

record.has("a"); // true
record.has("b"); // true (key existe, mesmo com valor undefined)
record.has("c"); // false

hasPath(path)

Verifica se um valor existe em path aninhado. Usa in no último nível — retorna true mesmo se o valor é undefined.

hasPath(path: readonly string[]): boolean
const data = createRecord("data", { a: { b: { c: 1 } } });

data.hasPath(["a", "b", "c"]); // true
data.hasPath(["a", "b", "x"]); // false
data.hasPath(["x", "y"]); // false
data.hasPath([]); // true

keys()

Retorna todas as keys top-level.

keys(): (keyof T)[]
const record = createRecord("r", { name: "A", age: 30, role: "admin" });
record.keys(); // ['name', 'age', 'role']

values()

Retorna todos os valores top-level.

values(): T[keyof T][]
const record = createRecord("r", { name: "A", age: 30 });
record.values(); // ['A', 30]

entries()

Retorna todas as entries top-level como [key, value][].

entries(): [keyof T, T[keyof T]][]
const record = createRecord("r", { name: "A", age: 30 });
record.entries(); // [['name', 'A'], ['age', 30]]

Properties

size

Número de keys top-level. Getter — avaliado sob demanda.

readonly size: number
const record = createRecord("r", { a: 1, b: 2, c: 3 });
record.size; // 3

isEmpty

true se o record não tem nenhuma key. Getter.

readonly isEmpty: boolean
const empty = createRecord("e", {});
empty.isEmpty; // true

const full = createRecord("f", { a: 1 });
full.isEmpty; // false

Estado

getState()

Retorna o estado completo. A referência é imutável por convenção — não modifique diretamente.

getState(): T
const record = createRecord("r", { a: 1, b: 2 });
const state = record.getState(); // { a: 1, b: 2 }

// Cada mutação gera uma nova referência
record.set("a", 99);
const newState = record.getState();
state !== newState; // true — referências diferentes
state.a; // 1 — original intacto
newState.a; // 99

id / type

Identificadores do primitivo.

readonly id: string    // ID passado na criação
readonly type: string  // 'record'

Transactions

O Record suporta transações aninhadas via stack de snapshots. Cada beginTransaction() empilha o estado atual. rollback() restaura, commitTransaction() descarta o snapshot.

API básica

const record = createRecord("hp", { value: 100, max: 100 });

record.beginTransaction();
record.set("value", 30);
record.get("value"); // 30

record.rollback();
record.get("value"); // 100 — restaurado
record.beginTransaction();
record.set("value", 50);
record.commitTransaction();
record.get("value"); // 50 — mantido

transaction(fn)

Commit automático no sucesso, rollback automático no throw.

transaction<R>(fn: () => R): R
const record = createRecord("data", { a: 1, b: 2, c: 3 });

// Sucesso → commit
const result = record.transaction(() => {
  record.set("a", 10);
  record.set("b", 20);
  return record.get("a") + record.get("b");
});
// result === 30, record.get('a') === 10

// Falha → rollback
try {
  record.transaction(() => {
    record.set("a", 999);
    throw new Error("abort");
  });
} catch {
  // ignorado
}
record.get("a"); // 10 — rollback restaurou

simulate(fn)

Sempre rollback. Para cenários "what if?" sem mutar estado.

simulate<R>(fn: () => R): R
const hp = createRecord("hp", { value: 100, min: 0 });

const wouldDie = hp.simulate(() => {
  hp.set("value", 0);
  return hp.get("value") <= hp.get("min");
});

wouldDie; // true
hp.get("value"); // 100 — intacto

Nesting

Transações suportam nesting arbitrário. Cada chamada abre um nível na stack.

const record = createRecord("state", { x: 1 });

record.transaction(() => {
  record.set("x", 10);

  // Simula dentro de transaction
  const safe = record.simulate(() => {
    record.set("x", -1);
    return record.get("x") >= 0;
  });
  // record.get('x') ainda 10

  if (!safe) throw new Error("invalid state");
});

inTransaction / transactionDepth

readonly inTransaction: boolean   // true se algum nível está ativo
readonly transactionDepth: number // 0 = sem transaction
const record = createRecord("r", { a: 1 });

record.inTransaction; // false
record.transactionDepth; // 0

record.beginTransaction();
record.inTransaction; // true
record.transactionDepth; // 1

record.beginTransaction();
record.transactionDepth; // 2

record.rollback();
record.transactionDepth; // 1

Subscribe (reatividade)

Registra um listener que é chamado a cada mutação. Recebe (state, prevState).

subscribe(listener: (state: T, prevState: T) => void): () => void

Retorna uma função de unsubscribe.

const record = createRecord("hp", { value: 100, max: 100 });

const unsub = record.subscribe((state, prev) => {
  console.log(`HP: ${prev.value} → ${state.value}`);
});

record.set("value", 70); // log: "HP: 100 → 70"
record.set("value", 50); // log: "HP: 70 → 50"

unsub();
record.set("value", 30); // sem log

prevState de graça

Como toda mutação é shallow clone, a referência anterior já existe em memória. O listener recebe prevState sem custo extra de cópia.

Múltiplos listeners

const a = record.subscribe((state) => console.log("A:", state.value));
const b = record.subscribe((state) => console.log("B:", state.value));

record.set("value", 42);
// log: "A: 42"
// log: "B: 42"

a(); // unsubscribe A
record.set("value", 0);
// log: "B: 0" (só B)

Zero overhead sem subscribers

Quando nenhum listener está registrado, o pipeline de mutação usa a strategy mutateBare (ou variante sem listeners). Sem iteração, sem checagem — puro state = newState.


Middleware

Interceptors de mutação. Podem observar, validar, bloquear ou transformar mutações.

use(middleware: (current: T, next: T, proceed: (override?: T) => void) => void): () => void

Retorna uma função de remoção.

| Chamada | Comportamento | | -------------------- | ----------------------------------------- | | proceed() | Aplica o valor original (next) | | proceed(override) | Substitui next pelo override e aplica | | sem chamar proceed | Bloqueia a mutação |

Logging

const record = createRecord("data", { count: 0 });

const remove = record.use((current, next, proceed) => {
  console.log("before:", current.count, "→", next.count);
  proceed();
  console.log("applied");
});

record.set("count", 1);
// log: "before: 0 → 1"
// log: "applied"

remove(); // remove middleware

Validação (bloquear mutação)

Não chamar proceed() bloqueia a mutação. O estado permanece inalterado e listeners não são notificados.

const hp = createRecord("hp", { value: 100, min: 0, max: 100 });

hp.use((current, next, proceed) => {
  if (next.value < next.min) return; // bloqueia
  if (next.value > next.max) return; // bloqueia
  proceed(); // permite
});

hp.set("value", 50); // permitido → value === 50
hp.set("value", -10); // bloqueado → value === 50
hp.set("value", 200); // bloqueado → value === 50

Normalização — proceed(override)

proceed(override) substitui o valor antes de aplicar. Para clamping, arredondamento, sanitização.

const hp = createRecord("hp", { value: 100, min: 0, max: 100 });

hp.use((_current, next, proceed) => {
  const clamped = Math.max(next.min, Math.min(next.max, next.value));
  if (clamped !== next.value) {
    proceed({ ...next, value: clamped }); // normaliza
  } else {
    proceed();
  }
});

hp.set("value", 200); // normalizado → value === 100
hp.set("value", -50); // normalizado → value === 0
hp.set("value", 50); // passthrough → value === 50

Chain de transformações

O override flui pela chain — cada middleware recebe o valor já transformado pelo anterior.

const stats = createRecord("stats", { hp: 100 });

// mw1: arredonda
stats.use((_cur, next, proceed) => {
  proceed({ ...next, hp: Math.round(next.hp) });
});

// mw2: clamp 0..100
stats.use((_cur, next, proceed) => {
  proceed({ ...next, hp: Math.max(0, Math.min(100, next.hp)) });
});

stats.set("hp", 150.7);
// mw1 recebe 150.7, passa 151 → mw2 recebe 151, passa 100 → aplica 100

stats.set("hp", -3.2);
// mw1 recebe -3.2, passa -3 → mw2 recebe -3, passa 0 → aplica 0

Diff capture (pós-proceed)

Middleware pode capturar diffs entre o estado anterior e o novo após proceed().

const record = createRecord("data", { count: 0 });

record.use((current, _next, proceed) => {
  proceed();
  const after = record.getState();
  saveDelta(current, after); // captura diff antes dos listeners
});

Cadeia de middleware

Múltiplos middleware executam em ordem de registro (FIFO). Cada um decide se chama proceed().

record.use((current, next, proceed) => {
  console.log("mw1 before");
  proceed();
  console.log("mw1 after");
});

record.use((current, next, proceed) => {
  console.log("mw2 before");
  proceed();
  console.log("mw2 after");
});

record.set("count", 1);
// "mw1 before"
// "mw2 before"
// "mw2 after"
// "mw1 after"

Middleware bloqueando listeners

Se middleware não chama proceed(), a mutação é bloqueada e listeners não são notificados.

const listener = vi.fn();
record.subscribe(listener);
record.use(() => {
  /* não chama proceed */
});

record.set("count", 1);
listener; // não chamado

Snapshot / Restore

Serialização e restauração de estado para persistência.

snapshot()

Retorna um objeto serializado com tipo, ID e dados.

snapshot(): PrimitiveSnapshot
// { type: 'record', id: string, data: unknown }
const record = createRecord("settings", { volume: 80, theme: "dark" });
const snap = record.snapshot();
// {
//   type: 'record',
//   id: 'settings',
//   data: { volume: 80, theme: 'dark' }
// }

// Persistir
localStorage.setItem("settings", JSON.stringify(snap));

restore(snapshot)

Restaura estado de um snapshot.

restore(snapshot: PrimitiveSnapshot): void
// Restaurar
const saved = JSON.parse(localStorage.getItem("settings")!);
record.restore(saved);
record.get("volume"); // 80

Store Integration

O Record funciona standalone por padrão. Quando registrado em um Store (orquestrador), o Store seta hooks internos para coordenar transações entre múltiplos primitivos.

// Standalone — sem Store
const hp = createRecord("hp", { value: 100 });
hp.set("value", 70); // funciona normalmente

// Com Store — coordenação entre primitivos
store.register(hp);
store.transaction(() => {
  hp.set("value", 50);
  board.setCell(0, 0, "X");
  // throw → rollback AMBOS
});

_onBeforeMutate / _updateStrategy()

Hooks internos usados pelo Store. O consumer não precisa usar diretamente.

_onBeforeMutate: (() => void) | null   // hook pré-mutação (lazy transaction)
_updateStrategy(): void                 // recompila pipeline de mutação

Extensão via Composição

O Record é um primitivo — ele cuida de storage, transaction, snapshot, reactivity. Tipos derivados compõem com o Record, adicionando actions/getters de domínio sem se preocupar com mecânica interna.

Exemplo: Counter

Um Counter é um Record { value, min, max, step } com actions de domínio (increment, decrement, clamp) e getters de domínio (percent).

import { createRecord } from "@statedelta-apex/record-state";
import type { RecordPrimitive } from "@statedelta-apex/record-state";

interface CounterState {
  value: number;
  min: number;
  max: number;
  step: number;
}

interface Counter extends RecordPrimitive<CounterState> {
  increment(amount?: number): void;
  decrement(amount?: number): void;
  clamp(): void;
  readonly percent: number;
}

function createCounter(
  id: string,
  initial: { value: number; min?: number; max?: number; step?: number },
): Counter {
  const data: CounterState = {
    value: initial.value,
    min: initial.min ?? -Infinity,
    max: initial.max ?? Infinity,
    step: initial.step ?? 1,
  };

  const record = createRecord<CounterState>(id, data);

  return {
    ...record,

    increment(amount?: number) {
      const state = record.getState();
      const delta = amount ?? state.step;
      record.set("value", Math.min(state.value + delta, state.max));
    },

    decrement(amount?: number) {
      const state = record.getState();
      const delta = amount ?? state.step;
      record.set("value", Math.max(state.value - delta, state.min));
    },

    clamp() {
      const state = record.getState();
      record.set(
        "value",
        Math.max(state.min, Math.min(state.value, state.max)),
      );
    },

    get percent() {
      const state = record.getState();
      if (state.max === state.min) return state.value >= state.max ? 1 : 0;
      return (state.value - state.min) / (state.max - state.min);
    },
  };
}
const hp = createCounter("hp", { value: 100, min: 0, max: 100 });

hp.get("value"); // 100 (getter do Record)
hp.percent; // 1.0 (getter do Counter)

hp.decrement(30);
hp.get("value"); // 70
hp.percent; // 0.7

hp.decrement(999);
hp.get("value"); // 0 (clamped pelo min)

// Transaction, subscribe, middleware — tudo funciona
hp.transaction(() => {
  hp.decrement(50);
  if (hp.get("value") <= 0) throw new Error("would die");
});

hp.subscribe((state, prev) => {
  console.log(`HP: ${prev.value} → ${state.value}`);
});

// Snapshot inclui tudo
hp.snapshot();
// { type: 'record', id: 'hp', data: { value: 0, min: 0, max: 100, step: 1 } }

Exemplo: AppSettings

Um derivado que adiciona validação e defaults sobre um Record.

interface Settings {
  locale: string;
  timezone: string;
  notifications: boolean;
  maxRetries: number;
}

const DEFAULTS: Settings = {
  locale: "pt-BR",
  timezone: "America/Sao_Paulo",
  notifications: true,
  maxRetries: 3,
};

function createAppSettings(id: string, overrides?: Partial<Settings>) {
  const record = createRecord<Settings>(id, { ...DEFAULTS, ...overrides });

  // Middleware de validação
  record.use((_current, next, proceed) => {
    if (next.maxRetries < 0 || next.maxRetries > 10) return; // bloqueia
    if (!next.locale || !next.timezone) return; // bloqueia
    proceed();
  });

  return {
    ...record,

    resetToDefaults() {
      record.replace(DEFAULTS);
    },

    isDefault<K extends keyof Settings>(key: K): boolean {
      return record.get(key) === DEFAULTS[key];
    },
  };
}
const settings = createAppSettings("app-settings");

settings.set("maxRetries", 5); // permitido
settings.set("maxRetries", -1); // bloqueado pelo middleware
settings.get("maxRetries"); // 5

settings.isDefault("locale"); // true
settings.set("locale", "en-US");
settings.isDefault("locale"); // false

settings.resetToDefaults();
settings.isDefault("locale"); // true

O padrão

O derivado:

  1. Cria um Record internamente
  2. Adiciona actions/getters de domínio
  3. Registra middleware se precisa de validação
  4. Expõe tudo via spread (...record) + métodos adicionais

O derivado não sabe como transaction funciona, como snapshot serializa, como subscribe notifica. O Record cuida de tudo.


Configuração

trackDeltas

Habilita rastreamento de diffs a cada mutação. Default: false.

// Performance mode (default) — zero overhead
const fast = createRecord("fast", { x: 1 });

// Audit mode — rastreia diffs
const audited = createRecord("audited", { x: 1 }, { trackDeltas: true });

| Modo | Overhead | Uso | | --------------------- | ---------------- | ---------------------------- | | Performance (default) | Zero | Games, FPS, simulações | | Audit | Diff por mutação | Undo/redo, histórico, replay |


Tipos Exportados

import type {
  RecordPrimitive,
  RecordConfig,
} from "@statedelta-apex/record-state";
import { createRecord } from "@statedelta-apex/record-state";

| Export | Tipo | Descrição | | -------------------- | ---------- | --------------------------------- | | createRecord | function | Factory principal | | RecordPrimitive<T> | interface | Tipo do retorno de createRecord | | RecordConfig | type alias | Alias para PrimitiveConfig |