Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1bf9f0e
feat(api): add command registry endpoint and update API configuration
geeewhy Jun 8, 2025
5f7c082
refactor(core): improve JSON Schema conversion and enhance type handling
geeewhy Jun 8, 2025
6f1a78c
feat(setup): add api and test user creation and related perms along w…
geeewhy Jun 9, 2025
e53819c
feat(api): initialize database connection and implement graceful shut…
geeewhy Jun 9, 2025
5a6a343
feat(api): add metrics routes and access logging middleware
geeewhy Jun 9, 2025
62f862b
feat(api): wire commands api
geeewhy Jun 9, 2025
3f899c9
feat(api): add command scheduling, noted improvements for devx in com…
geeewhy Jun 10, 2025
3c42353
feat(core): add aggregate routing support and lint tool
geeewhy Jun 10, 2025
27d06ca
feat(ui): add role switch, enhance cmd highlighting
geeewhy Jun 10, 2025
342d72e
feat(logger): integrate apiLogger into middleware and routes
geeewhy Jun 10, 2025
e221c8d
feat(core): add roles registration and API endpoints
geeewhy Jun 10, 2025
c5955f4
feat(roles): add roles management and domain-based query
geeewhy Jun 10, 2025
223cd69
feat(ui): improve tenant handling and role selection
geeewhy Jun 10, 2025
eda89c9
docs(ADR): introduce local DevX companion UI ADR
geeewhy Jun 10, 2025
37e9794
feat(ci): add DevX UI build workflow
geeewhy Jun 10, 2025
633a003
feat(core): enhance logging, policy, and linting robustness
geeewhy Jun 10, 2025
75ad101
refactor(core-lint): clean up imports
geeewhy Jun 10, 2025
3c685f2
docs(tools): add core domain linting tool READMEs
geeewhy Jun 10, 2025
54733b2
feat(error-handling): enhance error serialization and structure
geeewhy Jun 11, 2025
c62f2d0
refactor(core/ui): simplify imports and clean unused code, more flexi…
geeewhy Jun 11, 2025
e9be57c
refactor(logger): reduce callstack
geeewhy Jun 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build
name: Core and Infra Build

on:
pull_request:
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/core-linter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Core Linter

on:
pull_request:
branches:
- '**'

jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: '22'
cache: 'npm'

- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-

- run: npm ci
- run: npm run tool:core-lint
28 changes: 28 additions & 0 deletions .github/workflows/devx-ui-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: DevX Build

on:
pull_request:
branches:
- '**'

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: '22'
cache: 'npm'

- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-

- run: npm ci
working-directory: devex-ui
- run: npm run build
working-directory: devex-ui
63 changes: 63 additions & 0 deletions ADRs/021-core-aggregate-routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# ADR 021: Co-locate Aggregate Routing with Command Registration

## Context

The system currently registers command types separately from their routing logic. Routing to aggregates (i.e., deriving `aggregateType` and `aggregateId` from a command) is either inferred or redundantly handled elsewhere. This introduces implicit coupling and runtime ambiguity, especially for commands triggered via sagas or external workflows.

In particular:

* Aggregates rely on `aggregateId` and `aggregateType` being available in payloads.
* Command metadata lives in `DomainRegistry.commandTypes()` but lacks routing.
* Workflows (`processCommand`) must inject routing metadata for proper aggregate reconstruction.
* Ambiguous `aggregateId` primary key mappings

This violates the principle of co-location and increases the risk of drift between command schemas and routing logic.

## Decision

Extend `CommandTypeMeta` to include an optional `aggregateRouting` key:

```ts
aggregateRouting?: {
aggregateType: string;
extractId: (payload: any) => UUID;
};
```

Update all `registerCommandType()` calls (e.g., in `system/register.ts`) to include routing metadata where applicable.

During workflow dispatch in `WorkflowRouter.handle(cmd)`, inject routing data into command payloads before processing:

