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/matrix-state

v3.0.0

Published

ApexStore Matrix primitive - typed 2D grid state with cell management

Downloads

230

Readme

@statedelta-apex/matrix-state

Matrix primitive — grid 2D tipado de dimensões fixas, com 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. A Matrix inverte isso: é um store completo e autossuficiente que já sabe como armazenar, mutar, fazer transaction/rollback, notificar listeners e serializar seu próprio estado.

Diferente de um T[][] genérico, a Matrix possui dimensões fixas, valor default e operações otimizadas de grid (neighbors, diagonals, ranges). A Matrix não é um wrapper de array bidimensional — é um storage especializado onde posições 2D e structural sharing são first-class citizens.

import { createMatrix } from "@statedelta-apex/matrix-state";

const board = createMatrix<string | null>("board", 3, 3, null);

board.setCell(0, 0, "X");
board.setCell(1, 1, "O");
board.swap(0, 0, 2, 2);

board.getCell(2, 2); // "X"
board.getNeighbors(1, 1); // 4 vizinhos cardinais
board.isCellEmpty(0, 0); // true (voltou a null após swap)

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.
  • Dimensões fixasrows, cols e defaultValue são imutáveis. O grid é denso — toda célula tem um valor.
  • Structural sharing — mutações clonam apenas as rows afetadas. Rows não tocadas mantêm a mesma referência.

Instalação

pnpm add @statedelta-apex/matrix-state

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

Quick Start

import { createMatrix } from "@statedelta-apex/matrix-state";

// Criar — 8×8 grid de números, default 0
const grid = createMatrix<number>("grid", 8, 8, 0);

// Mutar
grid.setCell(3, 4, 99);
grid.fillRow(0, 1);
grid.swap(3, 4, 7, 7);

// Ler
grid.getCell(7, 7); // 99
grid.getRow(0); // [1, 1, 1, 1, 1, 1, 1, 1]
grid.getNeighbors(4, 4, "all"); // 8 vizinhos
grid.find((v) => v === 99); // { row: 7, col: 7, value: 99 }

// Reagir
const unsub = grid.subscribe((cells, prevCells) => {
  console.log("Grid changed");
});

// Transacionar
grid.transaction(() => {
  grid.setCell(0, 0, 42);
  grid.setCell(0, 1, 43);
  // throw aqui → rollback ambos
});

// Simular (what-if)
const result = grid.simulate(() => {
  grid.fill(0);
  return grid.every((v) => v === 0);
});
// result === true, grid intacto

Criação

createMatrix(id, rows, cols, defaultValue, config?)

function createMatrix<T>(
  id: string,
  rows: number,
  cols: number,
  defaultValue: T,
  config?: MatrixConfig,
): MatrixState<T>;

| Parâmetro | Tipo | Descrição | | -------------- | -------------- | -------------------------------------------- | | id | string | Identificador único do primitivo | | rows | number | Número de linhas (>= 1, imutável) | | cols | number | Número de colunas (>= 1, imutável) | | defaultValue | T | Valor inicial de todas as células (imutável) | | config | MatrixConfig | Configuração opcional |

// Tabuleiro 3×3
const board = createMatrix<string | null>("board", 3, 3, null);

// Grid numérico
const grid = createMatrix<number>("grid", 100, 100, 0);

// Strict mode
const strict = createMatrix("safe", 4, 4, 0, { strict: true });

Também disponível via new:

import { MatrixState } from "@statedelta-apex/matrix-state";

const board = new MatrixState<string | null>("board", 3, 3, null);

Dimensões devem ser >= 1. createMatrix("x", 0, 3, null) lança RangeError.


Actions (mutações)

Toda action produz uma nova referência do grid e dispara o pipeline de mutação (hook → middleware → state → listeners). Apenas as rows afetadas são clonadas — structural sharing garante que rows intocadas mantêm a mesma referência.

setCell(row, col, value)

Seta o valor de uma célula.

setCell(row: number, col: number, value: T): void
board.setCell(0, 0, "X");
board.setCell(2, 2, "O");

updateCell(row, col, fn)

Atualiza uma célula via função transformadora. Recebe o valor atual, retorna o novo.

updateCell(row: number, col: number, fn: (value: T) => T): void
grid.updateCell(0, 0, (v) => v + 1);
grid.updateCell(1, 1, (v) => v * 2);

updateRow(row, fn)

Transforma todos os valores de uma row. A função recebe o valor e o índice da coluna.

updateRow(row: number, fn: (value: T, col: number) => T): void
grid.updateRow(0, (v, col) => v + col); // [0,1,2,3,4,...]
grid.updateRow(2, () => 99); // toda a row vira 99

fill(value)

Preenche toda a matrix com um valor.

fill(value: T): void
board.fill(null); // limpa o tabuleiro
grid.fill(0); // zera o grid

