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
1 change: 1 addition & 0 deletions apps/dashboard/src/components/provider-icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ export const ProviderIcons = {
bigquery: createSimpleProviderIcon(siGooglebigquery),
cal: createSimpleProviderIcon(siCaldotcom),
cloudflare_d1: CloudflareD1Icon,
cloudflare_r2_sql: createSimpleProviderIcon(siCloudflareworkers),
cloudflare_web_analytics: createSimpleProviderIcon(siCloudflareworkers),
cloudflare_workers_observability:
createSimpleProviderIcon(siCloudflareworkers),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ Use the session pooler host from the Supabase Connect panel. Keep SSL enabled an
- Limit schemas and tables at the database permission layer.
- Use explicit columns and `limit` clauses in agent queries.

Cloudflare D1 is covered with warehouse-style SQL sources because its provider ID, `cloudflare_d1`, is grouped with OneQuery warehouse providers.
Cloudflare D1 is covered with warehouse-style SQL sources because its provider ID, `cloudflare_d1`, is grouped with OneQuery warehouse providers. Cloudflare R2 SQL uses the separate `cloudflare_r2_sql` warehouse provider for querying R2 Data Catalog Iceberg tables.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Warehouses
description: Connect Snowflake, BigQuery, Athena, MotherDuck, and related warehouse systems to OneQuery.
description: Connect Snowflake, BigQuery, Athena, MotherDuck, Cloudflare R2 SQL, and related warehouse systems to OneQuery.
sidebar:
order: 3
---
Expand All @@ -16,6 +16,7 @@ Warehouse sources are useful for analytics, operations review, and agent-assiste
| AWS Athena Connector | `aws_athena_connector` | Uses a registered OneQuery connector and Athena database details. |
| MotherDuck | `motherduck` | PostgreSQL wire protocol endpoint with service token. |
| Cloudflare D1 | `cloudflare_d1` | Serverless SQL source for edge application data. |
| Cloudflare R2 SQL | `cloudflare_r2_sql` | Serverless analytics SQL over R2 Data Catalog Iceberg tables. |

## Snowflake Setup Notes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ Use the provider ID with `onequery source connect --source <provider-id>`. The s

## Provider Families

| Family | Provider IDs |
| ------------------ | ----------------------------------------------------------------------------------------- |
| Databases | `postgres`, `supabase`, `mysql`, `mongodb` |
| Warehouses | `snowflake`, `bigquery`, `aws_athena_connector`, `motherduck`, `cloudflare_d1` |
| Observability | `sentry`, `laminar`, `cloudflare_workers_observability` |
| Developer workflow | `github`, `linear`, `jira`, `confluence`, `vercel`, `discord`, `onepassword` |
| Product analytics | `ga`, `amplitude`, `mixpanel`, `posthog`, `microsoft_clarity`, `cloudflare_web_analytics` |
| Marketing | `google_search_console`, `amazon_ads`, `linkedin_ads`, `tiktok_marketing`, `sendgrid` |
| Productivity | `airtable`, `cal`, `granola` |
| Family | Provider IDs |
| ------------------ | --------------------------------------------------------------------------------------------------- |
| Databases | `postgres`, `supabase`, `mysql`, `mongodb` |
| Warehouses | `snowflake`, `bigquery`, `aws_athena_connector`, `motherduck`, `cloudflare_d1`, `cloudflare_r2_sql` |
| Observability | `sentry`, `laminar`, `cloudflare_workers_observability` |
| Developer workflow | `github`, `linear`, `jira`, `confluence`, `vercel`, `discord`, `onepassword` |
| Product analytics | `ga`, `amplitude`, `mixpanel`, `posthog`, `microsoft_clarity`, `cloudflare_web_analytics` |
| Marketing | `google_search_console`, `amazon_ads`, `linkedin_ads`, `tiktok_marketing`, `sendgrid` |
| Productivity | `airtable`, `cal`, `granola` |

For the live list used by the site, see [Connectors](/connectors/).
1 change: 1 addition & 0 deletions apps/landing/src/shared/icons/brands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ const BRAND_ICONS = {
bun: createSimpleBrandIcon(siBun),
cal: createSimpleBrandIcon(siCaldotcom),
cloudflare_d1: CloudflareD1Icon,
cloudflare_r2_sql: createSimpleBrandIcon(siCloudflareworkers),
cloudflare_web_analytics: createSimpleBrandIcon(siCloudflareworkers),
cloudflare_workers_observability: createSimpleBrandIcon(siCloudflareworkers),
confluence: createSimpleBrandIcon(siConfluence),
Expand Down
23 changes: 23 additions & 0 deletions packages/cli-server/src/connect/service/source/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,29 @@ describe("parseConnectSourceCredentials", () => {
});
});