```ts
const meta = DomainRegistry.commandTypes()[cmd.type];
const routing = meta?.aggregateRouting;

if (routing) {
cmd.payload.aggregateType ??= routing.aggregateType;
cmd.payload.aggregateId ??= routing.extractId(cmd.payload);
}
```

## Consequences

### Pros

* Declarative routing co-located with command type definition
* Avoids ad-hoc inference of `aggregateType` / `aggregateId` at runtime
* Enables automated analysis (e.g., lint-payloads tool) to verify routing completeness
* Respects open/closed principle — saga/workflow code need not change per command type

### Cons

* Requires updating all `registerCommandType()` calls with routing
* Minor learning curve: developers must now specify `aggregateRouting` explicitly

## Alternatives Considered

* **Central Routing Table**: Rejected — adds indirection, risks desync, harder to statically analyze.
* **Assume client prepopulates routing**: Rejected — contradicts encapsulation; workflows should enforce context, not leak it.

## Implementation Notes

* A lint tool (`src/tools/lint-payloads.ts`) will enforce that all registered command types with `payloadSchema` also declare `aggregateRouting` (unless explicitly marked as saga-only).
* This ADR aligns with system goals of deterministic routing, explicit boundaries, and tooling-aware metadata propagation.
104 changes: 104 additions & 0 deletions ADRs/022-devx-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# ADR-022: **Local Side-panel Dev-X Companion**

## 1 Context

* The platform uses a ports-first, event-sourced architecture. Commands and events are routed through the same deterministic workflows used in production.
* Developers previously relied on disparate tools (shell, `psql`, Temporal Web UI, Jaeger) to understand or simulate system behavior.
* Postgres `LISTEN` channels (`new_event`, `new_command`) already feed into the local Temporal workers and projections.
* Devs requested a unified, **local-first** DevEx console that enables:

1. Inspection, validation, and emission of commands/events
2. Viewing projection and trace state in real-time
3. Structured AI-assisted scaffolding (types, sagas, events, projections)
4. Replay/debug capabilities from the event log
* The UI must reflect actual runtime constraints: multi-tenant, role-restricted command access, and full audit of side effects.

---

## 2-Decision

### 2.1 High-Level View

```
┌─────────────┐
│ DevEx UI SPA│ ← Tabs: Commands | Events | Traces | Replay | Projections | AI
└─────┬───────┘
│ REST + SSE/WS
┌─────▼──────────────┐
│ /api (ports-only) │ ← no direct DB access; calls EventStorePort, SagaRegistry, etc.
│ + /api/ai-proxy │ ← calls LLM with scoped context
└─────┬──────────────┘
┌─────▼────────────┐
│ Postgres LISTEN │ ← streams new_command + new_event (if WS)
└─────┬────────────┘
┌──────────────┐
│ AI + CLI │ ← Generates `.patch` files for types & handlers (later ADR)
└──────────────┘
```

---

### 2.2 Detailed Design Points

| Aspect | Decision |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Frontend** | SPA under `src/devex-ui/`; includes tabs for Commands, Events, Projections, Traces, Aggregates, Rewind, AI. |
| **Command issuer** | Fully type-aware with schema-based form generation. Role dropdown adapts dynamically to selected command domain, ensuring only valid roles appear. |
| **Role validation** | If selected role isn’t part of the domain's available roles, it is reset; the role selector UI reflects this with dynamic placeholders (“Select a role” or “Roles populate…”). |
| **Registry source** | `commandRegistry`, `rolesStore`, `eventRegistry`, `projectionRegistry`, `sagaRegistry` are exported from `core/registry.ts`; no hardcoded lists outside source-of-truth. |
| **Backend façade** | `/api/*` routes use ports only (never call DB/Temporal directly). AI and scaffolding features run isolated from runtime mutations. |
| **AI Assist** | `/api/ai-proxy` sends scoped context (selected domain/contract/slice) to LLM. Scaffold output includes `.patch` files only, with no auto-commit. |
| **Replay + simulate** | Event streams are replayed against in-memory aggregates; what-if diffs are computed and shown as JSON state patches. |
| **Security** | No JWT in local mode; RLS still enforced if using a remote DB. |
| **CLI parity** | `devx ui` launches the SPA using same `.env` as CLI + workers. |