fillRow(row, value)

Preenche uma linha inteira.

fillRow(row: number, value: T): void
grid.fillRow(0, 1); // primeira linha toda = 1

fillCol(col, value)

Preenche uma coluna inteira.

fillCol(col: number, value: T): void
grid.fillCol(0, 5); // primeira coluna toda = 5

fillRange(fromRow, toRow, fromCol, toCol, value)

Preenche um retângulo. from é inclusivo, to é exclusivo.

fillRange(fromRow: number, toRow: number, fromCol: number, toCol: number, value: T): void
// Preenche o quadrante central de um 4×4
grid.fillRange(1, 3, 1, 3, 9);
// [0, 0, 0, 0]
// [0, 9, 9, 0]
// [0, 9, 9, 0]
// [0, 0, 0, 0]

swap(fromRow, fromCol, toRow, toCol)

Troca os valores de duas células. Noop se as posições são iguais.

swap(fromRow: number, fromCol: number, toRow: number, toCol: number): void
board.setCell(0, 0, "X");
board.setCell(2, 2, "O");
board.swap(0, 0, 2, 2);
board.getCell(0, 0); // "O"
board.getCell(2, 2); // "X"

move(fromRow, fromCol, toRow, toCol)

Move o valor de uma célula para outra posição. A origem vira defaultValue. Noop se as posições são iguais.

move(fromRow: number, fromCol: number, toRow: number, toCol: number): void
board.setCell(0, 0, "X");
board.move(0, 0, 2, 2);
board.getCell(0, 0); // null (defaultValue)
board.getCell(2, 2); // "X"

replace(data)

Substitui a matrix inteira por um T[][]. Deve ter as mesmas dimensões. Input é clonado internamente.

replace(data: T[][]): void
board.replace([
  ["X", "O", "X"],
  ["O", "X", "O"],
  ["X", "O", "X"],
]);

Lança RangeError se as dimensões não batem.

reset()

Volta todas as células ao defaultValue.

reset(): void

Getters (leitura)

Getters são leituras puras. Sem side effects, sem mutações.

getCell(row, col)

Valor de uma célula.

getCell(row: number, col: number): T
board.getCell(0, 0); // "X"
board.getCell(1, 1); // null

getRow(row)

Linha inteira. Retorna uma cópia — modificar o array retornado não afeta o state.

getRow(row: number): T[]
const row = board.getRow(0); // ["X", null, null]
row[0] = "mutated";
board.getCell(0, 0); // "X" — original intacto

getCol(col)

Coluna inteira. Retorna um novo array construído a partir do grid.

getCol(col: number): T[]
board.getCol(0); // ["X", null, null] (coluna 0 de cada row)

getRange(fromRow, toRow, fromCol, toCol)

Sub-grid retangular. from inclusivo, to exclusivo. Retorna um novo T[][].

getRange(fromRow: number, toRow: number, fromCol: number, toCol: number): T[][]
grid.getRange(0, 2, 0, 2); // [[v00, v01], [v10, v11]]

getNeighbors(row, col, mode?)

Células vizinhas de uma posição. Retorna array de CellPosition<T>.

getNeighbors(row: number, col: number, mode?: "cardinal" | "all"): CellPosition<T>[]

| Modo | Vizinhos | Direções | | ---------------------- | -------- | -------------------------- | | "cardinal" (default) | Até 4 | N, E, S, W | | "all" | Até 8 | N, E, S, W, NE, SE, SW, NW |

Células fora dos limites são excluídas automaticamente (cantos retornam 2-3 vizinhos).

board.getNeighbors(1, 1); // 4 vizinhos (centro de 3×3)
board.getNeighbors(0, 0); // 2 vizinhos (canto)
board.getNeighbors(1, 1, "all"); // 8 vizinhos (centro, com diagonais)
board.getNeighbors(0, 0, "all"); // 3 vizinhos (canto, com diagonais)

Cada CellPosition contém:

interface CellPosition<T> {
  row: number;
  col: number;
  value: T;
}

getDiagonal(direction?)

Diagonal principal ou anti-diagonal. Para matrizes não-quadradas, percorre min(rows, cols) elementos.

getDiagonal(direction?: "main" | "anti"): T[]
// 3×3 com diagonal principal: A, E, I
// A B C
// D E F
// G H I
board.getDiagonal(); // ["A", "E", "I"]
board.getDiagonal("anti"); // ["C", "E", "G"]

find(predicate)

Primeira célula que satisfaz o predicate. Percorre em row-major order (esquerda→direita, cima→baixo). Short-circuit no primeiro match.

find(predicate: (value: T, row: number, col: number) => boolean): CellPosition<T> | undefined
board.find((v) => v === "X");
// { row: 0, col: 0, value: "X" }

