Vex · guide

Shapes and locales

The rows hold cents. The shape asks for a price in words, in the reader’s language, from one stored entry.

Install it
pnpm add @niscorp/vex @niscorp/prism @niscorp/strata zod @electric-sql/pglite
Step 1 of 3

The database and the engine

Two shows with a price in cents, and an engine that has read the schema.

The database and the engine
import { PGlite } from '@electric-sql/pglite';
import { createPostgresAdapter, createQueryEngine, seedCache } from '@niscorp/vex';
import { createPglitePool } from '@niscorp/vex/pglite';

const db = new PGlite();
await db.exec(`
  CREATE TABLE shows (night text PRIMARY KEY, title text, price_cents integer);
  INSERT INTO shows VALUES ('2026-10-24', 'The Tempest', 4500), ('2026-10-25', 'Twelfth Night', 3800);
`);
const engine = createQueryEngine({ adapter: createPostgresAdapter({ pool: createPglitePool(db) }) });
await engine.introspect();
Step 2 of 3

A read with a mapping

The rows hold price_cents. The shape asks for price, in words. Between the two stands a mapping: a Prism document over $.result, the rows, and $.scope, what the server knows about the reader. It is compiled and stored with the query at seed, and applied on every replay with no model.

$localeMoney writes an amount as the reader’s language writes it, and the locale it reads is $.scope.locale. Nothing in the app formats money anywhere else.

A read with a mapping
await seedCache(engine.cache, [{
  fingerprint: 'boxoffice/prices',
  shape: [{ title: '', price: '' }],
  dsl: {
    from: ['shows'],
    fields: ['shows.title', 'shows.price_cents'],
    sort: [{ field: 'shows.night', dir: 'asc' }],
  },
  mapping: {
    $map: {
      over: { $ref: '$.result' },
      as: 'show',
      body: {
        title: { $get: { from: { $var: 'show' }, path: ['title'] } },
        price: {
          $localeMoney: {
            value: { $get: { from: { $var: 'show' }, path: ['price_cents'] } },
            currency: 'EUR',
            locale: { $ref: '$.scope.locale' },
          },
        },
      },
    },
  },
}]);
Step 3 of 3

Two readers

The same entry, replayed for a reader in Vienna and one in Dublin. The locale comes in as scope, which a request cannot set.

Two readers
for (const locale of ['de-AT', 'en-IE']) {
  const response = await engine.execute({ fingerprint: 'boxoffice/prices', context: {} }, { scope: { locale } });
  console.log(locale, JSON.stringify(response.result));
}
Then

Run it.

Each reader gets the price as their language writes it, from one stored entry.

Run it
npx tsx prices.mts
It prints
de-AT [{"title":"The Tempest","price":"€ 45,00"},{"title":"Twelfth Night","price":"€ 38,00"}]
en-IE [{"title":"The Tempest","price":"€45.00"},{"title":"Twelfth Night","price":"€38.00"}]