Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 10 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
# Tinybird
TINYBIRD_HOST=http://localhost:7181
TINYBIRD_TOKEN=your-tinybird-token
# Required only when raw SQL is routed to Tinybird. These values mint scoped,
# per-org read JWTs and are intentionally independent from the API token.
# TINYBIRD_SIGNING_KEY=your-tinybird-jwt-signing-key
# TINYBIRD_WORKSPACE_ID=your-tinybird-workspace-id

# ClickHouse
CLICKHOUSE_URL=http://localhost:9000
# tinybird is the default for compatibility with existing gateway deployments.
# Set clickhouse when CLICKHOUSE_URL points at a vanilla/self-managed server so
# raw SQL preserves CLICKHOUSE_PASSWORD instead of substituting a scoped JWT.
# Env-level vanilla ClickHouse raw SQL is allowed only in single-org self-hosted mode.
CLICKHOUSE_PROVIDER=clickhouse
CLICKHOUSE_USER=maple
CLICKHOUSE_DATABASE=default
CLICKHOUSE_PASSWORD=maple
Expand Down Expand Up @@ -175,4 +184,4 @@ ELECTRIC_URL=http://localhost:3473

# Flue chat model
MAPLE_CHAT_MODEL=openrouter/z-ai/glm-5.2
INTERNAL_SERVICE_TOKEN=dev-internal-service-token
INTERNAL_SERVICE_TOKEN=dev-internal-service-token
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ These values align with `docker-compose.development.yml` and self-hosted auth:
```dotenv
# Warehouse — route API queries to local ClickHouse instead of Tinybird Cloud
CLICKHOUSE_URL=http://localhost:8123
CLICKHOUSE_PROVIDER=clickhouse
CLICKHOUSE_USER=maple
CLICKHOUSE_PASSWORD=maple
CLICKHOUSE_DATABASE=default
Expand Down Expand Up @@ -186,7 +187,9 @@ Default URL: `http://localhost:3472`
| `MAPLE_DEFAULT_ORG_ID` | yes | Default `default`; must match ingest org override |
| `TINYBIRD_HOST` | yes | Placeholder OK when using `CLICKHOUSE_URL` |
| `TINYBIRD_TOKEN` | yes | Placeholder OK when using `CLICKHOUSE_URL` |
| `TINYBIRD_SIGNING_KEY` / `TINYBIRD_WORKSPACE_ID` | with Tinybird raw SQL | Explicit JWT signing configuration; never derived from `TINYBIRD_TOKEN` |
| `CLICKHOUSE_URL` | recommended | `http://localhost:8123` for local ClickHouse stack |
| `CLICKHOUSE_PROVIDER` | optional | `tinybird` (default) or `clickhouse`; set `clickhouse` for env-level vanilla/self-managed ClickHouse |
| `CLICKHOUSE_USER` / `CLICKHOUSE_PASSWORD` / `CLICKHOUSE_DATABASE` | with CH | Match docker-compose (`maple` / `maple` / `default`) |
| `INTERNAL_SERVICE_TOKEN` | recommended | Shared with scraper + chat-flue |
| `SD_INTERNAL_TOKEN` | optional | Prometheus scraper internal API auth |
Expand Down
5 changes: 4 additions & 1 deletion apps/alerting/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
PlanetScaleService,
QueryEngineService,
ServiceMapRollupService,
TinybirdOrgTokenService,
WarehouseQueryService,
} from "@maple/api/alerting"
import * as MapleCloudflareSDK from "@maple-dev/effect-sdk/cloudflare"
Expand All @@ -50,8 +51,10 @@ const buildLayer = (_env: Record<string, unknown>) => {

const OrgClickHouseSettingsLive = OrgClickHouseSettingsService.layer.pipe(Layer.provide(BaseLive))

const TinybirdOrgTokenLive = TinybirdOrgTokenService.layer.pipe(Layer.provide(EnvLive))

const WarehouseQueryServiceLive = WarehouseQueryService.layer.pipe(
Layer.provide(Layer.mergeAll(EnvLive, OrgClickHouseSettingsLive)),
Layer.provide(Layer.mergeAll(EnvLive, OrgClickHouseSettingsLive, TinybirdOrgTokenLive)),
)

// EdgeCacheService's storage backend is injected via the CacheBackend port.
Expand Down
9 changes: 9 additions & 0 deletions apps/api/alchemy.run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { MapleApiRpcShape } from "@maple/domain/internal-rpc"
import type { MapleDomains, MapleStage } from "@maple/infra/cloudflare"
import {
CLOUDFLARE_WORKER_PLACEMENT,
formatMapleStage,
resolveDeploymentEnvironment,
resolveHyperdriveName,
resolveHyperdriveRefId,
Expand Down Expand Up @@ -137,12 +138,20 @@ export const createMapleApi = ({ stage, domains }: CreateMapleApiOptions) =>
VCS_SYNC_QUEUE: vcsSyncQueue,
CLICKHOUSE_SCHEMA_APPLY_WORKFLOW: schemaApplyWorkflow,
AI_TRIAGE_WORKFLOW: aiTriageWorkflow,
API_V2_RATE_LIMITER: Cloudflare.RateLimit("API_V2_RATE_LIMITER", {
namespaceId: 2026071801,
simple: { limit: 600, period: 60 },
}),
API_V2_RATE_LIMIT_PARTITION: formatMapleStage(stage),
EMAIL: Cloudflare.Email.SendEmail("email", {
allowedSenderAddresses: ["notifications@noreply.maple.dev"],
}),
TINYBIRD_HOST: requireEnv("TINYBIRD_HOST"),
TINYBIRD_TOKEN: Redacted.make(requireEnv("TINYBIRD_TOKEN")),
...optionalSecret("TINYBIRD_SIGNING_KEY"),
...optionalPlain("TINYBIRD_WORKSPACE_ID"),
...optionalPlain("CLICKHOUSE_URL"),
CLICKHOUSE_PROVIDER: process.env.CLICKHOUSE_PROVIDER?.trim() || "tinybird",
...optionalPlain("CLICKHOUSE_USER"),
...optionalPlain("CLICKHOUSE_DATABASE"),
...optionalSecret("CLICKHOUSE_PASSWORD"),
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/alerting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ export { PlanetScaleOAuthService } from "./services/PlanetScaleOAuthService"
export { PlanetScaleService } from "./services/PlanetScaleService"
export { QueryEngineService } from "./services/QueryEngineService"
export { ServiceMapRollupService } from "./services/ServiceMapRollupService"
export { TinybirdOrgTokenService } from "./services/TinybirdOrgTokenService"
export { WarehouseQueryService } from "./lib/WarehouseQueryService"
export { WorkerEnvironment } from "./lib/WorkerEnvironment"
34 changes: 20 additions & 14 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ import { HttpV2OrganizationLive } from "./routes/v2/organization.http"
import { HttpV2InstrumentationRecommendationsLive } from "./routes/v2/recommendations.http"
import { HttpV2ScrapeTargetsLive } from "./routes/v2/scrape-targets.http"
import { HttpV2SessionReplaysLive } from "./routes/v2/session-replays.http"
import {
HttpV2LogsLive,
HttpV2MetricsLive,
HttpV2QueryLive,
HttpV2ServiceMapLive,
HttpV2ServicesLive,
HttpV2TracesLive,
} from "./routes/v2/telemetry.http"
import { V2SchemaErrorsLive } from "./routes/v2/error-envelope"
import { HttpAuthLive, HttpAuthPublicLive } from "./routes/auth.http"
import { HttpChatLive } from "./routes/chat.http"
Expand Down Expand Up @@ -57,6 +65,7 @@ import { HazelOAuthService } from "./services/HazelOAuthService"
import { InvestigationService } from "./services/InvestigationService"
import { NotificationDispatcher } from "./services/NotificationDispatcher"
import { ApiKeysService } from "./services/ApiKeysService"
import { ApiV2RateLimiter } from "./services/ApiV2RateLimiter"
import { AuthService } from "./services/AuthService"
import { ApiAuthorizationLayer } from "./services/ApiAuthorizationLayer"
import { ApiAuthorizationV2Layer } from "./services/ApiAuthorizationV2Layer"
Expand All @@ -72,10 +81,10 @@ import { Env } from "./lib/Env"
import { IngestAttributeMappingService } from "./services/IngestAttributeMappingService"
import { OrgIngestKeysService } from "./services/OrgIngestKeysService"
import { OrgClickHouseSettingsService } from "./services/OrgClickHouseSettingsService"
import { TinybirdOrgTokenService } from "./services/TinybirdOrgTokenService"
import { OrganizationService } from "./services/OrganizationService"
import { QueryEngineService } from "./services/QueryEngineService"
import { RecommendationIssueService } from "./services/RecommendationIssueService"
import { RawSqlChartService } from "@maple/query-engine/runtime"
import { PlanetScaleConnectionService } from "./services/PlanetScaleConnectionService"
import { PlanetScaleDiscoveryService } from "./services/PlanetScaleDiscoveryService"
import { PlanetScaleOAuthService } from "./services/PlanetScaleOAuthService"
Expand All @@ -91,6 +100,7 @@ import { VcsCommitService } from "./services/vcs/VcsCommitService"
import { VcsProviderRegistry } from "./services/vcs/VcsProviderRegistry"
import { VcsRepository } from "./services/vcs/VcsRepository"
import { VcsSyncQueue } from "./services/vcs/VcsSyncQueue"
import { API_CORS_OPTIONS } from "./lib/api-cors"

const HealthRouter = HttpRouter.use((router) => router.add("GET", "/health", HttpServerResponse.text("OK")))

Expand Down Expand Up @@ -133,6 +143,7 @@ const CoreServicesLive = Layer.mergeAll(
OnboardingService.layer,
OrgIngestKeysService.layer,
OrgClickHouseSettingsService.layer,
TinybirdOrgTokenService.layer,
OrganizationService.layer,
PlanetScaleOAuthLive,
PlanetScaleDiscoveryLive,
Expand Down Expand Up @@ -253,7 +264,6 @@ export const MainLive = Layer.mergeAll(
DigestServiceLive,
DemoServiceLive,
VcsServicesLive,
RawSqlChartService.layer,
)

const ApiRoutes = HttpApiBuilder.layer(MapleApi).pipe(
Expand Down Expand Up @@ -301,6 +311,12 @@ const ApiV2Routes = HttpApiBuilder.layer(MapleApiV2).pipe(
HttpV2AnomaliesLive,
HttpV2OrganizationLive,
HttpV2SessionReplaysLive,
HttpV2TracesLive,
HttpV2LogsLive,
HttpV2MetricsLive,
HttpV2ServicesLive,
HttpV2ServiceMapLive,
HttpV2QueryLive,
),
),
Layer.provide(V2SchemaErrorsLive),
Expand All @@ -320,20 +336,10 @@ export const AllRoutes = Layer.mergeAll(
McpGetFallback,
DocsRoute,
DocsV2Route,
).pipe(
Layer.provideMerge(
HttpRouter.cors({
allowedOrigins: ["*"],
allowedMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["*"],
// The ElectricSQL shape proxy (and its electric-* exposed headers) moved
// to the standalone `apps/electric-sync` worker.
exposedHeaders: ["Mcp-Session-Id"],
}),
),
)
).pipe(Layer.provideMerge(HttpRouter.cors(API_CORS_OPTIONS)))

export const ApiAuthLive = Layer.mergeAll(ApiAuthorizationLayer, ApiAuthorizationV2Layer).pipe(
Layer.provideMerge(ApiV2RateLimiter.layer),
Layer.provideMerge(ApiKeysService.layer),
Layer.provideMerge(Env.layer),
)
Expand Down
14 changes: 14 additions & 0 deletions apps/api/src/lib/Env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ export interface EnvShape {
readonly PORT: number
readonly TINYBIRD_HOST: string
readonly TINYBIRD_TOKEN: Redacted.Redacted<string>
readonly TINYBIRD_SIGNING_KEY: Option.Option<Redacted.Redacted<string>>
readonly TINYBIRD_WORKSPACE_ID: Option.Option<string>
readonly CLICKHOUSE_URL: Option.Option<string>
readonly CLICKHOUSE_PROVIDER: string
readonly CLICKHOUSE_USER: string
readonly CLICKHOUSE_PASSWORD: Option.Option<Redacted.Redacted<string>>
readonly CLICKHOUSE_DATABASE: string
Expand Down Expand Up @@ -82,7 +85,10 @@ const envConfig = Config.all({
PORT: portConfig,
TINYBIRD_HOST: Config.string("TINYBIRD_HOST"),
TINYBIRD_TOKEN: Config.redacted("TINYBIRD_TOKEN"),
TINYBIRD_SIGNING_KEY: optionalRedacted("TINYBIRD_SIGNING_KEY"),
TINYBIRD_WORKSPACE_ID: optionalString("TINYBIRD_WORKSPACE_ID"),
CLICKHOUSE_URL: optionalString("CLICKHOUSE_URL"),
CLICKHOUSE_PROVIDER: stringWithDefault("CLICKHOUSE_PROVIDER", "tinybird"),
CLICKHOUSE_USER: stringWithDefault("CLICKHOUSE_USER", "default"),
CLICKHOUSE_PASSWORD: optionalRedacted("CLICKHOUSE_PASSWORD"),
CLICKHOUSE_DATABASE: stringWithDefault("CLICKHOUSE_DATABASE", "default"),
Expand Down Expand Up @@ -212,6 +218,14 @@ const makeEnv = Effect.gen(function* () {
return yield* Effect.die(new EnvValidationError({ message: "MAPLE_DEFAULT_ORG_ID cannot be empty" }))
}

if (env.CLICKHOUSE_PROVIDER !== "clickhouse" && env.CLICKHOUSE_PROVIDER !== "tinybird") {
return yield* Effect.die(
new EnvValidationError({
message: "CLICKHOUSE_PROVIDER must be either 'clickhouse' or 'tinybird'",
}),
)
}

const authMode = env.MAPLE_AUTH_MODE.toLowerCase()

if (authMode !== "clerk" && Option.isNone(env.MAPLE_ROOT_PASSWORD)) {
Expand Down
Loading
Loading