board.find((v) => v === "Z");
// undefined

board.find((v, row, col) => row === col && v !== null);
// primeira célula na diagonal que não é null

findAll(predicate)

Todas as células que satisfazem o predicate. Row-major order.

findAll(predicate: (value: T, row: number, col: number) => boolean): CellPosition<T>[]
board.findAll((v) => v === "X");
// [{ row: 0, col: 0, value: "X" }, { row: 2, col: 2, value: "X" }]

board.findAll((v) => v === "Z");
// []

count(predicate)

Conta células que satisfazem o predicate.

count(predicate: (value: T, row: number, col: number) => boolean): number
board.count((v) => v !== null); // células preenchidas
board.count((v) => v === null); // células vazias

isCellEmpty(row, col)

Verifica se uma célula contém o defaultValue.

isCellEmpty(row: number, col: number): boolean
board.isCellEmpty(0, 0); // true (se defaultValue é null e célula é null)
board.setCell(0, 0, "X");
board.isCellEmpty(0, 0); // false

every(predicate)

true se todas as células satisfazem o predicate. Short-circuit no primeiro false.

every(predicate: (value: T, row: number, col: number) => boolean): boolean
const allEmpty = board.every((v) => v === null);
const allPositive = grid.every((v) => v > 0);
const topRowFilled = board.every((v, row) => row !== 0 || v !== null);

some(predicate)

true se alguma célula satisfaz o predicate. Short-circuit no primeiro true.

some(predicate: (value: T, row: number, col: number) => boolean): boolean
const hasX = board.some((v) => v === "X");
const hasNegative = grid.some((v) => v < 0);

Properties

rows

Número de linhas. Imutável.

readonly rows: number

cols

Número de colunas. Imutável.

readonly cols: number

defaultValue

Valor default das células. Imutável.

readonly defaultValue: T

cells

O grid como T[][]. Alias para getState(). Imutável por convenção.

readonly cells: T[][]

size

Número total de células (rows × cols). Getter.

readonly size: number

Estado

getState()

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

getState(): T[][]
const cells = board.getState();
// [[null, null, null], [null, null, null], [null, null, null]]

// Cada mutação gera uma nova referência
board.setCell(0, 0, "X");
const newCells = board.getState();
cells !== newCells; // true — referências diferentes
cells[0]![0]; // null — original intacto

id / type

readonly id: string    // ID passado na criação
readonly type: string  // "matrix"

Transactions

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

API básica

board.beginTransaction();
board.setCell(0, 0, "X");
board.setCell(1, 1, "O");

board.rollback();
board.getCell(0, 0); // null — restaurado

transaction(fn)

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

transaction<R>(fn: () => R): R
// Sucesso → commit
board.transaction(() => {
  board.setCell(0, 0, "X");
  board.setCell(1, 1, "O");
});

// Falha → rollback
try {
  board.transaction(() => {
    board.setCell(0, 0, "X");
    throw new Error("abort");
  });
} catch {}
board.getCell(0, 0); // null — rollback restaurou

simulate(fn)

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

simulate<R>(fn: () => R): R
const isDraw = board.simulate(() => {
  board.setCell(2, 2, "X");
  return board.every((v) => v !== null);
});
// isDraw calculado, board intacto

Nesting

Transações suportam nesting arbitrário.

board.transaction(() => {
  board.setCell(0, 0, "X");

  const wouldWin = board.simulate(() => {
    board.setCell(1, 1, "X");
    board.setCell(2, 2, "X");
    return checkDiagonalWin(board);
  });
  // board tem só [0,0]="X"

  if (!wouldWin) {
    board.setCell(0, 1, "X"); // estratégia diferente
  }
});

inTransaction / transactionDepth

readonly inTransaction: boolean   // true se algum nível está ativo
readonly transactionDepth: number // 0 = sem transaction

Subscribe (reatividade)

Registra um listener chamado a cada mutação. Recebe (cells, prevCells).

subscribe(listener: (cells: T[][], prevCells: T[][]) => void): () => void

Retorna uma função de unsubscribe.

const unsub = board.subscribe((cells, prev) => {
  console.log("Board changed");
});

board.setCell(0, 0, "X");
// log: "Board changed"

unsub();
board.setCell(1, 1, "O");
// sem log

Zero 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): () => void

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

Logging

