This is a monorepo spanning Go and TypeScript.
bulker/ Go services
bulkerapp/ Bulker — warehouse ingestion service
ingest/ HTTP ingest endpoint
sync-controller/ Connector sync orchestration
bulkerlib/ Core ingestion library
connectors/ Warehouse connectors (ClickHouse, BigQuery, Redshift, Snowflake, S3, GCS, …)
services/
rotor/ Event routing, transformation, function execution
webapps/
console/ Admin UI (Next.js), Management API, MCP server
libs/
jitsu-js/ Browser + Node.js SDK (@jitsu/js)
jitsu-react/ React bindings (@jitsu/jitsu-react)
functions/ Functions runtime (@jitsu/functions-lib)
juava/ Shared TypeScript utilities
cli/jitsu-cli/ Developer CLI (jitsu-cli on npm)
types/protocols/ Shared TypeScript protocols (@jitsu/protocols)
helm/ Helm chart
docker/ Docker Compose setup (deprecated)
node: >=22npxpnpm: >= 10docker: >= 19.03.0go: 1.26— for the Go services; the repo uses Go workspaces viago.work
pnpm install- Install dependenciespnpm codegen- Generate the Prisma client + zod schemas. Required once after a fresh checkout (or a new worktree) before anything else will buildpnpm build- Build the projectpnpm build:turbo- Same, orchestrated by Turbo
pnpm format- Apply prettier to the project, only to changed filespnpm format:check- Check if prettier needs to be applied, check only changed filespnpm format:check:all- Check if prettier needs to be applied. Check all filespnpm format:all- Same aspnpm format, but check all files, regardless of changes
pnpm typecheck- Run typecheckpnpm typecheck:turbo- Same, orchestrated by Turbo
pnpm lint- Run linterpnpm test- Run testspnpm console:dev- Run just the console
For the Go services, inside bulker/:
go build ./...
go test ./...Run
docker compose -f ./docker/docker-compose.yml up --force-recreateto start all dependencies required to run Jitsu.docker compose -f ./docker/docker-compose.yml up --profile jitsu-services-dev --force-recreate- to run dependencies + all Jitsu services in a hot reload mode, seedocker/README.md
Every node process spawned by a pnpm script auto-loads two layered .env.local files
(later wins; existing process.env always wins over both, missing files skipped silently):
~/.jitsu/.env.local— shared across all worktrees of all branches (Firebase, Stripe, OIDC, GitHub OAuth — anything that doesn't change per branch).<repo>/.env.local— per-worktree (DATABASE_URL,NEXTAUTH_URL,AUTH_COOKIE_DOMAIN, anything that should differ between two worktrees of two PRs).
No wrapper. node --inspect script.js and your debugger attach to the script's own
process directly — there's no dotenv-cli parent in the tree.
.env.example documents the variables the apps expect. Runtime defaults belong in code
(process.env.FOO ?? "default"), not in a tracked .env.
The root .npmrc sets node-options=--require=env-preload,
so pnpm exports NODE_OPTIONS=--require=... for every node process it spawns from a
script. The preload (env-preload/preload-env.cjs) loads
~/.jitsu/.env.local, then walks up from process.cwd() to find pnpm-workspace.yaml
and load the repo-root .env.local. (Why a preload instead of --env-file-if-exists:
Node disallows --env-file* in NODE_OPTIONS for security; --require is allowed.)
mkdir -p ~/.jitsu && chmod 700 ~/.jitsu
touch ~/.jitsu/.env.local && chmod 600 ~/.jitsu/.env.local
echo 'STRIPE_KEY=sk_live_xxx' >> ~/.jitsu/.env.localpnpm test runs every package's tests. Most are plain vitest/jest suites; the console
has a two-project setup worth knowing about.
Two vitest projects, split by what they need:
- unit (
__tests__/unit/) — pure tests, no external dependencies. Instant, run anywhere. - integration (
__tests__/integration/) — service tests against real Postgres and ClickHouse started via testcontainers (Docker required), with MSW intercepting all outbound HTTP. Novi.mockanywhere: SQL runs against real databases, access checks and audit logging hit the same database the service writes to.
cd webapps/console
pnpm test # both projects (starts containers)
pnpm exec vitest run --project unit # pure tests only, no Docker
pnpm exec vitest run __tests__/integration/sync-service.test.ts # one fileHow the harness works (__tests__/integration/support/): a global setup boots one
Postgres (postgres:18-alpine, matching prod) and one ClickHouse
(clickhouse/clickhouse-server:25.4-alpine) container per run and pushes the prisma
schema into a sealed template database. Each test file runs in its own process (vitest
forks + isolate) and clones the template into a private database (~150ms), so files are
fully isolated and can run in parallel. Baseline env is set before the test file's imports,
which is what binds the db.prisma() / pgPool / clickhouse singletons to the per-file
databases. Every outbound HTTP request must have an MSW handler (server.use(...)) or the
test fails loudly; requests to the ClickHouse container pass through.
A cold run pays for container boot + prisma db push (~10–30s depending on the machine,
more if images need pulling). To keep the containers running between runs:
CONSOLE_TEST_CONTAINERS_REUSE=1 pnpm testSubsequent runs connect in about a second. The template database is keyed by a hash of
schema.prisma, so schema changes rebuild it automatically, and leftover per-file
databases from previous runs are swept at startup. Trade-off: the two containers keep
running until you remove them (docker ps → docker rm -f <id>). Don't set this in CI.
Notes:
vitest --watchis supported for the unit project; for integration prefervitest run(re-runs re-clone databases in new forks, containers stay up for the session).- Per-test env overrides use
vi.stubEnv(auto-restored). Baseline env lives in__tests__/integration/support/setup.ts. __tests__/integration/support/harness.tsexposesdeps()(the real singletons) andseedWorkspace(); other fixtures are plainprisma.create/clickhouse.insertcalls inline in the tests.
The default development branch is newjitsu.
Branch naming: Use a type prefix — feat/, fix/, chore/. Example: feat/workspace-oidc.
Merging policy: When working on a feature branch, never merge the default branch into it — always rebase your branch onto the latest default branch. When merging a PR into the default branch, either "Create a merge commit" (the default) or "Rebase and merge" is fine. Squash merge stays off; if you want to squash overly granular commits, do it locally before opening the PR.
Commit style: Conventional commits —
type(scope): description. Common types: fix, feat, chore, refactor, ci.
Examples: fix(rotor): enable DNS caching for undici pools,
feat(console): add workspace OIDC configuration.
Trivial changes, bug fixes, and config updates go directly to newjitsu. Larger or
riskier changes use pull requests. The engineer decides based on complexity and risk.
lint.yml runs on every push and PR:
- Prettier format check, TypeScript typecheck, ESLint
- Jest unit tests
- Playwright E2E tests (frontend changes only)
- Go integration tests against real cloud warehouses (AWS S3, BigQuery, Redshift, Snowflake)
ai-review.yml runs on every PR and on direct pushes
to newjitsu. It uses OpenAI Codex to check for bugs, security issues, and correctness
problems — style nitpicks are skipped. For PRs it posts a review via a GitHub App. For
direct commits it posts a commit comment.
There are two independent release pipelines with separate versioning.
Services & CLI tools — Docker images for backend services (console, rotor,
functions-server, bulker, ingest, and others) and NPM packages (jitsu-cli,
@jitsu/functions-lib). Managed by
services.yaml.
Base version in .services.version.json.
Client libraries — NPM packages @jitsu/js, @jitsu/jitsu-react,
@jitsu/protocols. Managed by
client-libraries.yaml.
Base version in .jsclient.version.json.
Each pipeline publishes to three channels determined by the branch:
| Pipeline | newjitsu |
stable-services |
stable-jsclient |
Any other branch |
|---|---|---|---|---|
| Services & CLI | beta — 2.14.1-beta.N |
stable — 2.14.1 |
— | canary — 2.14.1-canary.20260416.abc1234 |
| Client libraries | beta — 1.11.0-beta.N |
— | stable — 1.11.0 |
canary — 1.11.0-canary.20260416.abc1234 |
- Stable —
X.Y.Z - Beta —
X.Y.Z-beta.N, whereNis auto-incremented based on existing git tags - Canary —
X.Y.Z-canary.YYYYMMDD.shortsha— no git tag or GitHub release created - The base version
X.Yis defined in.services.version.jsonor.jsclient.version.json;Zis the patch number, incremented per release
Builds are not produced on every merge. A push to newjitsu publishes only when a
version file (.services.version.json or .jsclient.version.json) is bumped in that
push: lint.yml waits for tests to pass, then dispatches
publish.yml, which builds the affected stack(s) — stable
if the matching version file changed, otherwise beta. Commits that don't bump a version
file publish nothing; trigger off-cycle canary/beta builds with a manual publish.yml
dispatch. On a successful beta or stable release, a GitHub release is created and the infra
repo is notified via webhook to update deployment configs.