it("injects the Cloudflare R2 SQL credential type", () => {
const result = parseConnectSourceCredentials("cloudflare_r2_sql", {
accountId: "023e105f4ecef8ad9ca31a8372d0c353",
apiToken: "cf_r2_sql_token",
bucketName: "analytics-events",
});

expect(result.isOk()).toBe(true);
if (result.isErr()) {
throw result.error;
}

expect(result.value).toEqual({
provider: "cloudflare_r2_sql",
credentials: {
accountId: "023e105f4ecef8ad9ca31a8372d0c353",
apiToken: "cf_r2_sql_token",
bucketName: "analytics-events",
type: "cloudflare_r2_sql",
},
});
});

it("infers Google Analytics service-account auth mode from guide-shaped credentials", () => {
const result = parseConnectSourceCredentials("ga", {
propertyId: "123456789",
Expand Down
4 changes: 4 additions & 0 deletions packages/cli-server/src/source/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ describe("source model", () => {
expect(getCliSourceInterfaceTypes("cloudflare_d1")).toEqual(["query"]);
});

it("exposes Cloudflare R2 SQL as query capable", () => {
expect(getCliSourceInterfaceTypes("cloudflare_r2_sql")).toEqual(["query"]);
});

it("keeps provider interfaces visible when a source is unhealthy", () => {
expect(getCliSourceInterfaceTypes("postgres")).toEqual(["query"]);
});
Expand Down
1 change: 1 addition & 0 deletions packages/db/src/__snapshots__/credentials.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ exports[`credentials schemas > credentialSchemaMap > matches supported provider
"bigquery",
"cal",
"cloudflare_d1",
"cloudflare_r2_sql",
"cloudflare_web_analytics",
"cloudflare_workers_observability",
"confluence",
Expand Down
19 changes: 19 additions & 0 deletions packages/db/src/connection-guide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,25 @@ export const SOURCE_CONNECT_PROVIDER_GUIDES: SourceConnectProviderGuide[] = [
},
},
},
{
provider: "cloudflare_r2_sql",
summary:
"Connect Cloudflare R2 SQL with an account ID, R2 bucket name, and API token for querying R2 Data Catalog Iceberg tables.",
steps: [
"Enable R2 Data Catalog for the bucket that owns the Iceberg tables OneQuery should query.",
"Copy the Cloudflare Account ID and the R2 bucket name from the Cloudflare dashboard or Wrangler.",
"Create a Cloudflare API token with R2 SQL read access plus R2 Data Catalog and R2 storage permissions for the target bucket.",
"Only include `apiBaseUrl` when you need a non-default R2 SQL API origin.",
],
exampleInput: {
sourceKey: "cloudflare_r2_sql_prod",
credentials: {
accountId: "023e105f4ecef8ad9ca31a8372d0c353",
bucketName: "analytics-events",
apiToken: "cloudflare_r2_sql_token",
},
},
},
{
provider: "laminar",
summary:
Expand Down
72 changes: 72 additions & 0 deletions packages/db/src/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
BigQueryCredentialsSchema,
CalCredentialsSchema,
CloudflareD1CredentialsSchema,
CloudflareR2SqlCredentialsSchema,
CloudflareWebAnalyticsCredentialsSchema,
ConfluenceCredentialsSchema,
ConnectorCredentialsSchema,
Expand Down Expand Up @@ -56,6 +57,7 @@ import type {
BigQueryCredentials,
CalCredentials,
CloudflareD1Credentials,
CloudflareR2SqlCredentials,
CloudflareWebAnalyticsCredentials,
ConfluenceCredentials,
ConnectorCredentials,
Expand Down Expand Up @@ -589,6 +591,51 @@ describe("credentials schemas", () => {
});
});

describe("CloudflareR2SqlCredentialsSchema", () => {
it("should validate valid Cloudflare R2 SQL credentials", () => {
const credentials: CloudflareR2SqlCredentials = {
accountId: "023e105f4ecef8ad9ca31a8372d0c353",
apiToken: "cf_r2_sql_token",
bucketName: "analytics-events",
type: "cloudflare_r2_sql",
};

const result = CloudflareR2SqlCredentialsSchema.safeParse(credentials);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data).toEqual(credentials);
}
});

it("should accept optional apiBaseUrl", () => {
const credentials: CloudflareR2SqlCredentials = {
accountId: "023e105f4ecef8ad9ca31a8372d0c353",
apiBaseUrl: "https://api.sql.cloudflarestorage.com/api/v1",
apiToken: "cf_r2_sql_token",
bucketName: "analytics-events",
type: "cloudflare_r2_sql",
};

const result = CloudflareR2SqlCredentialsSchema.safeParse(credentials);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.apiBaseUrl).toBe(
"https://api.sql.cloudflarestorage.com/api/v1"
);
}
});

it("should reject missing bucketName", () => {
const result = CloudflareR2SqlCredentialsSchema.safeParse({
accountId: "023e105f4ecef8ad9ca31a8372d0c353",
apiToken: "cf_r2_sql_token",
type: "cloudflare_r2_sql",
});

expect(result.success).toBe(false);
});
});

describe("LaminarCredentialsSchema", () => {
it("should validate valid Laminar credentials", () => {
const credentials: LaminarCredentials = {
Expand Down Expand Up @@ -2022,6 +2069,12 @@ describe("credentials schemas", () => {
);
});

it("should map cloudflare_r2_sql to CloudflareR2SqlCredentialsSchema", () => {
expect(credentialSchemaMap.cloudflare_r2_sql).toBe(
CloudflareR2SqlCredentialsSchema
);
});

it("should map laminar to LaminarCredentialsSchema", () => {
expect(credentialSchemaMap.laminar).toBe(LaminarCredentialsSchema);
});
Expand Down Expand Up @@ -2289,6 +2342,18 @@ describe("credentials schemas", () => {
expect(result.type).toBe("cloudflare_d1");
});

it("should validate Cloudflare R2 SQL credentials", () => {
const credentials = {
accountId: "023e105f4ecef8ad9ca31a8372d0c353",
apiToken: "cf_r2_sql_token",
bucketName: "analytics-events",
type: "cloudflare_r2_sql",
};

const result = validateCredentials(credentials);
expect(result.type).toBe("cloudflare_r2_sql");
});

it("should validate Sentry credentials", () => {
const credentials = {
authToken: "sntrys_123",
Expand Down Expand Up @@ -2448,6 +2513,12 @@ describe("credentials schemas", () => {
databaseId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
type: "cloudflare_d1",
},
{
accountId: "023e105f4ecef8ad9ca31a8372d0c353",
apiToken: "cf_r2_sql_token",
bucketName: "analytics-events",
type: "cloudflare_r2_sql",
},
{
apiKey: "lmnr_project_key_123",
type: "laminar",
Expand Down Expand Up @@ -2533,6 +2604,7 @@ describe("credentials schemas", () => {
"bigquery",
"aws_athena_connector",
"cloudflare_d1",
"cloudflare_r2_sql",
"laminar",
"motherduck",
"mysql",
Expand Down
16 changes: 16 additions & 0 deletions packages/db/src/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,18 @@ export type CloudflareD1Credentials = z.infer<
typeof CloudflareD1CredentialsSchema
>;

export const CloudflareR2SqlCredentialsSchema = z.object({
accountId: trimmedString("Account ID is required"),
apiBaseUrl: optionalTrimmedUrl("API base URL must be a valid URL"),
apiToken: requiredOpaqueString("API token is required"),
bucketName: trimmedString("Bucket name is required"),
type: z.literal("cloudflare_r2_sql"),
});

export type CloudflareR2SqlCredentials = z.infer<
typeof CloudflareR2SqlCredentialsSchema
>;

export const CloudflareWorkersObservabilityCredentialsSchema = z.object({
accountId: trimmedString("Account ID is required"),
apiBaseUrl: optionalTrimmedUrl("API base URL must be a valid URL"),
Expand Down Expand Up @@ -603,6 +615,7 @@ export const CredentialsSchema = z.union([
MicrosoftClarityCredentialsSchema,
OnePasswordCredentialsSchema,
CloudflareD1CredentialsSchema,
CloudflareR2SqlCredentialsSchema,
CloudflareWorkersObservabilityCredentialsSchema,
CloudflareWebAnalyticsCredentialsSchema,
LinearCredentialsSchema,
Expand All @@ -615,6 +628,7 @@ export type CredentialProviderType = Credentials["type"];
export const DATABASE_CREDENTIAL_PROVIDER = {
BIGQUERY: "bigquery",
CLOUDFLARE_D1: "cloudflare_d1",
CLOUDFLARE_R2_SQL: "cloudflare_r2_sql",
CONNECTOR: "aws_athena_connector",
LAMINAR: "laminar",
MOTHERDUCK: "motherduck",
Expand All @@ -632,6 +646,7 @@ export const DATABASE_CREDENTIAL_PROVIDER_TYPES = [
DATABASE_CREDENTIAL_PROVIDER.SNOWFLAKE,
DATABASE_CREDENTIAL_PROVIDER.BIGQUERY,
DATABASE_CREDENTIAL_PROVIDER.CLOUDFLARE_D1,
DATABASE_CREDENTIAL_PROVIDER.CLOUDFLARE_R2_SQL,
DATABASE_CREDENTIAL_PROVIDER.LAMINAR,
DATABASE_CREDENTIAL_PROVIDER.MOTHERDUCK,
DATABASE_CREDENTIAL_PROVIDER.CONNECTOR,
Expand All @@ -650,6 +665,7 @@ export const credentialSchemaMap = {
bigquery: BigQueryCredentialsSchema,
cal: CalCredentialsSchema,
cloudflare_d1: CloudflareD1CredentialsSchema,
cloudflare_r2_sql: CloudflareR2SqlCredentialsSchema,
confluence: ConfluenceCredentialsSchema,
discord: DiscordCredentialsSchema,
e2b: E2BCredentialsSchema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ exports[`data-sources schema > matches provider type snapshots 1`] = `
"mongodb",
"bigquery",
"cloudflare_d1",
"cloudflare_r2_sql",
"laminar",
"motherduck",
"aws_athena_connector",
Expand Down Expand Up @@ -48,6 +49,7 @@ exports[`data-sources schema > matches provider type snapshots 1`] = `
"mongodb",
"bigquery",
"cloudflare_d1",
"cloudflare_r2_sql",
"laminar",
"motherduck",
"aws_athena_connector",
Expand Down
32 changes: 32 additions & 0 deletions packages/db/src/source-providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
BigQueryCredentialsSchema,
CalCredentialsSchema,
CloudflareD1CredentialsSchema,
CloudflareR2SqlCredentialsSchema,
CloudflareWebAnalyticsCredentialsSchema,
CloudflareWorkersObservabilityCredentialsSchema,
ConfluenceCredentialsSchema,
Expand Down Expand Up @@ -299,6 +300,37 @@ export const SOURCE_PROVIDER_REGISTRY = {
},
},
},
cloudflare_r2_sql: {
label: "Cloudflare R2 SQL",
credentialSchema: CloudflareR2SqlCredentialsSchema,
credentialType: "cloudflare_r2_sql",
connectable: true,
analysisSource: true,
queryInterface: true,
sourceApiInterface: false,
testable: true,
dashboardConnectable: true,
dashboardCredentialForm: "json",
publicCategory: "Warehouses",
guide: {
summary:
"Connect Cloudflare R2 SQL with an account ID, R2 bucket name, and API token for querying R2 Data Catalog Iceberg tables.",
steps: [
"Enable R2 Data Catalog for the bucket that owns the Iceberg tables OneQuery should query.",
"Copy the Cloudflare Account ID and the R2 bucket name from the Cloudflare dashboard or Wrangler.",
"Create a Cloudflare API token with R2 SQL read access plus R2 Data Catalog and R2 storage permissions for the target bucket.",
"Only include `apiBaseUrl` when you need a non-default R2 SQL API origin.",
],
exampleInput: {
sourceKey: "cloudflare_r2_sql_prod",
credentials: {
accountId: "023e105f4ecef8ad9ca31a8372d0c353",
bucketName: "analytics-events",
apiToken: "cloudflare_r2_sql_token",
},
},
},
},
laminar: {
label: "Laminar",
credentialSchema: LaminarCredentialsSchema,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { athenaConnectorQueryDriver } from "../providers/athena-connector/driver";
import { bigQueryQueryDriver } from "../providers/bigquery/driver";
import { cloudflareD1QueryDriver } from "../providers/cloudflare-d1/driver";
import { cloudflareR2SqlQueryDriver } from "../providers/cloudflare-r2-sql/driver";
import { laminarQueryDriver } from "../providers/laminar/driver";
import { motherDuckQueryDriver } from "../providers/motherduck/driver";
import { mysqlQueryDriver } from "../providers/mysql/driver";
Expand All @@ -12,6 +13,7 @@ export const queryDriverRegistry = {
aws_athena_connector: athenaConnectorQueryDriver,
bigquery: bigQueryQueryDriver,
cloudflare_d1: cloudflareD1QueryDriver,
cloudflare_r2_sql: cloudflareR2SqlQueryDriver,
laminar: laminarQueryDriver,
motherduck: motherDuckQueryDriver,
mysql: mysqlQueryDriver,
Expand Down
Loading
Loading