OpenTelemetry observability platform
1,772
stars
1,977
commits
TypeScript
primary language
Sep 11, 2026
updated
Open-source observability for traces, logs & metrics — built on OpenTelemetry + ClickHouse.
Maple is now organized as a monorepo with a SPA frontend and an Effect-based backend API.
apps/web: TanStack Router SPA (Vite)apps/api: Effect HTTP API (Tinybird proxy + MCP server code + AI chat/triage on @opencode-ai/ai)apps/ingest: OTLP ingest gateway (key auth + org enrichment + collector forwarding)apps/landing: Astro landing siteapps/alerting: Alert evaluation workerapps/cli: CLI utilitiesapps/ios: Native SwiftUI app (Clerk + v2 API)packages/domain: Shared Effect HTTP contracts and domain typespackages/query-engine: Shared query and observability logicpackages/ui: Shared UI primitives and components>=1.3bun install
Run Maple as a single local binary with OTLP ingest, embedded ClickHouse, and the dashboard:
brew install Makisuo/tap/maple
maple start
See docs/local-mode.md for Homebrew, manual installer, update, and uninstall details.
Run every available dev task in the monorepo:
bun run dev
Run individual apps from the repo root with workspace filters:
bun --filter=@maple/web dev
bun --filter=@maple/api dev
bun --filter=@maple/ingest dev
bun --filter=@maple/landing dev
There is also a dedicated root helper for alerting:
bun run dev:alerting
Turbo dev runs in TUI mode so interactive servers stay attached.
bun run typecheck
bun run build
bun run test
Run the local multi-service stack (API + web + ingest + otel collector):
docker compose -f docker-compose.yml up --build
Services:
http://localhost:3472http://localhost:3471http://localhost:34744317 (gRPC), 4318 (HTTP), 13133 (health/extensions)Deployments run on Alchemy v2 (Effect-based): the root alchemy.run.ts exports a
single Alchemy.Stack("maple", …) whose program composes per-app factories:
apps/api/alchemy.run.ts — Hyperdrive (PlanetScale Postgres) MAPLE_DB, KV, queue,
workflows, the ChatSession Durable Object + api Worker with all env bindingsapps/alerting/alchemy.run.ts — cron-driven alerting Worker (cross-script workflow ref)apps/electric-sync/alchemy.run.ts — ElectricSQL shape-proxy Workerapps/web/alchemy.run.ts / apps/landing/alchemy.run.ts / apps/local-ui/alchemy.run.ts
— static builds via Command.Build + asset-serving WorkersStage grammar is prd / stg / pr-<number> / dev names, resolved via
@maple/infra/cloudflare (parseMapleStage, resolveMapleDomains, resolveWorkerName,
resolveHyperdriveName, resolveHyperdriveRefId, resolveDatabaseMode). stg/prd bind the
dashboard-managed Hyperdrive by config ID (resolveHyperdriveRefId) — origin credentials
never touch a deploy. MAPLE_PG_URL is only needed for dev stages, whose Hyperdrive alchemy
manages itself. PR previews bind no database at all (resolveDatabaseMode → "none"):
DB-backed routes 500, everything else in the preview works.
Run locally:
bun run alchemy:deploy:stg
PR_NUMBER=123 bun run alchemy:deploy:pr
The first v2 deploy against a stage with live v1-created resources needs --adopt
(the pr script passes it already); v1 state is incompatible and simply abandoned —
never run a v1 alchemy destroy against a live stage.
Tear down:
bun run alchemy:destroy:stg
PR_NUMBER=123 bun run alchemy:destroy:pr
CI workflows:
main): .github/workflows/deploy-stg.ymlworkflow_dispatch): .github/workflows/deploy-prd.yml.github/workflows/deploy-pr-preview.yml (pull_request opened/synchronize/reopened/closed)Secrets source model (CI):
Infisical/secrets-action using OIDC
(credential-less — GitHub's OIDC token authenticates a machine identity, no long-lived
token stored). CI needs:
INFISICAL_PROJECT_SLUG (the project slug — a
variable, not a secret: GitHub masks secret values everywhere, and a
slug like maple would then blank out the PR-preview deployment URL
app-pr-<n>.maple.dev)INFISICAL_MACHINE_IDENTITY_ID (the machine identity ID)prod, staging, dev — mapped from the old Doppler
prd/stg/pr configs) must define:
CLOUDFLARE_API_TOKENCLOUDFLARE_DEFAULT_ACCOUNT_ID (bridged to alchemy v2's CLOUDFLARE_ACCOUNT_ID in the root alchemy.run.ts; ALCHEMY_PASSWORD/ALCHEMY_STATE_TOKEN were v1-only and are no longer read)TINYBIRD_HOSTTINYBIRD_TOKENEMAIL_FROM (sender address on an onboarded Cloudflare Email Service domain; delivery uses the EMAIL worker binding, no API key)MAPLE_INGEST_KEY_ENCRYPTION_KEYMAPLE_INGEST_KEY_LOOKUP_HMAC_KEYMAPLE_AUTH_MODEMAPLE_ROOT_PASSWORD (required in self_hosted mode)CLERK_SECRET_KEYCLERK_PUBLISHABLE_KEYCLERK_JWT_KEYSetup note: the machine identity must have a GitHub OIDC auth method configured in Infisical (scoped to this repo, ideally to the production/staging/pr-preview GitHub environments) and read access to the project. The workflows select secrets via project-slug (INFISICAL_PROJECT_SLUG) and per-stage env-slug (prod/staging/dev).
Runtime API URL behavior:
VITE_API_BASE_URL from the Cloudflare api worker domain (api.maple.dev in prd, api-staging.maple.dev in stg, worker.dev URL for pr-*).bun --filter=@maple/web dev can still use root .env VITE_API_BASE_URL for local API routing..env.exampleapps/api/.env.example.env values are local-only and should stay untracked.The web app expects VITE_API_BASE_URL to point to the API (defaults to http://localhost:3472).
For ingest + key auth, set these at minimum in your root .env when running the ingest gateway:
MAPLE_INGEST_KEY_LOOKUP_HMAC_KEYMAPLE_INGEST_KEY_ENCRYPTION_KEY (required when ingest reads encrypted credentials from Postgres)INGEST_PORTINGEST_FORWARD_OTLP_ENDPOINTMAPLE_INTERNAL_ORG_ID (the org the gateway's own telemetry is filed under; no default)INGEST_FORWARD_TIMEOUT_MSINGEST_MAX_REQUEST_BODY_BYTESINGEST_REQUIRE_TLSINGEST_REPLAY_MAX_SESSION_BYTES (optional; ceiling on the decompressed rrweb
payload one replay session may record, default 1 GiB, 0 disables)Maple persists application state in PostgreSQL:
bun db:up.apps/api/.data/pglite; set
MAPLE_DB_URL=memory:// for an ephemeral database.Migration commands:
bun db:up
bun db:migrate:local
bun run --cwd packages/db db:generate
bun run --cwd packages/db db:studio
CI migrates deployed PlanetScale branches over their direct port before Alchemy deploys the
Workers. See docs/persistence.md for the full workflow.
public + private).MAPLE_INGEST_KEY_ENCRYPTION_KEY (base64-encoded 32-byte key).MAPLE_INGEST_KEY_LOOKUP_HMAC_KEY.Maple supports exactly two auth modes via MAPLE_AUTH_MODE:
clerk
MAPLE_AUTH_MODE=clerkCLERK_SECRET_KEYCLERK_JWT_KEY for networkless verificationCLERK_PUBLISHABLE_KEY for the web appVITE_CLERK_SIGN_IN_URL and VITE_CLERK_SIGN_UP_URLself_hosted
MAPLE_AUTH_MODE=self_hostedMAPLE_ROOT_PASSWORD (required)MAPLE_DEFAULT_ORG_ID (defaults to default)/sign-in with the root password before accessing the dashboard/API.POST /api/auth/session/refresh, up to an absolute cap of 7 days from sign-in. Past the cap
the root password is required again. Rotating MAPLE_ROOT_PASSWORD still invalidates every
token immediately — it is the HMAC key, and the only way to revoke a session early.Start apps:
bun --filter=@maple/api dev
bun --filter=@maple/web dev
Validate behavior:
/sign-in/org-required/sign-inMAPLE_ROOT_PASSWORD login issues a bearer session token with a bounded lifetimePOST /api/auth/session/refresh until the session's absolute deadlineBreaking change:
MAPLE_ROOT_PASSWORD is now required when MAPLE_AUTH_MODE=self_hosted.TypeScript
94.6%
Rust
1.6%
Astro
1.5%
Swift
1.2%
OpenTelemetry observability platform
1,772
stars
1,977
commits
TypeScript
primary language
Sep 11, 2026
updated
Open-source observability for traces, logs & metrics — built on OpenTelemetry + ClickHouse.
Maple is now organized as a monorepo with a SPA frontend and an Effect-based backend API.
apps/web: TanStack Router SPA (Vite)apps/api: Effect HTTP API (Tinybird proxy + MCP server code + AI chat/triage on @opencode-ai/ai)apps/ingest: OTLP ingest gateway (key auth + org enrichment + collector forwarding)apps/landing: Astro landing siteapps/alerting: Alert evaluation workerapps/cli: CLI utilitiesapps/ios: Native SwiftUI app (Clerk + v2 API)packages/domain: Shared Effect HTTP contracts and domain typespackages/query-engine: Shared query and observability logicpackages/ui: Shared UI primitives and components>=1.3bun install
Run Maple as a single local binary with OTLP ingest, embedded ClickHouse, and the dashboard:
brew install Makisuo/tap/maple
maple start
See docs/local-mode.md for Homebrew, manual installer, update, and uninstall details.
Run every available dev task in the monorepo:
bun run dev
Run individual apps from the repo root with workspace filters:
bun --filter=@maple/web dev
bun --filter=@maple/api dev
bun --filter=@maple/ingest dev
bun --filter=@maple/landing dev
There is also a dedicated root helper for alerting:
bun run dev:alerting
Turbo dev runs in TUI mode so interactive servers stay attached.
bun run typecheck
bun run build
bun run test
Run the local multi-service stack (API + web + ingest + otel collector):
docker compose -f docker-compose.yml up --build
Services:
http://localhost:3472http://localhost:3471http://localhost:34744317 (gRPC), 4318 (HTTP), 13133 (health/extensions)Deployments run on Alchemy v2 (Effect-based): the root alchemy.run.ts exports a
single Alchemy.Stack("maple", …) whose program composes per-app factories:
apps/api/alchemy.run.ts — Hyperdrive (PlanetScale Postgres) MAPLE_DB, KV, queue,
workflows, the ChatSession Durable Object + api Worker with all env bindingsapps/alerting/alchemy.run.ts — cron-driven alerting Worker (cross-script workflow ref)apps/electric-sync/alchemy.run.ts — ElectricSQL shape-proxy Workerapps/web/alchemy.run.ts / apps/landing/alchemy.run.ts / apps/local-ui/alchemy.run.ts
— static builds via Command.Build + asset-serving WorkersStage grammar is prd / stg / pr-<number> / dev names, resolved via
@maple/infra/cloudflare (parseMapleStage, resolveMapleDomains, resolveWorkerName,
resolveHyperdriveName, resolveHyperdriveRefId, resolveDatabaseMode). stg/prd bind the
dashboard-managed Hyperdrive by config ID (resolveHyperdriveRefId) — origin credentials
never touch a deploy. MAPLE_PG_URL is only needed for dev stages, whose Hyperdrive alchemy
manages itself. PR previews bind no database at all (resolveDatabaseMode → "none"):
DB-backed routes 500, everything else in the preview works.
Run locally:
bun run alchemy:deploy:stg
PR_NUMBER=123 bun run alchemy:deploy:pr
The first v2 deploy against a stage with live v1-created resources needs --adopt
(the pr script passes it already); v1 state is incompatible and simply abandoned —
never run a v1 alchemy destroy against a live stage.
Tear down:
bun run alchemy:destroy:stg
PR_NUMBER=123 bun run alchemy:destroy:pr
CI workflows:
main): .github/workflows/deploy-stg.ymlworkflow_dispatch): .github/workflows/deploy-prd.yml.github/workflows/deploy-pr-preview.yml (pull_request opened/synchronize/reopened/closed)Secrets source model (CI):
Infisical/secrets-action using OIDC
(credential-less — GitHub's OIDC token authenticates a machine identity, no long-lived
token stored). CI needs:
INFISICAL_PROJECT_SLUG (the project slug — a
variable, not a secret: GitHub masks secret values everywhere, and a
slug like maple would then blank out the PR-preview deployment URL
app-pr-<n>.maple.dev)INFISICAL_MACHINE_IDENTITY_ID (the machine identity ID)prod, staging, dev — mapped from the old Doppler
prd/stg/pr configs) must define:
CLOUDFLARE_API_TOKENCLOUDFLARE_DEFAULT_ACCOUNT_ID (bridged to alchemy v2's CLOUDFLARE_ACCOUNT_ID in the root alchemy.run.ts; ALCHEMY_PASSWORD/ALCHEMY_STATE_TOKEN were v1-only and are no longer read)TINYBIRD_HOSTTINYBIRD_TOKENEMAIL_FROM (sender address on an onboarded Cloudflare Email Service domain; delivery uses the EMAIL worker binding, no API key)MAPLE_INGEST_KEY_ENCRYPTION_KEYMAPLE_INGEST_KEY_LOOKUP_HMAC_KEYMAPLE_AUTH_MODEMAPLE_ROOT_PASSWORD (required in self_hosted mode)CLERK_SECRET_KEYCLERK_PUBLISHABLE_KEYCLERK_JWT_KEYSetup note: the machine identity must have a GitHub OIDC auth method configured in Infisical (scoped to this repo, ideally to the production/staging/pr-preview GitHub environments) and read access to the project. The workflows select secrets via project-slug (INFISICAL_PROJECT_SLUG) and per-stage env-slug (prod/staging/dev).
Runtime API URL behavior:
VITE_API_BASE_URL from the Cloudflare api worker domain (api.maple.dev in prd, api-staging.maple.dev in stg, worker.dev URL for pr-*).bun --filter=@maple/web dev can still use root .env VITE_API_BASE_URL for local API routing..env.exampleapps/api/.env.example.env values are local-only and should stay untracked.The web app expects VITE_API_BASE_URL to point to the API (defaults to http://localhost:3472).
For ingest + key auth, set these at minimum in your root .env when running the ingest gateway:
MAPLE_INGEST_KEY_LOOKUP_HMAC_KEYMAPLE_INGEST_KEY_ENCRYPTION_KEY (required when ingest reads encrypted credentials from Postgres)INGEST_PORTINGEST_FORWARD_OTLP_ENDPOINTMAPLE_INTERNAL_ORG_ID (the org the gateway's own telemetry is filed under; no default)INGEST_FORWARD_TIMEOUT_MSINGEST_MAX_REQUEST_BODY_BYTESINGEST_REQUIRE_TLSINGEST_REPLAY_MAX_SESSION_BYTES (optional; ceiling on the decompressed rrweb
payload one replay session may record, default 1 GiB, 0 disables)Maple persists application state in PostgreSQL:
bun db:up.apps/api/.data/pglite; set
MAPLE_DB_URL=memory:// for an ephemeral database.Migration commands:
bun db:up
bun db:migrate:local
bun run --cwd packages/db db:generate
bun run --cwd packages/db db:studio
CI migrates deployed PlanetScale branches over their direct port before Alchemy deploys the
Workers. See docs/persistence.md for the full workflow.
public + private).MAPLE_INGEST_KEY_ENCRYPTION_KEY (base64-encoded 32-byte key).MAPLE_INGEST_KEY_LOOKUP_HMAC_KEY.Maple supports exactly two auth modes via MAPLE_AUTH_MODE:
clerk
MAPLE_AUTH_MODE=clerkCLERK_SECRET_KEYCLERK_JWT_KEY for networkless verificationCLERK_PUBLISHABLE_KEY for the web appVITE_CLERK_SIGN_IN_URL and VITE_CLERK_SIGN_UP_URLself_hosted
MAPLE_AUTH_MODE=self_hostedMAPLE_ROOT_PASSWORD (required)MAPLE_DEFAULT_ORG_ID (defaults to default)/sign-in with the root password before accessing the dashboard/API.POST /api/auth/session/refresh, up to an absolute cap of 7 days from sign-in. Past the cap
the root password is required again. Rotating MAPLE_ROOT_PASSWORD still invalidates every
token immediately — it is the HMAC key, and the only way to revoke a session early.Start apps:
bun --filter=@maple/api dev
bun --filter=@maple/web dev
Validate behavior:
/sign-in/org-required/sign-inMAPLE_ROOT_PASSWORD login issues a bearer session token with a bounded lifetimePOST /api/auth/session/refresh until the session's absolute deadlineBreaking change:
MAPLE_ROOT_PASSWORD is now required when MAPLE_AUTH_MODE=self_hosted.TypeScript
94.6%
Rust
1.6%
Astro
1.5%
Swift
1.2%