Dependency Rule
The Rule
Dependencies point inward. Nothing in an inner layer knows about an outer layer.
Adapters → Application → Ports → Domain ↑ ↑ ↑ ↑ outer middle inner innermostIn NEKTE’s monorepo this translates to a strict package dependency graph:
@nekte/core ← zero external dependencies (domain + ports)@nekte/client ← depends only on core@nekte/server ← depends only on core@nekte/bridge ← depends on core + server@nekte/cli ← depends on core + clientWhat This Means in Practice
Domain Layer (@nekte/core)
Imports nothing from other NEKTE packages. Contains:
- Types, schemas, hash functions, budget resolution
- SIEVE eviction and GDSF policies (pure algorithms)
- SSE event types and encoding
- TaskEntry aggregate root with state machine
- Filtering strategies (keyword, semantic, hybrid)
// VALID: core imports only its own modules and standard librariesimport { canonicalize } from './hash.js';import type { TokenBudget } from './types.js';// INVALID: core must never import from client or serverimport { NekteClient } from '@nekte/client'; // VIOLATIONimport { NekteServer } from '@nekte/server'; // VIOLATIONApplication Layer (@nekte/client, @nekte/server)
Imports from core only. Orchestrates domain objects and calls ports.
// server/src/server.ts — valid importsimport { resolveBudget, createLogger } from '@nekte/core'; // domainimport type { CapabilityFilterStrategy } from '@nekte/core'; // portimport { CapabilityRegistry } from './capability.js'; // domain serviceAdapter Layer (transports, auth, cache stores)
Imports from core and its own package’s application layer.
// server/src/http-transport.ts — valid importsimport type { NekteServer } from './server.js'; // applicationimport type { SseStream } from './sse-stream.js'; // adapter peerimport { createServer } from 'node:http'; // external libraryHow It’s Enforced
1. Package Boundaries
pnpm workspace:* references make dependencies explicit. @nekte/core has no workspace dependencies in its package.json:
{ "dependencies": { "msgpackr": "^1.11", "zod": "^3.24" } // No @nekte/* dependencies}2. Build Order
pnpm build respects the dependency graph: core builds first, then client and server in parallel, then bridge and cli.
3. Import Structure
Each package’s index.ts re-exports only its public API. Internal modules use relative .js imports and are not accessible to consumers.
Common Violations and How to Avoid Them
| Violation | Fix |
|---|---|
| Server imports a client class | Extract shared type to core |
| Core imports a transport | Define a port interface in core, implement in the adapter |
Domain logic depends on fetch | Create a Transport port, inject the HTTP adapter |
Cache policy depends on Map | Keep the policy pure (input/output arrays), wrap Map in an adapter |
Testing Implications
The dependency rule creates a natural testing pyramid:
| Layer | Test Type | I/O | Speed |
|---|---|---|---|
Domain (@nekte/core) | Unit tests | None | ~1ms/test |
| Application | Unit tests with mocked ports | None | ~5ms/test |
| Adapters | Integration tests | Real HTTP/gRPC | ~50ms/test |
| Full stack | E2E tests | Real server + client | ~100ms/test |
Domain tests (85 in core) run without any mocking because the domain layer has no I/O dependencies. This is the primary benefit of the dependency rule.