Moss · guide

A press on the server

A press is an event sent up. The trigger runs in the shell on the server, and the next frame comes back.

Install it
pnpm add @niscorp/moss @niscorp/charter @niscorp/nova @niscorp/prism @niscorp/strata @niscorp/tide @niscorp/vex zod @electric-sql/pglite
Step 1 of 4

The app, booted

The same theatre. The seats screen holds a count, and a trigger that adds one to it when the button with the ref hold is pressed.

The app, booted
import { PGlite } from '@electric-sql/pglite';
import { createServer, defineApp, mintDevToken } from '@niscorp/moss';
import { canvasTreeOf, shellSettled } from '@niscorp/nova';
import type { ActionDefinition, RenderNode } from '@niscorp/nova';
import { createPglitePool } from '@niscorp/vex/pglite';

const db = new PGlite();
const runtime = { db, pool: createPglitePool(db), session: 'dev-open' as const };
const charter = {
  public: ['programme'],
  member: ['programme', 'seats'],
  manager: { extends: ['member'], allow: ['takings'] },
};
const assignments = { grace: ['member'], ruth: ['manager'] };
const text = (says: string) => ({ component: 'Text', children: says });
const actions: Record<string, ActionDefinition> = {
  programme: { id: 'programme', layout: text('This week: The Tempest') },
  seats: {
    id: 'seats',
    data: { held: 0 },
    triggers: [{ event: 'ui:click', ref: 'hold', do: [{ increment: 'held' }] }],
    layout: {
      component: 'Stack',
      children: [
        text('Seats held: {{$.held}}'),
        { component: 'Button', ref: 'hold', props: { label: 'Hold a seat' } },
      ],
    },
  },
  takings: { id: 'takings', layout: text('Saturday took 4,180') },
};
const app = defineApp({
  charter,
  assignments,
  actions,
  shell: { canvases: [{ id: 'main', initial: ['takings', 'seats', 'programme'] }] },
});
const server = await createServer(app, runtime);
const words = (nodes: readonly RenderNode[]): string[] =>
  nodes.flatMap((node) => (node.type === 'text' ? [node.value] : 'children' in node ? words(node.children) : []));
Step 2 of 4

Grace’s session

shells.session gives the living shell of one person, the one a socket would attach to. It is built the first time it is asked for and kept.

Grace’s session
const session = await server.shells!.session(mintDevToken('grace'), 'grace');
await shellSettled(session.shell);
console.log('before:', words(canvasTreeOf(session.shell, 'main')));
Step 3 of 4

Send the press

This is all a browser sends when a button is pressed: the kind of event and the ref of what was pressed. It does not say what should happen.

What happens is decided by the trigger, which is on the server. The count goes up in the shell, and the screen is drawn again.

Send the press
session.dispatch('main', { type: 'ui:click', ref: 'hold' });
await shellSettled(session.shell);
console.log('after:', words(canvasTreeOf(session.shell, 'main')));
Step 4 of 4

A press for something that is not there

An event for a ref the screen does not have changes nothing. A browser cannot make up a press that does more than the screen allows.

A press for something that is not there
session.dispatch('main', { type: 'ui:click', ref: 'refund-everyone' });
await shellSettled(session.shell);
console.log('still:', words(canvasTreeOf(session.shell, 'main')));
server.close();
Then

Run it.

The count before the press, after it, and unchanged by an event the screen has no trigger for.

Run it
npx tsx press.mts
It prints
before: [ 'Seats held: 0' ]
after: [ 'Seats held: 1' ]
still: [ 'Seats held: 1' ]