DynoStaticAdmin sign in
Documentation

Architecture

Architecture

DynoStatic is a PSR-4 modular monolith with a manual composition root. Production requests enter through physical PHP gateways (index.php, admin/index.php, api/index.php, install/index.php, health.php, and integration.php) so Apache/Nginx rewrites are never required.

Ownership model

website โ†’ exact address โ†’ widget โ†’ publication

The website is the ownership and permission boundary. Website owners and moderators manage its widgets, origins, discussions, and settings; viewers are read-only. Platform owners and administrators span websites. Every public read resolves the widget, active website, exact Origin, and current publication before it accesses module data.

Widget lifecycle

ModuleRegistry is the catalog and configuration contract. Each definition fixes a stable slug, category, rendering engine, field schema, defaults, write behavior, readiness class, and verified capability list. Only ready, official_embed, and link_only definitions can create widgets; previews and integrations without a completed connection remain disabled. ModuleDefinition::supports() is the common feature-discovery boundary. normalize() is the server-side boundary for drafts. Browser preview values are never trusted directly by the renderer.

Draft saves use an expected revision. A stale editor receives HTTP 409 rather than silently overwriting newer work. Publishing writes an immutable JSON snapshot and advances the widget's publication pointer. Public dynamic data, such as approved comments or poll totals, has a separate content version so ordinary draft edits never leak and ordinary analytics do not churn public ETags.

The public renderer emits sanitized HTML. The loader mounts it into Shadow DOM by default and loads the managed CSS inside that root. Light-DOM mode emits the same semantic HTML without requesting or injecting CSS.

Headless CMS (separate from widgets)

DynoStatic also provides a website-scoped headless CMS for static-site generators. CMS schemas, draft entries, immutable publications, media, and API tokens live in dedicated cms_* tables. The Content API (api/?action=cms.*) authenticates with Bearer tokens for CI builds; it does not use widget Origin rules.

Boundary: CMS documents are site content the SSG renders at build time (posts, settings, pages). Widgets remain runtime embeds (comments, forms, polls) loaded by embed.js. Do not store CMS pages as widgets. See cms.md and foundations.md.

Spine: Web requests compose through SharedKernel (one SiteRepository, JobQueue, and cache facade). Feature packages implement AppModule (Widget, Discussion, Cms, Admin, Platform); Application only orchestrates boot/routes/handlers. Admin controllers use AdminGate directly. Gateway query maps and Router paths share RouteCatalog. Job kinds are JobHandler classes registered via modules (src/Jobs/Handlers/, CMS fan-out). DiscussionKernel requires SharedKernel. Cron (bin/dynostatic cron) is recommended for timely CMS rebuild webhooks; the web runner is best-effort.

Runtime data

Internal keys are unsigned BIGINT; public identifiers are ULIDs. Times are UTC DATETIME(6). JSON is stored as LONGTEXT for consistent MySQL 8/MariaDB 10.6 behavior and decoded through a throwing boundary. Text uses utf8mb4.

Specialized tables own interactive data: submissions, votes, comments/reactions, review sources/reviews, consent audit, daily anonymous visitors, metrics, jobs, and audit events. Modules do not reach into each other's tables. Shared services own origins, crypto, tickets, uploads, outbound requests, rendering, and publication.

Hosting model

The only required production processes are PHP and MySQL/MariaDB. The filesystem cache and protected dynostatic-content/ directory work on one ordinary host. CLI commands improve predictability but are not required for serving widgets. The implementation has no application-server JavaScript and no build step for browser assets.