Nymfonicat/ coreSymfonicat
symfonicat/node

Nymfonicat

The multi-tenant runtime, rebuilt for Node.js.

Nymfonicat is a Node.js multi-tenant frontend runtime built with Express 5, Pug, MySQL through Knex, Redis-backed sessions, Webpack, Nginx, Mercure, Composer-managed hybrid packages, and native C/Go build hooks. It preserves Symfonicat’s domain, subdomain, endpoint, parcel, module, middleware, environment, and application model.

71documented project files
13runtime dependencies
14core route groups
5environment scopes

# Mental model

Nymfonicat has a database-backed control plane and a JSON-backed public runtime. The protected core UI changes MySQL rows, then syncs them to config/nymfonicat.json. Public requests resolve their composition from that generated configuration.

REQUESTHost + pathExpress 5 catch-all
CONTEXTMatcherdomain · subdomain · endpoint
COMPOSITIONScope stackparcel · env · module · middleware
RESPONSEPug rendererHTML through middleware
Domain

A bare host and top-level runtime shell.

Subdomain

A reusable affix with its own runtime composition.

Endpoint

A literal/wildcard path pattern with optional trailing catch.

Parcel

The frontend entry used to render a runtime scope.

Module

An attached browser feature with a registered Node back end.

Application

An optional domain, subdomain, or endpoint context.

# Quick start

bash
git clone https://github.com/symfonicat/node nymfonicat
cd nymfonicat
cp .env.example .env
docker compose up -d --build
touch nymfonicat.lock

The app entrypoint applies pending migrations automatically. When the core tables are empty, it also seeds the example catalog and writes config/nymfonicat.json. Set NYMFONICAT_AUTO_BOOTSTRAP=0 when you want to manage that lifecycle manually.

Local routing

Add 127.0.0.1 example.com and 127.0.0.1 subdomain1.example.com to /etc/hosts. Nginx publishes port 80 only after the Express health check passes. The core area remains disabled until nymfonicat.lock exists.

# Public runtime

The Express catch-all resolves the active domain, subdomain, endpoint, and optional application context. Core routes, module routes, build assets, and explicit Express routes are mounted first so the public renderer cannot swallow them.

  1. Resolve hostA matched domain renders its domain shell; a matched affix selects the subdomain shell.
  2. Match endpointRepeatable arguments accept literals or *; catch permits additional path segments.
  3. Merge scopeParcel, modules, middleware, and environment values are assembled for the active context.
  4. Render PugDomain, subdomain, endpoint, or application templates produce the response.
templates/domain/main.pugtemplates/subdomain/main.pugtemplates/endpoint/main.pugtemplates/application/main.pug

# Core administration

/core redirects to /core/d. Server-rendered Pug screens with Bootstrap styling manage domains, subdomains, endpoints, parcels, middleware, applications, environment values, file uploads, and JSON synchronization.

/core/d

Domains

/core/s

Subdomains

/core/e

Endpoints

/core/p

Parcels

/core/m

Middleware

/core/a

Applications

/core/env

Environment groups and values

/core/f

File uploads

JSON dump/load actions live at /core/y/dump and /core/y/load; /core/u is the sync alias.

# Modules and the hybrid package system

Modules attach to Domains, Subdomains, or Endpoints. Browser requests post to /m/{module-id}; the router executes only a registered module attached to the active scope and reconstructs context from Nymfonicat or Symfonicat-compatible headers. Bodies and responses support ordinary JSON plus the shared Brotli media types.

javascript
'nymfonicat/core/main'.log('module active!')

const result = await 'nymfonicat/core/main'.json({
    test: true,
})

'nymfonicat/core/main'.log(
    '/m/nymfonicat/core/main result:',
    result,
)

One package, two server runtimes

Symfonicat and Nymfonicat use the same Composer-installed feature package. The package owns one public module id in vendor/package/name form and one set of browser assets, but supplies a runtime-specific server adapter: PHP under src/ for Symfony and Node.js under modules/ for Express.

text
symfonicat/analytics/
├── composer.json                 # Composer discovery metadata
├── package.json                  # optional Node package metadata
├── assets/
│   ├── module/main/index.js     # one browser module for both runtimes
│   └── parcel/analyticsparcel/index.js
├── modules/main.js               # Nymfonicat Node.js backend
└── src/Module/AnalyticsModule.php # Symfonicat PHP backend
Shared module frontend

