memory()

The database kept in the process, for a first application and the tests.

memory() keeps the rows in the Node process: nothing to install or run. It ships with ultimabase, so a first application and every test start on it. The rows last until the application stops.

import { graphqlPlugin } from '@ultimabase/graphql'
import { httpPlugin } from '@ultimabase/http'
import { defineApp, memory } from 'ultimabase'
import { Contact } from './models.ts'

export const app = defineApp({
	// Nothing to install or run: the rows live in the process.
	database: memory(),
	plugins: [httpPlugin(), graphqlPlugin()],
	models: [Contact],
})

The full example, with its tests: examples/memory.

For data that must outlive the process, use PostgreSQL: the models, the queries and the errors stay the same.

Empty at each start

Each start begins on an empty database. testApplication starts the application around each test, so each test begins without rows:

it('writes a note', async () => {
	await Note.create({ data: { text: 'Milk', ownerId: 'ada' } }, { as: master })
	expect(await Note.count({}, { as: master })).toBe(1)
})

it('starts on an empty database', async () => {
	expect(await Note.count({}, { as: master })).toBe(0)
})

ultimabase dev restarts the application when a file changes: the rows written before are gone.

A write is whole or not at all

A write runs on a copy of the rows, which replaces them once it succeeded. A write that fails changes nothing, as a PostgreSQL statement does:

// The second email is the first's: the whole write is refused.
await expect(
	Contact.createMany(
		{
			data: [
				{ name: 'Ada', email: 'ada@example.com' },
				{ name: 'Ada L.', email: 'ada@example.com' },
			],
		},
		as,
	),
).rejects.toMatchObject({ code: 'UNIQUE_VIOLATION' })
expect(await Contact.count({}, as)).toBe(0)

Writes run one at a time; an open transaction holds the others until it ends. A query that fails inside a transaction rolls it back, as on PostgreSQL.

What it shares with PostgreSQL

A test on memory() passes on PostgreSQL: both adapters run the same suite of tests. Strings sort by their bytes, as PostgreSQL's "C" collation does:

await Contact.createMany(
	{
		data: [
			{ name: 'adam', email: 'adam@example.com' },
			{ name: 'Émile', email: 'emile@example.com' },
			{ name: 'Zoe', email: 'zoe@example.com' },
			{ name: 'Ada', email: 'ada@example.com' },
		],
	},
	as,
)
const sorted = await Contact.findMany({ orderBy: { name: 'asc' } }, as)
// Capitals first, then small letters, then letters with accents.
expect(names(sorted)).toEqual(['Ada', 'Zoe', 'adam', 'Émile'])

_search reads words as PostgreSQL's 'simple' configuration does: a word is a run of letters and digits, in any case, without stems or stop words. Relevance follows PostgreSQL's ts_rank.

await Contact.createMany(
	{
		data: [
			{ name: 'Émile Zola', email: 'emile@example.com' },
			{ name: 'Jean-Paul Sartre', email: 'sartre@example.com' },
		],
	},
	as,
)
const search = async (words: string) =>
	names(await Contact.findMany({ where: { _search: words } }, as))
// Words ignore case, a hyphen separates them, an accent counts.
expect(await search('ZOLA')).toEqual(['Émile Zola'])
expect(await search('paul')).toEqual(['Jean-Paul Sartre'])
expect(await search('emile')).toEqual([])

Realtime changes reach the clients after the write, as PostgreSQL's notifications do (see Realtime).

What it does not do

  • It has no tables to migrate: sync, reset and each migrate command refuse it.
  • One process holds the rows: two instances of the application share nothing.
  • postgres({ searchLanguage }) stems words in a language; memory() reads them as 'simple' only.
  • Raw SQL, a database tool, a dump: test those on PostgreSQL, in a *.postgres.test.ts file (see Testing).

Reference

memory()

It takes no options, and returns the database for defineApp's database.

Errors

MessageWhen
This database starts empty every time: it has no tables to migrate.ultimabase sync, reset or migrate on memory().

The errors of a query are those of every database: see Errors.

Limits

  • The rows live as long as the process: a stop, a crash or a restart loses them.
  • Each write copies the rows: keep it to tests and development, with the rows they need.
  • Writes run one at a time, in the order they came.

On this page