One action, step by step
Take this action:actions/greetAction.ts
1
The server starts
app.listen() loads every file in the actionsDirectory folder given to new AwaitIO() and opens the port. A file’s path becomes the action’s URL: actions/greetAction.ts is at /actions/greet.2
The UI lists the actions
After login the UI asks
GET /api/actions for the name, description and slug of every action. Nothing else about the action, and none of its code, reaches the browser.3
A run starts
Opening an action sends
POST /api/actions/:slug/runs. The server creates a run and calls your handler as a plain async function.4
The handler waits on io
await io.input.text('Your name') does not block anything else: the handler’s promise is parked on the run until someone answers. The run’s state now holds a step with one text field.5
The step reaches the browser
The page follows the run over server-sent events at
GET /api/runs/:id/events. On every (re)connect the server first sends the current state, so a refresh or a dropped connection picks up where
it was.6
The answer is checked on the server
The form sends
POST /api/runs/:id/respond with the values. The server parses each value with the component’s schema and runs its validate. An invalid value never reaches your handler: the same
step comes back with the error under the field.7
The handler goes on
A valid answer resolves the parked promise,
name gets the value and the handler continues to the next await io or to its return.8
The result is shown
The return value is sent to the page as the run’s result. A thrown error ends the run as a failure, and its name and message are shown instead.
Where runs live
Runs exist only in the server’s memory.- Refresh or a dropped connection keeps the run: the page reconnects and gets the current state.
- A closed page cancels the run after 30 seconds without any page following it. The same grace period applies to a run that no page ever opened. A canceled handler gets an error at its pending or next
iocall. - A finished run stays available for 15 minutes, then it is removed. Its URL no longer works after that.
- A restart or a deploy ends every running action. A handler is a live closure in memory and cannot be saved anywhere.
Security
Everything stays in your process
There is no AwaitIO service. Your action code, environment variables and the data your handlers load run only on your server. The browser receives exactly whatio calls and return values send it, and nothing is forwarded to a third party.
AwaitIO has no database and writes nothing to disk. The server does not log
io values, return values or anything else.
Who can use it
- Login: without a valid session the API and the component scripts answer
401; only the login page,/healthand the project name are public. See Users for passwords, sessions and lockout. - Runs are private: a run belongs to the user who started it. Its page, its answers and canceling it are open only to that user; anyone else gets
404. - Environments: the login page shows only the current environment. The URLs of the other environments are sent only to logged-in users.
- Only this machine by default:
app.listen()binds to127.0.0.1unless you pass anotherhost.
Requests from other sites
- Every API request that changes something must carry the
X-AwaitIO-Request: 1header and a JSON body. A page on another origin cannot send that header without a CORS preflight, which the server never allows. Requests whoseSec-Fetch-Siteis anything other thansame-originget403as well. - The session cookie is
HttpOnlyandSameSite=Strict. - Every response sets
X-Frame-Options: DENYandContent-Security-Policy: frame-ancestors 'none', so no other page can frame the UI and trick a user into clicking through a step.X-Content-Type-Options: nosniffandReferrer-Policy: no-referrerare set too.
The browser cannot skip validation
Components are checked on the server: every answer is parsed with the component’s schema and passed through itsvalidate before the handler sees it. The browser’s own checks, such as a required field, only save a round trip. A modified browser or a hand-written request cannot hand your handler a value the component would reject.