const remove = board.use((current, next, proceed) => {
  console.log("Board mutation");
  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.

board.use((current, next, proceed) => {
  // Impede que a célula central seja sobrescrita
  if (current[1]![1] !== next[1]![1]) return;
  proceed();
});

Snapshot / Restore

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

snapshot()

snapshot(): StateSnapshot
// { type: "matrix", id: string, data: T[][] }
const snap = board.snapshot();
// { type: "matrix", id: "board", data: [[null,"X",null], ...] }

localStorage.setItem("board", JSON.stringify(snap));

restore(snapshot)

restore(snapshot: StateSnapshot): void
const saved = JSON.parse(localStorage.getItem("board")!);
board.restore(saved);

O restore valida que o snapshot tem as mesmas dimensões (rows × cols). Lança RangeError se incompatível.


Bounds Checking

Toda operação que aceita coordenadas valida limites antes de operar. Lança RangeError com mensagem descritiva.

board.setCell(5, 0, "X");
// RangeError: Position [5, 0] out of bounds for 3×3 matrix

board.getRow(-1);
// RangeError: Row -1 out of bounds (0–2)

board.fillRange(0, 4, 0, 3, "X");
// RangeError: Range [0:4, 0:3] out of bounds for 3×3 matrix

board.fillRange(2, 1, 0, 3, "X");
// RangeError: Invalid range: from must be less than to (got [2:1, 0:3])

Convenção de ranges: from inclusivo, to exclusivo (mesmo padrão de Array.prototype.slice()).


Store Integration

A Matrix funciona standalone por padrão. Quando registrada em um Store, o Store seta hooks internos para coordenar transações entre múltiplos primitivos.

import { createStore } from "@statedelta-apex/store";
import { createMatrix } from "@statedelta-apex/matrix-state";

// Standalone — sem Store
const board = createMatrix<string | null>("board", 3, 3, null);
board.setCell(0, 0, "X");

// Com Store — coordenação entre primitivos
const store = createStore();
store.register(board);

store.transaction(() => {
  board.setCell(0, 0, "X");
  board.setCell(1, 1, "O");
  // throw → rollback AMBOS
});

Extensão via Composição

A Matrix é um primitivo — cuida de storage, transaction, snapshot, reactivity. Tipos derivados compõem com a Matrix (via extends), adicionando actions/getters de domínio sem se preocupar com mecânica interna.

Exemplo: Board

Um Board é uma Matrix com semântica de tabuleiro de jogo (placement rules, win conditions).

import { MatrixState } from "@statedelta-apex/matrix-state";

class Board extends MatrixState<string | null> {
  place(row: number, col: number, piece: string): void {
    if (!this.isCellEmpty(row, col)) {
      throw new Error(`Cell [${row}, ${col}] is occupied`);
    }
    this.setCell(row, col, piece);
  }

  isFull(): boolean {
    return this.every((v) => v !== null);
  }

  getEmptyCells() {
    return this.findAll((v) => v === null);
  }

  checkLine(positions: [number, number][]): boolean {
    if (positions.length === 0) return false;
    const first = this.getCell(positions[0]![0], positions[0]![1]);
    if (first === null) return false;
    return positions.every(([r, c]) => this.getCell(r, c) === first);
  }
}

function createBoard(id: string, size: number): Board {
  return new Board(id, size, size, null);
}
const board = createBoard("tic-tac-toe", 3);

board.place(0, 0, "X");
board.place(1, 1, "O");
board.place(2, 2, "X");

board.isFull(); // false
board.getEmptyCells().length; // 6
board.checkLine([
  [0, 0],
  [1, 1],
  [2, 2],
]); // false ("X", "O", "X")

// Transaction, subscribe, middleware — tudo funciona
board.transaction(() => {
  board.place(0, 1, "X");
  board.place(0, 2, "X");
});

board.subscribe((cells, prev) => {
  console.log("Board updated");
});

O Board não sabe como transaction funciona, como snapshot serializa, como subscribe notifica. A Matrix cuida de tudo.


Configuração

type MatrixConfig = StateConfig;
interface StateConfig {
  /** Validação de serializabilidade a cada mutação. Default: false */
  strict?: boolean;

  /** Rastreamento de diffs (audit mode). Default: false */
  trackDeltas?: boolean;
}

| Config | Default | Descrição | | ------------- | ------- | --------------------------------------------- | | strict | false | Validação de serializabilidade a cada mutação | | trackDeltas | false | Rastreamento de diffs (audit mode) |


Tipos Exportados

import { createMatrix, MatrixState } from "@statedelta-apex/matrix-state";
import type {
  MatrixConfig,
  IMatrixState,
  CellPosition,
  CellPredicate,
} from "@statedelta-apex/matrix-state";

| Export | Tipo | Descrição | | ------------------ | --------- | ------------------------------------------------------ | | createMatrix | function | Factory principal | | MatrixState<T> | class | Classe do primitivo (usável com new ou via factory) | | MatrixConfig | type | Configuração (alias de StateConfig) | | IMatrixState<T> | interface | Interface pública completa | | CellPosition<T> | interface | Posição + valor de uma célula { row, col, value } | | CellPredicate<T> | type | Predicate para iteração (value, row, col) => boolean |