Skip to content

Repository files navigation

No Database No Problem

TypeScript implementation of the Prevayler (object prevalence) concept. Keep your system in memory, log every change, and recover fast.

Key Concepts

  • In-Memory State: Your application's entire state is held in a single object in RAM for maximum performance.
  • Commands: The state is modified exclusively by dispatching commands. These are pure, deterministic functions that take the current state and produce a new one.
  • Append-Only Journal: Every command that modifies the state is recorded as an entry in a log file (the journal). On application startup, this journal is replayed to restore the state.
  • State Access: The current state can be read at any time via the getState() method.
  • Snapshots: To speed up recovery, you can create a snapshot, which serializes the entire current state into a new, clean journal and archives the old one.

Quick start

1. Define state

type Books = { id: string; title: string; createdAt: Date }[];
type SystemState = { books: Books };

2. Create a command

import { createCommandFactory } from "src/command";

export const createBook = createCommandFactory<
  SystemState,
  { id: string; title: string }
>("createBook", ({ state, executionTime, input }) => {
  return {
    ...state,
    books: [
      ...state.books,
      { id: input.id, title: input.title, createdAt: executionTime },
    ],
  };
});

3. Create Prevayler and run

import { createPrevayler } from "src/index";
import { createFileSystem } from "src/utils/file-system";
import { createTime } from "src/utils/time";
import { createSuperjsonSerializer } from "src/utils/serializer";

const fileSystem = createFileSystem();
const time = createTime();
const serializer = createSuperjsonSerializer();

const prevayler = await createPrevayler<SystemState>({
  fileSystem,
  time,
  serializer,
})({
  commands: [createBook],
  initialSystem: { books: [] },
  journalDirectory: "src/journal-data/books",
});

const createBookResult = await prevayler.dispatch(
  createBook({ id: "1", title: "The Great Gatsby" }),
);
if (!createBookResult.ok) throw createBookResult.error;

const books = prevayler.getState().books;
// books => [{ id: "1", title: "The Great Gatsby", createdAt: Date }]

Rules

  • Commands Must Be Pure: For predictable state restoration, commands must be deterministic. Given the same state and input, a command must always produce the same new state.
  • No Side Effects in Commands: Do not call external APIs, access the filesystem, or use non-deterministic functions like Math.random(). For time-based logic, use the executionTime passed into your command instead of Date.now().
  • State Must Be Serializable: Your entire state object must be compatible with superjson for it to be saved and loaded correctly.
  • Use Snapshots for Performance: For systems with large states or long-running journals, call createSnapshot() periodically to significantly reduce application restart times.

Credits

Inspired by Jarosław Ratajski's talk:

This project is an implementation of the Prevayler concept:

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages