Architecture

Core Concepts

App - Logical grouping of features - typically a website page or re-usable widget.

Events - Signals that can trigger queries ro effects.

State - Variables that track state. State changes can trigger queries or effects.

Queries - Declarative functions that fetch data and may be triggered by events or state changes.

Effects - Declarative functions that cause side effects and may be triggered by events or state changes.

App

An app is the organizational structure for building. Typically, you will build apps that correspond with a page or re-usable widget in the browser. On the server you will create an app that contains state, events, queries, or effects. On the frontend you will create a corresponding React component that wraps the ui elements for your app.

//server
const myApp = app(() => {
  // Add state, events, queiries, and effects 

  return {
    name: "myApp",
    state: [], //TODO state 
    events: [], //TODO events
    queries: [], //TODO queries
    effects: [] //TODO effects
  };
});
//ui
const MyApp = app("myApp");

const UI = () => {
  return (
    <MyApp>
      {/* Your Components Here */}
    </MyApp>
  );
};

Events

Events are signals that can trigger queries or effects. They are useful for responding to button clicks, link clicks, toggle switches, etc. You can trigger events from either the frontend or backend. See queries for details about triggering and subscribing to events.

//server
const myApp = app(() => {
  const myEvent$ = useEvent("myEvent"); 

  return {
    name: "myApp",
    state: [], //TODO state 
    events: [myEvent$], //TODO events
    queries: [], //TODO queries
    effects: [] //TODO effects
  };
});
//ui
const MyApp = app("myApp");
const useMyEvent = event("myApp", "myEvent");

const MyAppUI = () => {
  const [triggerMyEvent] = useMyEvent();

  return (
    <MyApp>
      <button onClick={triggerMyEvent}>Trigger My Event</button>
    </MyApp>
  );
};

State

State is synchronized across the server and frontend. On the server you can create state via the state function. On the frontend you can create corresponding states.

//server
const myApp = app(() => {
  const myState$ = useState<string>("myState");

  return {
    name: "myApp",
    state: [myState$], //TODO state 
    events: [], //TODO events
    queries: [], //TODO queries
    effects: [] //TODO effects
  };
});
//ui
const useMyState = state("myApp", "myState");

const MyAppUI = () => {
  const [myState, setMyState] = useMyEvent();

  return (
    <MyApp>
      <input defaultValue={myState} onChange={e => setMyState(e.currentTarget.value)} />
    </MyApp>
  );
};

Queries

Queries wrap typescript functions. Queries can be triggered by events or state changes. And they can inject state variables as parameters into the function being invoked.

Simple query

This simple query wraps the function myFunc and will be invoked when the app is initialized.

//server
const myFunc = () => 
  "Hello World";

const myApp = app(() => {
  const myFunc$ = useQuery(myFunc);

  return {
    name: "myApp",
    state: [], //TODO state 
    events: [], //TODO events
    queries: [myFunc$], //TODO queries
    effects: [] //TODO effects
  };
});

Queries with asyncronous function

Queries work with asyncronous functions natively.

//server
const myFunc = (): Promise<string> => 
  Promise.resolve("Hello World");

const myApp = app(() => {
  const myFunc$ = useQuery(myFunc);

  return {
    name: "myApp",
    state: [], //TODO state 
    events: [], //TODO events
    queries: [myFunc$], //TODO queries
    effects: [] //TODO effects
  };
});

Queries injected with state

This query wraps the myFunc function and passes the variable tracked by the a$ into the function when invoked. Queries trigger when state it depends on changes or on initialization if it doesn't depend on any state. This query will be triggered when a$ changes.

const myFunc = (a: string) =>
  console.log(`a: ${a}`);

const myApp = app(() => {
  const a$ = useState<string>();
  const myFunc$ = useQuery(myFunc, [a$]);

  return {
    name: "myApp",
    state: [a$], //TODO state 
    events: [], //TODO events
    queries: [myFunc$], //TODO queries
    effects: [] //TODO effects
  };
});

Queries triggered by events

Queries can be triggered by events specified in the triggers option. This query will be triggered by myEvent$.

//server
const myFunc = () => 
  console.log("Hello World");

const myApp = app(() => {
  const myEvent$ = useEvent();
  const myFunc$ = useQuery(myFunc, [], {triggers: [myEvent$]});

  return {
    name: "myApp",
    state: [], //TODO state 
    events: [myEvent$], //TODO events
    queries: [myFunc$], //TODO queries
    effects: [] //TODO effects
  };
});

Queries can trigger events

After a query has completed it's function it can trigger downstream events. This query invokes myFunc. Then it triggers myEvent$.

//server
const myFunc = () => 
  console.log("Hello World");

const myApp = app(() => {
  const myEvent$ = useEvent();
  const myFunc$ = useQuery(myFunc, [], {onSuccess: [myEvent$]});

  return {
    name: "myApp",
    state: [], //TODO state 
    events: [myEvent$], //TODO events
    queries: [myFunc$], //TODO queries
    effects: [myFunc$], //TODO effects
  };
});

Queries are also events and state

File Structure

Blueprint has four top-level directories: scripts, server, shared, and ui.

scripts - Build scripts used by the makefile for queries like installing, compiling, and cleaning
server - Code for the server-side of your application. This is where you'll write your business logic.
shared - Code shared between your server and ui. Include types shared between your server and ui.
ui - Code for your user interface. Typically, you'll include display logic, preferring to put business logic on your server.

/scripts
/server
  /src
    /apps
    index.ts
    diagram.ts
    session.ts
/shared
  /src
    /apps
/ui
  /src
    /apps
  home.tsx  
  index.tsx