@statedelta-apex/list-state
v3.0.0
Published
ApexStore List primitive - ordered list state with dual-end access (queue/stack)
Maintainers
Readme
@statedelta-apex/list-state
List primitive — lista ordenada tipada sem semântica de identidade, acesso dual-end, capacidade opcional, transações aninhadas e imutabilidade por convenção.
Filosofia
O CollectionState gerencia entidades com identidade — cada item tem um ID único, operações são ID-based, duplicatas são proibidas. Perfeito pra users, products, records.
O ListState gerencia valores posicionais — qualquer tipo T, sem restrição de identidade, duplicatas válidas. A ordem é o que importa, não quem é o item. Perfeito pra filas, pilhas, buffers, logs, histórico.
import { createList } from "@statedelta-apex/list-state";
// Strings, números, objetos sem ID, duplicatas — tudo válido
const tasks = createList<string>("tasks", ["process", "validate", "process"]);
const scores = createList<number>("scores", [100, 85, 92]);
const events = createList<{ type: string; data: unknown }>("events", []);O ListState é agnóstico de padrão de acesso. Expõe ambas as pontas (append/prepend, removeFirst/removeLast) sem impor FIFO ou LIFO. O consumer decide como usar — ou compõe um derived type (Queue, Stack) pra semântica explícita.
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.
- Sem identidade — items são valores posicionais. Duplicatas válidas, primitivos aceitos, sem overhead de ID tracking.
Instalação
pnpm add @statedelta-apex/list-stateRequer @statedelta-apex/store como dependência (instalada automaticamente).
Quick Start
import { createList } from "@statedelta-apex/list-state";
// Criar
const queue = createList<string>("tasks", ["task-A", "task-B"]);
// Mutar
queue.append("task-C");
queue.prepend("urgent-task");
const next = queue.removeFirst(); // "urgent-task"
// Ler
queue.peekFirst(); // "task-A"
queue.peekLast(); // "task-C"
queue.size; // 3
// Reagir
const unsub = queue.subscribe((items, prevItems) => {
console.log(`Queue: ${prevItems.length} → ${items.length}`);
});
// Transacionar
queue.transaction(() => {
queue.append("step-1");
queue.append("step-2");
// throw aqui → rollback ambos
});
// Simular (what-if)
const wouldOverflow = queue.simulate(() => {
queue.append("big-payload");
return queue.size > MAX_QUEUE;
});
// queue intactoCriação
createList(id, initialItems, config?)
function createList<T>(
id: string,
initialItems: T[],
config?: ListConfig,
): ListState<T>;| Parâmetro | Tipo | Descrição |
| -------------- | ------------ | -------------------------------------- |
| id | string | Identificador único do state |
| initialItems | T[] | Items iniciais (clonados internamente) |
| config | ListConfig | Configuração opcional |
// Default — sem limite de tamanho
const log = createList<Event>("log", []);
// Com capacidade máxima
const buffer = createList<Message>("buffer", [], { maxSize: 100 });
// Strict mode
const safe = createList<string>("safe", [], { strict: true });Também disponível via new:
import { ListState } from "@statedelta-apex/list-state";
const list = new ListState<string>("tasks", ["a", "b"]);Actions (mutações)
Toda action produz uma nova referência do array e dispara o pipeline de mutação (hook → middleware → state → listeners). Uma action = um clone = um notify.
append(item)
Adiciona um item no final.
append(item: T): voidqueue.append("task-C");
// ["task-A", "task-B"] → ["task-A", "task-B", "task-C"]appendMany(items)
Batch append — N items numa única mutação. Um clone, um notify. Noop se array vazio.
appendMany(items: T[]): voidqueue.appendMany(["task-D", "task-E"]);prepend(item)
Adiciona um item no início.
prepend(item: T): voidqueue.prepend("urgent");
// ["task-A", "task-B"] → ["urgent", "task-A", "task-B"]prependMany(items)
Batch prepend — N items no início numa única mutação. Noop se array vazio.
prependMany(items: T[]): voidremoveFirst()
Remove e retorna o primeiro item. Retorna undefined se vazio (sem mutação).
removeFirst(): T | undefinedconst next = queue.removeFirst(); // "task-A"
// ["task-A", "task-B", "task-C"] → ["task-B", "task-C"]removeLast()
Remove e retorna o último item. Retorna undefined se vazio (sem mutação).
removeLast(): T | undefinedconst last = queue.removeLast(); // "task-C"clear()
Remove todos os items. Noop se já está vazio.
clear(): voidreplace(items)
Substitui todos os items. Input é clonado internamente. Valida contra maxSize.
replace(items: T[]): voidqueue.replace(["new-A", "new-B"]);reset()
Restaura os items iniciais (passados na criação). Cada reset produz um clone independente.
reset(): voidGetters (leitura)
Getters são leituras puras. Sem side effects, sem mutações.
peekFirst() / peekLast()
Retorna o primeiro/último item sem remover. undefined se vazio.
peekFirst(): T | undefined
peekLast(): T | undefinedqueue.peekFirst(); // olha o início sem tocar
queue.peekLast(); // olha o final sem tocarat(index)
Acesso posicional. Suporta índices negativos (-1 = último).
at(index: number): T | undefinedqueue.at(0); // primeiro
queue.at(-1); // último
queue.at(999); // undefinedfind(predicate)
Primeiro item que satisfaz o predicado.
find(predicate: (item: T, index: number) => boolean): T | undefinedconst urgent = queue.find((task) => task.startsWith("urgent"));findIndex(predicate)
Índice do primeiro item que satisfaz o predicado. -1 se não encontrado.
findIndex(predicate: (item: T, index: number) => boolean): numberincludes(item)
Verifica se o item existe na lista (por referência/valor).
includes(item: T): booleanindexOf(item)
Índice do item na lista. -1 se não encontrado.
indexOf(item: T): numberevery(predicate)
true se todos os items satisfazem o predicado.
every(predicate: (item: T, index: number) => boolean): booleansome(predicate)
true se pelo menos um item satisfaz o predicado.
some(predicate: (item: T, index: number) => boolean): booleantoArray()
Retorna cópia do array (spread). Seguro pra mutar.
toArray(): T[]const copy = queue.toArray();
copy.push("extra"); // não afeta o stateProperties
| Property | Tipo | Descrição |
| --------- | --------- | -------------------------------------------------- |
| items | T[] | Referência direta ao array (alias de getState()) |
| size | number | Número de items |
| isEmpty | boolean | true se zero items |
| isFull | boolean | true se size >= maxSize |
| maxSize | number | Capacidade máxima (Infinity = sem limite) |
| id | string | ID passado na criação |
| type | string | "list" |
Estado
getState()
Retorna o array completo. A referência é imutável por convenção — não modifique diretamente.
getState(): T[]const items = queue.getState(); // ["task-A", "task-B"]
queue.append("task-C");
const newItems = queue.getState();
items !== newItems; // true — referências diferentes
items.length; // 2 — original intactoCapacidade (maxSize)
Limite opcional de items. Default: Infinity (sem limite).
const buffer = createList<string>("buffer", [], { maxSize: 5 });
buffer.append("a");
buffer.append("b");
buffer.append("c");
buffer.append("d");
buffer.append("e");
buffer.isFull; // true
buffer.append("f"); // ← throw RangeError: would exceed maxSizeO maxSize é validado em todas as operações que aumentam o tamanho (append, appendMany, prepend, prependMany, replace, restore). Operações que reduzem ou mantêm o tamanho nunca falham por capacidade.
Transactions
A List suporta transações aninhadas via stack de snapshots. Cada beginTransaction() empilha o estado atual. rollback() restaura, commitTransaction() descarta o snapshot.
transaction(fn)
Commit automático no sucesso, rollback automático no throw.
transaction<R>(fn: () => R): Rqueue.transaction(() => {
queue.append("step-1");
queue.append("step-2");
// throw → rollback ambos
});simulate(fn)
Sempre rollback. Para cenários "what if?" sem mutar estado.
simulate<R>(fn: () => R): Rconst wouldOverflow = buffer.simulate(() => {
buffer.appendMany(newBatch);
return buffer.size > threshold;
});
// buffer intactoNesting
Transações suportam nesting arbitrário.
queue.transaction(() => {
queue.append("a");
const safe = queue.simulate(() => {
queue.append("danger");
return queue.every((t) => isValid(t));
});
if (!safe) throw new Error("invalid");
});inTransaction / transactionDepth
readonly inTransaction: boolean // true se algum nível está ativo
readonly transactionDepth: number // 0 = sem transactionSubscribe (reatividade)
Registra um listener chamado a cada mutação. Recebe (items, prevItems).
subscribe(listener: (items: T[], prevItems: T[]) => void): () => voidRetorna uma função de unsubscribe.
const unsub = queue.subscribe((items, prev) => {
console.log(`Queue: ${prev.length} → ${items.length}`);
});
queue.append("task-X");
// log: "Queue: 2 → 3"
unsub();
queue.append("task-Y");
// sem logZero overhead sem subscribers
Quando nenhum listener está registrado, o pipeline de mutação usa a strategy sem listeners. Sem iteração, sem checagem.
Middleware
Interceptors de mutação. Podem observar, validar ou bloquear mutações.
use(middleware: (current: T[], next: T[], proceed: (override?: T[]) => void) => void): () => voidRetorna uma função de remoção.
Logging
const remove = queue.use((current, next, proceed) => {
console.log(`${current.length} → ${next.length} items`);
proceed();
});Validação (bloquear mutação)
Não chamar proceed() bloqueia a mutação. O estado permanece inalterado e listeners não são notificados.
buffer.use((current, next, proceed) => {
if (next.some((item) => !isValid(item))) return; // bloqueia
proceed();
});Snapshot / Restore
Serialização e restauração de estado para persistência.
snapshot()
snapshot(): StateSnapshot
// { type: 'list', id: string, data: T[] }const snap = queue.snapshot();
localStorage.setItem("queue", JSON.stringify(snap));restore(snapshot)
Restaura o estado. Valida contra maxSize — rejeita se o snapshot excede a capacidade.
restore(snapshot: StateSnapshot): voidconst saved = JSON.parse(localStorage.getItem("queue")!);
queue.restore(saved);Store Integration
A List funciona standalone por padrão. Quando registrada em um Store, o Store seta hooks internos para coordenar transações entre múltiplos states.
import { createStore } from "@statedelta-apex/store";
// Standalone
const queue = createList<string>("tasks", []);
queue.append("task-A");
// Com Store — coordenação entre states
const store = createStore();
store.register(queue);
store.transaction(() => {
queue.append("task-B");
hp.set("value", 50);
// throw → rollback AMBOS
});Extensão via Composição
O ListState é o mecanismo — dual-end, capacidade, transactions, reactivity. Derived types adicionam semântica sem se preocupar com mecânica interna.
Queue (FIFO)
class Queue<T> extends DerivedState<T[], ListState<T>> {
constructor(id: string, items: T[] = [], maxSize?: number) {
super(new ListState(id, items, { maxSize }));
}
enqueue(item: T): void {
this._primitive.append(item);
}
dequeue(): T | undefined {
return this._primitive.removeFirst();
}
peek(): T | undefined {
return this._primitive.peekFirst();
}
get size(): number {
return this._primitive.size;
}
get isEmpty(): boolean {
return this._primitive.isEmpty;
}
}Stack (LIFO)
class Stack<T> extends DerivedState<T[], ListState<T>> {
constructor(id: string, items: T[] = [], maxSize?: number) {
super(new ListState(id, items, { maxSize }));
}
push(item: T): void {
this._primitive.append(item);
}
pop(): T | undefined {
return this._primitive.removeLast();
}
peek(): T | undefined {
return this._primitive.peekLast();
}
get size(): number {
return this._primitive.size;
}
get isEmpty(): boolean {
return this._primitive.isEmpty;
}
}O Queue/Stack não sabem como transaction funciona, como snapshot serializa, como subscribe notifica. O ListState cuida de tudo.
Configuração
interface ListConfig {
/** Capacidade máxima. Default: Infinity (sem limite) */
maxSize?: number;
/** Validação de serializabilidade a cada mutação. Default: false */
strict?: boolean;
}| Config | Default | Descrição |
| --------- | ---------- | ---------------------------------------------- |
| maxSize | Infinity | Limite de items (throw RangeError se excedido) |
| strict | false | Validação de serializabilidade a cada mutação |
Tipos Exportados
import { createList, ListState } from "@statedelta-apex/list-state";
import type { ListConfig, IListState } from "@statedelta-apex/list-state";| Export | Tipo | Descrição |
| --------------- | --------- | ----------------------------------------------------- |
| createList | function | Factory principal |
| ListState<T> | class | Classe do primitivo (usável com new ou via factory) |
| ListConfig | type | Configuração (extends StateConfig) |
| IListState<T> | interface | Interface pública completa |
