Skip to main content

Local Dev Setup

:::info Source Sourced from services/search-service/LOCAL_DEV_SETUP.md in the documentation repo. :::

Targets macOS, Linux, and Windows (via WSL2). Goal: clone → hot-reload in ≤ 5 minutes.

1. Prerequisites

ToolVersionInstall
Node.js22 LTSvia fnm or nvm
pnpm9+npm i -g pnpm
Docker25+Docker Desktop / Podman Desktop
Docker Composev2bundled
mkcertlatestbrew install mkcert
Task3+brew install go-task
direnvlatestoptional but recommended
git-lfslatestfor model artifacts

macOS recommended: 8 CPU, 16 GB RAM. Windows: use WSL2 Ubuntu 22.04 backend.

2. First Run

git clone git@github.com:ghasi-edtech/platform.git
cd platform/services/search-service
pnpm install
cp .env.example .env.local
task dev:stack:up # boots local deps via docker-compose
task dev:seed # seed tenants + corpora
pnpm dev # hot-reload Fastify app

Open:

3. Compose Stack (dev)

docker-compose.dev.yaml in repo root provides:

ServicePortNotes
postgres5432single instance, initial DB search
redis6379single instance
opensearch9200, 9300single node, no security
opensearch-dashboards5601UI
nats4222, 8222JetStream enabled
ai-gateway-mock9090deterministic hashed embeddings
otel-collector4317, 4318OTLP → Jaeger + Prometheus
jaeger16686traces UI
prometheus9091metrics

Mock ai-gateway returns a deterministic 1024-dim vector from sha256(text) repeated/sliced. No network calls.

4. Environment Variables

NameDefaultPurpose
ROLEapiapi | indexer | jobs
PORT8080HTTP port
REGIONus-localRegion tag
OPENSEARCH_URLhttp://localhost:9200
POSTGRES_URLpostgres://dev:dev@localhost:5432/search
REDIS_URLredis://localhost:6379/0
NATS_URLnats://localhost:4222
AI_GATEWAY_URLhttp://localhost:9090
JWT_JWKS_URLhttp://localhost:3300/.well-known/jwks.json
OTEL_EXPORTER_OTLP_ENDPOINThttp://localhost:4318
LOG_LEVELdebug
FEATURE_QUERY_EXPANSIONfalse
FEATURE_RECOMMENDATIONStrue

5. Task Runner Targets

task --list
TaskWhat it does
dev:stack:upBoot compose stack
dev:stack:downTear down stack + volumes
dev:seedSeed tenants + ~1k docs across all types
dev:publish-eventPublish a fixture domain event to NATS
dev:reindexTrigger reindex for local-tenant-1
dev:logsTail all containers
dev:nats:browseNATS CLI into local JetStream
dev:openapi:serveServe Swagger UI
test:unitpnpm test:unit
test:integrationBoots testcontainers + runs integration
test:e2eRuns Playwright E2E (needs stack up)
lintESLint + Prettier check
typechecktsc --noEmit

6. Seed Data

scripts/seed/index.ts loads:

  • 3 tenants: local-tenant-1/2/3.
  • 100 courses, 300 lessons, 1k blocks, 20 marketplace listings, 50 users, 80 assignments, 30 certificates per tenant.
  • Embeddings computed via mock gateway (deterministic).

Run:

task dev:seed
# or partial
pnpm tsx scripts/seed/index.ts --tenants=1 --docs=50

7. Hot Reload

  • pnpm dev uses tsx --watch with --ignore node_modules.
  • Changes to .env.local require restart.
  • Changes to NATS subjects require task dev:stack:restart nats if JetStream config changes.

8. Running a Single Use Case

pnpm tsx scripts/repl.ts
> const { searchUseCase } = await import('./src/application/use-cases/search-query');
> await searchUseCase.execute({ tenantId: 'local-tenant-1', q: 'algebra' });

REPL pre-loads a dev-grade service-account JWT.

9. Debugging

  • VSCode launch config Debug: search-api pre-configured.
  • Node --inspect-brk exposed on port 9229.
  • Enable OTLP debug: OTEL_LOG_LEVEL=debug.
  • For NATS wire debug: NATS_DEBUG=1 task dev:publish-event.

10. Running Tests

pnpm test:unit # vitest unit
pnpm test:integration # testcontainers + vitest
pnpm test:contract # pact verifier
pnpm test:e2e # playwright
pnpm coverage # c8 coverage

Coverage gate enforced locally — fails at < 80% lines.

11. Lint, Format, Typecheck

pnpm lint # eslint
pnpm format # prettier --write
pnpm typecheck # tsc --noEmit
pnpm validate # all of the above

Pre-commit hook (lefthook) runs validate on staged files.

12. Troubleshooting

SymptomFix
ECONNREFUSED localhost:9200docker compose ps — opensearch not ready, wait 30s
OpenSearch OOM killedgive Docker 8GB RAM; alpine image uses 1GB heap
Test flaky on MacOS ARMensure platform: linux/arm64 in compose
Embeddings nondeterministicyou hit real ai-gateway; set AI_GATEWAY_URL to mock
Stale seedtask dev:stack:down && task dev:stack:up && task dev:seed
Kafka/NATS consumer lagtask dev:nats:browse and check consumer status

13. Common Workflows

Adding a new projector

  1. Write schema contract under src/interfaces/consumers/__contracts__/<subject>.json.
  2. Write unit test for mapping function (RED).
  3. Implement mapping.
  4. Register consumer in src/interfaces/consumers/index.ts.
  5. Integration test publishing the event and asserting doc appears.

Adding a new filter

  1. Extend filter AST in src/domain/ranking.ts.
  2. Extend parser tests.
  3. Extend OS DSL translator.
  4. Add happy + failure API contract tests.

14. Code Style

See root ESLINT_CONFIG + Prettier. In short:

  • 2-space indent.
  • Single quotes, trailing commas.
  • No any. No implicit undefined.
  • Immutability by default; Readonly<> everywhere domain.
  • One responsibility per file; max 400 lines.

15. Documentation

Each sub-module has a short README.md describing its role. Domain model docs live here; keep code references accurate.