Self-hostingOverview

Self-hosting a pwapp

An app built on PhiWebs can be downloaded as a pwapp and run on any static host with @phimajor-solutions/pwfabric-runner. The runner is a browser ES module: it fetches the app’s declaration files, mounts the renderer, and adds routing, navigation and visitor sign-in. No build step, no Node process.

How far a self-host goes depends on what the app needs:

The app…Runs whereWhat is on the wire
is static — pages, content, appearanceanywhere that serves filesnothing; the host is enough
reads or writes data — lists, forms, entitiesanywhere, plus an APIthe data contract — see Data and receipts
gates pages behind visitor sign-inanywhere, plus an issuerthe session protocol — see Visitor sign-in

The first row needs only this page. The other two need the base URL of an API that speaks the documented contract. Pointing it at api.phiwebs.com is the supported path; running your own implementation of the same contract is what the BSL permits and what those two pages make possible.

What a downloaded app contains

PhiCo offers two files. Export gives a zip that is a complete, runnable folder: the app files, the block bundles, and a copy of the runner. Download gives only the .pwpack.tgz envelope, which is the app without an engine; it is not runnable on its own. Self-hosting starts from the Export zip.

Inside it, the runner reads plain JSON files and resolves blocks from a manifest next to them:

index.html            ← the shell: loads the runner and calls runApp()
assets/
  runner.js           ← @phimajor-solutions/pwfabric-runner, copied in
  embed.js, theme.css ← renderer and theme
shims/                ← import-map shims the bundles resolve against
pwpack/
  project.json        ← the app: pages, routes, surfaces (required)
  navigation.json     ← menus (optional)
  appearance.json     ← theme tokens (optional)
  auth.json           ← sign-in declaration (optional)
  connections.json    ← data connections (optional)
blocks/
  manifest.json       ← which block bundles exist and where
  …                   ← the bundles themselves
engines/
  manifest.json       ← connector engines (optional)

The exact shape of each file is in The envelope.

Source: pwfabric-core/packages/runner/src/run-app.ts (the fetch list at the top of runApp); PhiWebs API (closed source): web-app-assembly-service.ts (assembleWebApp, the file map the Export writes).

Mounting the runner

The Export zip already contains an index.html that does this; it is shown here so you know what to keep when you replace the shell with your own page:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="./assets/theme.css" />
    <!-- the import map the export wrote, if any, goes here -->
    <script src="./assets/embed.js"></script>
    <!-- only for path routing; see below -->
    <!-- <meta name="pw-router" content="path" /> -->
  </head>
  <body>
    <div id="root"></div>
    <script type="module">
      import { runApp } from './assets/runner.js'
      runApp({ source: './pwpack', blocks: './blocks/', router: 'hash' })
    </script>
  </body>
</html>

runApp() takes an options object. Every field has a default that matches the folder layout above, so runApp() with no arguments is equivalent when the files sit next to index.html.

OptionDefaultMeaning
source'./pwpack'folder path or absolute URL of the app files
blocks'./blocks/'folder holding the block manifest.json
engines'./engines'folder holding the connector-engine manifest.json
router'hash''hash', 'path' or 'auto' — see routing
mount'#root'selector of the element the app renders into
locale'en'content locale of the delivered app ('en' or 'tr')

Source: pwfabric-core/packages/runner/src/types.ts (RunAppOptions); web-app-assembly-service.ts (buildShellHtml, the shell the Export writes).

Routing on a static host

The default router is hash: routes live after # in the URL, which works on every static host without configuration.

path routing gives clean URLs but needs the host to serve index.html for every path (an “SPA fallback”). Declare it in the page so the runner knows the host is ready:

<meta name="pw-router" content="path" />

auto picks path only when history.pushState works and that meta tag is present; otherwise it falls back to hash. If you enable path without the fallback, deep links return the host’s 404.

Source: pwfabric-core/packages/runner/src/types.ts (router), pwfabric-core/packages/runner/src/router.ts.

Where the platform is still on the wire

A static app makes no request to PhiWebs. Two things do, and each has its own page:

  • Data. When a surface binds to an entity, the runtime sends the query to the configured API base. What it sends, and what it expects back, is the data contract.
  • Sign-in. When a page is auth-gated, the runner opens one of two doors, chosen by the app’s identity topology: a PKCE flow against an external OIDC issuer, or the platform’s own app-session endpoints. The endpoints, the token, and where the session lives are in the session protocol.

These pages are written from source and cite it. A citation that is a file path points into the open engine (pwfabric-core), which you can read. A citation marked closed source names the PhiWebs server component whose behaviour the page describes; that code is not published, so the page states what it does rather than where. Where the code leaves something open, the page says so under Unverified rather than guessing.