assets/module/main/index.js is compiled and served by both runtimes. It calls the same module id and shared .json(), .html(), and .log() browser API.

Shared parcel frontend

assets/parcel/analyticsparcel/index.js is the reusable shell entry. The same parcel source can implement a Domain, Subdomain, or Endpoint in either runtime.

Nymfonicat backend

modules/main.js handles the module request in Node.js and receives the reconstructed runtime context.

Symfonicat backend

src/Module/AnalyticsModule.php handles the equivalent request through attributed Symfony module routes.

Discovery and build flow

  1. Install onceComposer places the hybrid package under vendor/vendor/package.
  2. Opt inNymfonicat accepts nymfonicat.packageName metadata or Composer extra.symfonicat: true.
  3. Discover both layersPackage discovery registers the Node backend and finds module and parcel entries under the installed package.
  4. Compile shared assetsThe Composer helper runs Webpack before app startup; active scopes receive only their selected parcel and attached modules.
  5. Dispatch locallyThe browser calls one stable module id. Nymfonicat routes it to the Node backend; Symfonicat routes the same feature contract to PHP.
Compatibility contract

Keep ids, action names, payload shapes, response formats, and browser behavior aligned between the PHP and Node implementations. Server code is intentionally separate; assets/module and assets/parcel are intentionally shared.

Middleware order

Domain middleware runs first, followed by Subdomain and Endpoint middleware. Each Node module receives { context, req, res, response, middleware } and may mutate context, return response fields, or send through Express directly.

# Environment resolution

Environment keys are grouped by parent and exposed as readable nested browser JSON.

javascript
window.env = {
    "colors": {
        "primary": "blue"
    }
}
  1. Parcelbase value
  2. Domaintenant override
  3. Subdomainaffix override
  4. Endpointpath override
  5. Applicationapplication override

# Assets and packages

Webpack scans the root and opted-in Composer packages for module and parcel entries. It builds public/build/app.js, public/build/app.css, and discovered bundles. Active runtime scopes receive only their active modules and parcel tags.

Discovery roots

assets/module/, assets/parcel/, assets/parcels/, and their package equivalents under vendor/**/**.

Composer build gate

The one-shot Composer service installs hybrid packages and runs the Webpack build before the app starts. Both services share the image-seeded node_modules volume, while Composer packages stay on the vendor bind mount.

# Configuration

The public runtime reads config/nymfonicat.json. npm run sync serializes the MySQL nymfonicat_* tables into that file. A checked-in config/nymfonicat.example.json supports local experiments and tests.

json
{ "nymfonicat": { "packageName": "vendor/package" } }

{ "extra": { "symfonicat": true } }

# Database lifecycle

Automatic bootstrap is idempotent: every app start runs migrations, but seed and sync run only when the parcel, domain, and endpoint tables are all empty. The same commands remain available for explicit control.

npm run migrate

Apply Knex migrations for runtime resources, scoped environment values, middleware/module joins, and uploads.

npm run seed

Load example domain, subdomain, endpoint, application, env, middleware, module, and parcel rows.

npm run sync

Serialize MySQL state into deterministic runtime JSON.

npm run build

Discover root and Composer package entries and compile the Webpack asset graph.

# Native C and Go hooks

C MODULESnode-gyp

Projects under native/c/**/binding.gyp compile as Node native addons.

GO MODULESGo binaries

Projects under native/go/**/go.mod compile to native/go/{name}/bin/{name}.

core/native supplies JavaScript fallbacks when native artifacts have not been built.

# Mercure

The Compose stack includes a Mercure hub. Server publishing uses MERCURE_URL and MERCURE_JWT_SECRET; public pages receive MERCURE_PUBLIC_URL through window.nymfonicat.mercurePublicUrl.

# Complete 71-file atlas

All 71 project files that implement or configure the documented runtime are indexed below. Vendored Bootstrap distribution files are intentionally excluded.

Request pathExpress → matcher → context → middleware → Pug.
Control pathCore router → forms → Knex → sync → JSON.
Package pathDiscovery → module/parcel entries → Webpack/native build.
Runtime entry2 files
Admin4 files
Routing3 files
Modules7 files
Runtime services9 files
Database4 files
Templates14 files
Assets11 files
Build and operations9 files
Native examples4 files
Configuration4 files

# Runtime checks

bash
npm run check

The check initializes Express without Redis, verifies endpoint argument serialization, confirms the seeded middleware attaches to example.com, and executes that middleware through the runtime runner.