# @devvit/runtime-lite

runtime-lite is a local runtime for executing apps in the browser (shreddit, d2x, :play), Android, iOS, and packages/ui-renderer/scripts/build-layouts.js clients. As much as possible is executed locally. Anything that can't be executed locally kicks back to the remote runtime where it is re-executed. This means anything that executes locally must be undoable so a partial failed execution can be repeated remotely.

Intended for internal use only.

Sign up for Reddit's Developer Platform [here!](https://developers.reddit.com)

## Not Locally Executable

The remote runtime can run any app code. The local only supports a subset.

- The local runtime has no plugins. If a plugin invocation is attempted by an app, such as fetch(), the Reddit API, or KV store, it will fail. The local runtime doesn't know when it will encounter remote code (plugin use) so it runs optimistically. When a request is ultimately unsatisfiable locally, a CircuitBreak error is thrown (see the local plugin shims). A caller is expected to catch and reissue the request against the remote runtime.
- When the app is downloading, no local code execution is attempted. Most client integrations load the initial render from the remote runtime first since this is much faster than downloading the app and rendering locally.

### A Typical Sequence of Renders

Rendering typically occurs as:

- Initial render happens on the server. This is to avoid waiting for an app to download.
- Any subsequent render needed while waiting for the app to download happens on the server too.
- Once the app is downloaded, always try the local runtime first and if it fails, then and only then always try the remote runtime.

## Entrypoints and Artifacts

runtime-lite supports the following entrypoints.

- dist/**browser-lite.worker**.js: d2x and shreddit entrypoint.
- dist/**mobile-lite**.js: Android and iOS entrypoint.
- **UnsandboxedRuntimeLite**: preview (:play) entrypoint.

## Boundaries, Environments, and Messaging

### App

Apps are servers that usually have clients. Runtimes execute apps on request.

Apps are compiled and bundled to usually\* looks like this:

```
┌──────────────────┐
│app               │
│                  │
│src/main.ts       │
│@devvit/public-api│
└──────────────────┘
```

\*:play apps do not bundle @devvit/public-api. This is injected by the @devvit/previews scripts into the environment.

### Runtime and App Execution Environment

#### Sandboxed

All app JavaScript executes in a heavyweight QuickJS virtual machine.

```
┌───────────────────┐
│runtime            │
│┌─────────────────┐│
││QuickJS env      ││
││                 ││
││┌───┐            ││
│││app│            ││
││└───┘            ││
││┌──────────────┐ ││
│││shim          │ ││
│││              │ ││
│││@devvit/protos│ ││
│││polyfills     │ ││
││└──────────────┘ ││
││┌───────────────┐││
│││runtime proxies│││
││└───────────────┘││
│└─────────────────┘│
└───────────────────┘
```

#### Unsandboxed

Skinny and easy to reason about but app JavaScript executes directly in the runtime.

```
┌─────────────────┐
│env              │
│                 │
│┌───┐            │
││app│            │
│└───┘            │
│┌──────────────┐ │
││shim          │ │
││              │ │
││@devvit/protos│ │
││polyfills     │ │
│└──────────────┘ │
│┌───────┐        │
││runtime│        │
│└───────┘        │
└─────────────────┘
```

### End-to-End Messaging

Native apps interact with the runtime directly. There's no worker or
other wrappers so they can invoke methods on the runtime itself in the
same environment. Both the unsandboxed runtime and app code are
untrusted since they execute in the same environment.

#### Shreddit

Web has many layers of indirection. Web uses the sandboxed runtime which
is trusted but embedded in a web worker for a new thread of execution
and that worker is accessed via BrowserLiteClient.

```
Post
ShredditDevvitUiLoader
DevvitCustomPost (element methods and events)
BrowserLiteClient (loadBundle(), call(), and getCallState())
─── web worker boundary: postMessage/onmessage() ───
RuntimeLiteWorker (translates postMessage/onmessage() to/from runtime)
SandboxedRuntimeLite (loadBundle(), call(), and getCallState())
QuickJS
─── VM boundary: Protobuf service APIs and QuickJS env ───
app
```

#### Native Apps

```
MobileWorker (adapts loadBundle() and call() to fake postMessage/onmessage())
RuntimeLiteWorker (translates postMessage/onmessage() to/from runtime)
UnsandboxedRuntimeLite (loadBundle(), call(), and getCallState())
app (Protobuf service APIs and runtime env)
```

#### :play

```
PlayPreview
DevvitPreview (element methods and events)
BrowserLiteClient (loadBundle(), call(), and getCallState())
─── web worker boundary: postMessage/onmessage() ───
RuntimeLiteWorker (translates postMessage/onmessage() to/from runtime)
Un/SandboxedRuntime (transparent; sprinkles in @devvit/public-api)
UnsandboxedRuntimeLite (loadBundle(), call(), and getCallState())
app (Protobuf service APIs and runtime env)
```