---

## 3-Behavior Summary

### Commands Tab

* Shows all available commands grouped by domain
* Selecting a command shows schema-driven form and available roles (from `useRoles(domain)`)
* Role selector resets if invalid; UI shows context-aware placeholder
* On submission, command is POSTed to `/api/commands`, processed via normal ports
* After dispatch, the corresponding trace is auto-expanded via correlation ID

### AI Side-panel

* **Chat Mode**: query behavior/state over event stream or aggregate
* **Scaffold Mode**: wizard to generate new commands/events/sagas/types
* **Simulator Mode**: run a hypothetical event chain through a rehydrated aggregate and preview resulting state diff

---

## 4-Consequences

| | Positive | Trade-offs / Mitigations |
| --------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
| **Unified DevEx dashboard** | Full visibility into all domain slices, including real-time traces, without context switch | WebSocket LISTEN loop may become noisy → supports throttle env var in `.env` |
| **Determinism preserved** | All command submissions flow through existing routing and validation | Incomplete schema = validation failure; schema coverage enforced at registry level |
| **Safe scaffolding** | `.patch` files are generated but not committed | Prevents unsafe auto-writes; CI ensures schema + unit coverage post-patch |
| **Zero infra requirement** | Works with docker-compose stack + local Postgres | If pointing to shared DB, advise dev pods only run LISTEN to avoid shared fan-out noise |
| **Context-aware UI** | Domain → Roles → Payload schema chaining enforced in UI | Role edge cases must be handled when registry mismatches occur (e.g. outdated role list) |

---

## 5-Rejected Alternatives

| Option | Reason for Rejection |
| ------------------------ | --------------------------------------------------------------------------------------- |
| Full Electron App | Unnecessary packaging overhead; SPA already integrates cleanly with CLI and local stack |
| Retool / External Studio | Breaks invariants, bypasses RLS and command auditing |
| Auto Git Commits | All scaffolds must be reviewed; early .patch outputs are often exploratory |

---

## Future Considerations

1. Add `useRoles(domain)` + auto-reset logic into CommandIssuer UI
2. Wire WS/SSE `/api/stream/commands` and `/api/stream/events` to feed DevEx console tabs
3. Enable `.patch` output from scaffold mode and render diff preview before saving...AI scaffolding begs for its own ADR
4. Add in-memory replay of event chains into “What-if Simulator” tab...begs for its own ADR
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

[![CI](https://github.com/geeewhy/intent/actions/workflows/build.yml/badge.svg)](https://github.com/geeewhy/intent/actions/workflows/build.yml)
[![CI](https://github.com/geeewhy/intent/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/geeewhy/intent/actions/workflows/unit-tests.yml)
[![CI](https://github.com/geeewhy/intent/actions/workflows/core-linter.yml/badge.svg)](https://github.com/geeewhy/intent/actions/workflows/core-linter.yml)
[![CI](https://github.com/geeewhy/intent/actions/workflows/projection-linter.yml/badge.svg)](https://github.com/geeewhy/intent/actions/workflows/projection-linter.yml)
[![CI](https://github.com/geeewhy/intent/actions/workflows/devx-ui-build.yml/badge.svg)](https://github.com/geeewhy/intent/actions/workflows/devx-ui-build.yml)

> **Intent** turns event-sourcing theory into a platform you can demo in five minutes. It’s a pragmatic, ports-first reference for multi-tenant, event-sourced CQRS back-ends powered by TypeScript and uses [Temporal](https://github.com/temporalio/temporal) for durable workflow execution.

Expand Down
Loading