Docs

Docs

Local Emulator

A toggle inside the cPod portal that flips every EDM call from the real backend to local seed data. No separate process. No Docker. No MongoDB. No env config beyond a single feature flag.

Try it now → Open the portal in Emulator mode (assumes the portal is running on :4000 — see Quickstart below)


What's emulated, what's not#

Only EDM domain endpoints (/api/v1/<edm-domain>/...) are intercepted. Everything else passes through to the real backend.

Path shapeEmulator modeWhy
/api/v1/crm/*, /api/v1/helpdesk/*, /api/v1/hr/*, etc.emulatorThese are the typed EDM CRUD endpoints — the surface devs build apps against.
/api/v1/registry/*liveRegistry data lives in the real DB; emulating it would mask real state.
/api/v1/auth/*, /api/v1/admin/*liveAuth, tenants, sessions — system of record only.
/api/portal/me, /api/portal/settingslivePortal metadata.

That's the rule the proxy route enforces. Even when you flip the toggle, registry / auth / admin / settings calls always go to the real backend — the inspector still tags them with mode=emulator so you can see exactly what hit live vs what didn't.

Why this exists#

When you're building a cPod app, you want to:

  • See the exact request that goes out and the response that comes back.
  • Try create → list → update → delete flows on EDM data without spinning up the backend.
  • Hand a teammate a working portal that runs anywhere with git clone && pnpm install && pnpm dev.

The Emulator gives you all three from a chip in the portal header.

How it works#

code
Toggle ON + EDM path  → /api/portal/proxy → in-portal emulator (JSON seeds + in-memory store)
Toggle ON + non-EDM   → /api/portal/proxy → forward to Python backend (unchanged)
Toggle OFF            → /api/portal/proxy → forward to Python backend (unchanged)
  • The toggle sends an x-cpod-mode: emulator header on every call.
  • The proxy route checks the path's first segment against an EDM-domain allowlist. Only EDM paths are dispatched to the emulator.
  • The emulator module is dynamic-imported — when the toggle is off, none of it is loaded.
  • Data lives in JSON files under portal/src/lib/emulator/seeds/edm/. Each file is one collection, keyed by URL path slug (e.g. crm-accounts.json/api/v1/crm/accounts). Hand-editable.
  • Writes mutate an in-memory layer on top of the seeds. "Reset data" in the inspector rolls back to the seed JSONs.
  • Resets also happen automatically when the Next.js process restarts (HMR, code edits).

Quickstart#

Enable the feature flag#

Add to portal/.env.local:

bash
NEXT_PUBLIC_EMULATOR_ENABLED=true

Without this flag the toggle chip is hidden and the portal behaves exactly as it did before. Safe default for prod.

Start the portal#

bash
cd cpod-sdk/portal
pnpm install
pnpm dev

Portal is at http://localhost:4000.

Flip the toggle#

In the top-right header chip, click Emulator. The chip turns amber.

You can also land directly in Emulator mode by linking to /portal?mode=emulator — the URL param wins over localStorage on load.

Open the Inspector#

Click Inspector in the header. A docked panel slides up showing every API call made through the portal, with:

  • Method · path · status · latency · mode (live / emulator)
  • Click any row → side-by-side request and response JSON
  • Copy buttons on both sides
  • Reset data button (Emulator mode only) → rolls in-memory writes back to seeds

What's emulated (EDM domains)#

Any path under one of these prefixes is served by the emulator when the toggle is on:

code
approvals · catalog · cloud-resources · compliance-controls · contracts ·
credentials · crm · datasources · edm-projects · employee · entitlements ·
finance · grc · group-metadata · groups · helpdesk · hr · integration ·
investments · knowledge · learning · licenses · notifications · okr · org ·
people · performance · physical-assets · policies · procurement ·
relationships · rfp · risk · soc · storage · technology · vulnerabilities ·
work

Every domain supports the five standard verbs via one generic CRUD handler:

MethodPath shapeEmulator behavior
GET/api/v1/<domain>/<entity>Return all rows from the seed file, paginated if page/pageSize is in the query.
GET/api/v1/<domain>/<entity>/<id>Return the row, or 404 Not found.
POST/api/v1/<domain>/<entity>Generate id, append to in-memory layer, return 201.
PATCH / PUT/api/v1/<domain>/<entity>/<id>Merge patch into the row.
DELETE/api/v1/<domain>/<entity>/<id>Remove from in-memory layer.

Endpoints without a seed file start empty — GET returns [], POST works, you're off to the races.

Editing seed data#

Files live at portal/src/lib/emulator/seeds/edm/. Filename = URL path slug. Examples:

code
seeds/edm/
├─ crm-accounts.json         (/api/v1/crm/accounts)
├─ crm-contacts.json         (/api/v1/crm/contacts)
├─ crm-deals.json            (/api/v1/crm/deals)
├─ helpdesk-tickets.json     (/api/v1/helpdesk/tickets)
├─ hr-employees.json         (/api/v1/hr/employees)
└─ work-tasks.json           (/api/v1/work/tasks)

Each file is a flat JSON array of records. Every record needs an id field; everything else is up to your schema.

Add a new seed file → pnpm dev HMR reloads → the next request to that path returns your data.

Custom verbs#

The generic dispatcher only knows the five CRUD verbs. EDM endpoints with custom verbs (/publish, /decide, /complete, sub-resource paths like /tickets/{id}/comments) hit the fallback handler and return:

json
{ "title": "Not emulated", "status": 404,
  "detail": "... doesn't match the generic EDM ... pattern. Custom verbs ... need a bespoke handler in src/lib/emulator/dispatcher.ts." }

To add one, append a route in portal/src/lib/emulator/dispatcher.ts. Pattern + handler. That's the whole change.

Safety#

  • The toggle chip is gated by NEXT_PUBLIC_EMULATOR_ENABLED. In prod/staging it's not set → no chip, no emulator code path, identical to today.
  • The _emulator field on responses is the easiest way to verify which world you're talking to (status + latencyMs come back from the in-portal emulator only).
  • Seeds are committed to git — your team can replay each other's state by sharing JSON edits.
  • Registry, auth, admin, and settings calls always go to the real backend, even when the toggle is on. There's no way for the emulator to accidentally serve identity or registry data.