@panomapp/mesh
v1.0.1
Published
Application mesh runtime for multi-instance Node.js apps, local orchestration, Podman deployment, routing, observability and distributed coordination.
Maintainers
Readme
@panomapp/mesh
Application mesh runtime for multi-instance Node.js apps.
@panomapp/mesh gives one config file and one CLI for running frontend, backend, worker and router processes behind a mesh gateway. It supports local process orchestration, health-aware reverse proxying, sticky sessions, Redis-backed service discovery, graceful drain, Podman/Quadlet generation, HSM route mapping, terminal observability, distributed log streaming, distributed locks, leader election and cleanup task primitives.
Docs
For the book-style documentation, start here:
Installation
npm install -D @panomapp/meshInitialize a project:
npx @panomapp/mesh initThis creates mesh.config.ts and package scripts such as mesh:run, mesh:run:all, mesh:ps, mesh:watch, mesh:dashboard, mesh:stream, mesh:locks, mesh:leaders, mesh:cleanup and mesh:stop.
Quick start
import { defineMeshConfig } from '@panomapp/mesh'
export default defineMeshConfig({
app: 'my-app',
router: {
port: 8080,
sessionAffinity: true,
secret: process.env.MESH_SECRET ?? 'dev-only-change-me'
},
runtime: {
mode: 'process',
portRange: { from: 3100, to: 3999 },
defaultWatch: true
},
services: {
frontend: {
type: 'frontend',
command: 'npm run dev',
cwd: './frontend',
instances: 1,
route: '/',
port: 5173
},
api: {
type: 'backend',
command: 'npm run dev',
cwd: './backend',
instances: 3,
route: '/api',
healthPath: '/health',
strategy: 'session-affinity'
},
worker: {
type: 'worker',
command: 'npm run worker',
cwd: './backend',
instances: 1,
watch: false
}
}
})Run everything:
npm run mesh:run:allRun only the backend service:
npm run mesh:run -- apiScale one service temporarily:
mesh run api --instances 10List instances:
mesh psWatch by unique id prefix:
mesh watch api-a7Local HTTPS development
Mesh can expose a native HTTPS router for browser-facing local development. Configure router.tls, then generate a trusted certificate with mesh cert:init.
export default defineMeshConfig({
app: 'my-app',
router: {
host: 'dev.my-app.com',
port: 3000,
secret: process.env.MESH_SECRET ?? 'dev-only-change-me',
tls: {
enabled: true,
certPath: '.mesh/certs/dev.my-app.com.pem',
keyPath: '.mesh/certs/dev.my-app.com-key.pem',
minVersion: 'TLSv1.2',
additionalPorts: [443]
}
}
})mesh cert:initWhen TLS is enabled:
- Mesh uses
https.createServer(...)for the public router listeners. x-forwarded-protois derived from the real incoming connection (httporhttps).- Sticky session cookies default to
Secure. - Additional TLS listener ports are supported alongside the primary router port.
For a privileged macOS 443 listener, generate first-class launchd artifacts:
mesh launchd:generate
sudo ./.mesh/launchd/install-router-443.shThis creates a root-owned launchd job that runs only the Mesh router on 443 while your normal developer session continues to own the fallback https://host:3000 listener.
Mesh router
The router is a managed mesh instance. It reverse-proxies requests to healthy service instances based on route ownership.
client → mesh-router:8080 → api-1 / api-2 / api-3Routing is health-aware. Draining, expired and stopped instances are excluded. session-affinity uses an HMAC-signed sticky cookie, so clients cannot forge a node target.
Supported strategies:
round-robinleast-connectionssession-affinity
HTTP and WebSocket upgrades are supported.
Registry
The default registry is file-based and suitable for local development. Redis is recommended for multi-process, Podman or multi-host deployments.
export default defineMeshConfig({
app: 'panom',
router: { secret: process.env.MESH_SECRET! },
registry: {
type: 'redis',
url: process.env.REDIS_URL!,
secret: process.env.MESH_SECRET!,
requireSignature: true,
heartbeatIntervalMs: 5_000,
ttlMs: 15_000
},
services: {
api: { command: 'node server.js', route: '/api', instances: 4 }
}
})Redis records are signed when a secret is configured. Expired nodes automatically fall out of routing.
Distributed coordination
Mesh includes distributed coordination primitives for multi-instance apps.
Locks
import { LockFactory } from '@panomapp/mesh/locks'
const locks = new LockFactory().createManager(config, 'api-1')
await locks.runExclusive('media:123:process', async () => {
await processMedia('123')
}, {
ttlMs: 30_000,
waitMs: 5_000,
metadata: { mediaId: '123' }
})Redis locks are owner-scoped and released only by the owner. Lease renewal is supported for long-running work.
Leader election
import { LeaderElection } from '@panomapp/mesh/leader'
const leader = new LeaderElection(locks, 'worker-1')
await leader.runWhenLeader('cleanup', async signal => {
while (!signal.aborted) {
await cleanupExpiredFiles()
await new Promise(resolve => setTimeout(resolve, 60_000))
}
})Leader election is built on the same lock lease mechanism and is useful for cron-like tasks that must not run on every backend instance.
Cleanup scheduler
import { CleanupScheduler } from '@panomapp/mesh/cleanup'
const cleanup = new CleanupScheduler({ locks, leader })
cleanup.task('temp-media', {
intervalMs: 10 * 60_000,
leader: true,
lockKey: 'cleanup:temp-media',
maxRuntimeMs: 5 * 60_000,
run: async ({ signal }) => {
await deleteExpiredTempMedia({ signal })
}
})
cleanup.start()The scheduler is app-agnostic. It does not delete anything by itself; it provides safe execution rules for your cleanup code.
Observability
Open the terminal dashboard:
mesh dashboardRender once:
mesh dashboard --once
mesh dashboard --jsonDashboard sections include router metrics, services, instances, route plan, HSM-derived routes, active locks and leaders.
Distributed log/event streaming:
mesh stream
mesh stream api-a7
mesh stream --service api --kind log
mesh watch api-a7 --streamWith Redis streaming enabled, logs/events from multiple processes or hosts can be consumed from one terminal.
Podman and Quadlet
Podman mode is designed for a single VM deployment where you want repeatable multi-instance containers without hand-writing proxy config.
export default defineMeshConfig({
app: 'panom',
router: { port: 8080, secret: process.env.MESH_SECRET! },
registry: {
type: 'redis',
url: 'redis://panom-redis:6379',
secret: process.env.MESH_SECRET!,
requireSignature: true
},
runtime: {
mode: 'podman',
podman: {
network: 'panom-mesh',
containerPrefix: 'panom',
redis: { enabled: true, containerName: 'panom-redis' },
quadlet: { outputDir: '.mesh/quadlet', user: true }
}
},
services: {
api: {
type: 'backend',
image: 'ghcr.io/you/my-api:latest',
command: 'node dist/server.js',
route: '/api',
port: 3000,
instances: 3,
healthPath: '/health',
strategy: 'session-affinity',
podman: { containerPort: 3000 }
}
}
})Commands:
mesh podman:plan
mesh podman:generate --force
mesh run --all
mesh podman:stop apiHSM bridge
If you use @panomapp/hsm, Mesh can derive route ownership from a compiled HSM schema.
export default defineMeshConfig({
app: 'panom',
hsm: {
schemaPath: './hsm.schema.json',
routeMode: 'both',
mappings: [
{ service: 'frontend', tags: ['public', 'app'], includeBackendRoutes: false },
{ service: 'api', states: ['app.*', 'cloud.*'], includeCanonicalRoutes: false }
]
},
services: {
frontend: { type: 'frontend', command: 'npm run dev', route: '/' },
api: { type: 'backend', command: 'npm run dev', route: '/api' }
}
})Inspect the derived plan:
mesh hsm:plan
mesh hsm:plan --jsonCLI reference
mesh init
mesh run [service]
mesh run --all
mesh ps [--json]
mesh watch <id-prefix> [--stream]
mesh stream [id-prefix]
mesh dashboard [--once] [--json] [--logs]
mesh locks [--json]
mesh leaders [--json]
mesh cleanup [--json]
mesh stop [service|id-prefix]
mesh hsm:plan [--json]
mesh podman:plan [--json]
mesh podman:generate [--force]
mesh podman:stop [target]Security notes
- Use a strong
MESH_SECRETin any shared or production environment. - Do not use
dev-only-change-meordev-only-mesh-secretoutside local development. - Prefer Redis registry with
requireSignature: truewhen nodes register across processes, containers or hosts. - Keep backend instances on an internal network; expose the mesh router through Nginx/Caddy or another TLS edge.
- Sticky session cookies are HMAC-signed but should not be treated as authentication.
- Frontend checks are UX only. Backend authorization remains mandatory.
- Tune request timeouts, body limits and drain timeouts for your application.
Exports
@panomapp/mesh
@panomapp/mesh/config
@panomapp/mesh/runtime
@panomapp/mesh/router
@panomapp/mesh/registry
@panomapp/mesh/registry/redis
@panomapp/mesh/node
@panomapp/mesh/events
@panomapp/mesh/drain
@panomapp/mesh/podman
@panomapp/mesh/hsm
@panomapp/mesh/observability
@panomapp/mesh/streaming
@panomapp/mesh/locks
@panomapp/mesh/leader
@panomapp/mesh/cleanupLicense
MIT
