How the microfrontend repo is laid out and the rules for crossing package lines. Read this before touching more than one package.
@ontotext/workbench-api.service() helper: getting API-layer singletons vs. Angular inject()./graphdb/.| Package | Framework | Role |
|---|---|---|
packages/api |
Framework-agnostic TypeScript | Shared API/services, consumed via the @ontotext/workbench-api alias |
packages/root-config |
single-spa + TypeScript | Root bootstrap/wiring of the microfrontend app |
packages/workbench |
Angular 21 (standalone, signals) | Main modern workbench frontend |
packages/shared-components |
Stencil.js (Web Components, TSX) | Reusable UI components |
packages/legacy-workbench |
AngularJS | Legacy workbench code |
e2e-tests |
Cypress | End-to-end and security tests |
plugins / wb-plugins |
— | Generated/bundled plugin artifacts |
packages/api or packages/shared-components, not duplicated locally.packages/root-config.packages/api knows nothing about UI: no view URLs, UI event names or component
types. shared-components knows nothing about the applications that host them (routes, view URLs, app events)
or about unrelated components. Pass what a component needs from outside, as a property, data-provider callback
or emitted event, and let the host decide.window, crypto, location) through WindowService
(packages/api/src/services/window/), so the code stays testable.application-query-params.ts.Everything in the API package must be imported through the alias:
import { ServiceProvider, RepositoryContextService } from '@ontotext/workbench-api';
Never import API internals via relative or absolute filesystem paths across packages. The api module is a separate package loaded via import maps where the alias is defined.
service() helperAPI services are singletons shared across all micro-frontends via ServiceProvider:
const repoService = ServiceProvider.get(RepositoryContextService);
// `service()` is an exported convenience alias for the same thing:
const repoService = service(RepositoryContextService);
Use ServiceProvider.get() / service() for API-layer services everywhere (Stencil components, root-config, and Angular components when Angular DI is unavailable). For Angular services registered through Angular DI (@Injectable), use inject() as normal.
new a class with logic
(resolver, strategy, handler) inside another class; get it via service() or pass it in, so tests can swap it.GraphDB can run behind a reverse proxy under a path prefix (https://example.com/graphdb/, with
graphdb.external-url set to that URL). Every page, asset and REST call must then stay under the prefix. Changes to
routing, navigation or URL building break this most often.
<base href> is the source of the prefix. root-config/src/index.ejs ships <base href="/">; GraphDB serves
it rewritten to the prefix, always with a trailing slash. Read it through getBasePath() (@ontotext/workbench-api,
routing-utils.ts), never from location. The modern workbench feeds it to Angular as APP_BASE_HREF.href="monitor/queries", rest/repositories. The browser resolves them
against <base href>, so they pick up the prefix. A root-absolute /monitor/queries escapes it.navigate() / navigateTo(). navigate() prepends the prefix to a root-absolute path
and resolves the rest against getOrigin() (origin + base path). Build absolute URLs with buildUrl().getPathName() / getCurrentRoute() when comparing against routes.verify-workbench skill on a context stack
(container.sh up --context /graphdb, then check-paths.sh); its features/context-path.md lists what to drive.