Skip to content
Closed
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
852c2f6
feat(runtime): add GenerateTemplate RPC handler for YAML generation
lovincyrus Feb 20, 2026
8252f8e
feat(web): migrate frontend to GenerateTemplate RPC for YAML generation
lovincyrus Feb 20, 2026
7b57e05
feat(runtime): complete GenerateTemplate RPC migration for HTTPS conn…
lovincyrus Feb 21, 2026
106af5f
chore: untrack personal docs and add to .gitignore
lovincyrus Feb 21, 2026
776d920
docs(runtime): explain why EnvVarName can't be inferred
lovincyrus Feb 21, 2026
d82fd9f
fix: resolve golangci-lint, gofmt, and ESLint errors
lovincyrus Feb 21, 2026
033d1e5
fix: remove trailing newline in generate_template_test.go
lovincyrus Feb 21, 2026
66b617d
brainstorm, plan
lovincyrus Feb 26, 2026
4345073
What was built New runtime/templates/ package (7 Go files) template.g…
royendo Mar 3, 2026
763777c
sort templates
royendo Mar 3, 2026
2dcab0f
backend gen
royendo Mar 4, 2026
6074d98
renamign files, fix small bugs
royendo Mar 4, 2026
5f908af
removal .ts migrate to templates, singular tmeplate for moidel and co…
royendo Mar 4, 2026
6eb3971
add comments to YAML
royendo Mar 4, 2026
4254c24
fix model based icons
royendo Mar 4, 2026
53b5003
icons in templates, duck and click
royendo Mar 4, 2026
b136e2b
rmeove (OLAP)
royendo Mar 4, 2026
5d5eca7
use template icons
royendo Mar 4, 2026
31be329
fix clickhouse sources
royendo Mar 4, 2026
dd1adca
cont
royendo Mar 4, 2026
970a4ae
salesforce fix
royendo Mar 5, 2026
860ebde
fix generated sql indentation
royendo Mar 5, 2026
91a8420
rmeove drive r fall back
royendo Mar 5, 2026
b98d810
feedback on tmeplating, actually check the correct olap_connector
royendo Mar 5, 2026
9707361
actuqally check olap part 2
royendo Mar 5, 2026
6918878
https for clickhouse headers
royendo Mar 5, 2026
e3148a8
icons to parent level
royendo Mar 5, 2026
bcb7712
local file and sqlite dont work due to clickhouse securty requirement…
royendo Mar 5, 2026
185699f
remove
royendo Mar 5, 2026
4a7b60b
Merge remote-tracking branch 'origin/main' into feat-template-connector
royendo Mar 5, 2026
892be63
Create supabase-duckdb.json
royendo Mar 5, 2026
4866038
merge fixes: add stub templates, supabase definition, fix tests for A…
royendo Mar 5, 2026
7fb71ff
fix tests: populate schema cache for API-driven connector schemas
royendo Mar 5, 2026
35ff46d
style: format connector-schemas.spec.ts
royendo Mar 5, 2026
75c5b35
coming soon.
royendo Mar 5, 2026
531ea3b
remove
royendo Mar 5, 2026
f4f1bfa
fix: resolve CI lint errors in `AddDataModal` and `submitAddDataForm`
royendo Mar 6, 2026
e0f0bb1
Merge remote-tracking branch 'origin/main' into feat-template-connector
royendo Mar 6, 2026
999b959
openAPI
royendo Mar 6, 2026
bb7254b
Adopt connectRPC `RuntimeClient` pattern in template modal code
royendo Mar 6, 2026
e64961f
issue with preview requiring refresh fix, add public options for clou…
royendo Mar 6, 2026
49f7c72
fix gcs env var
royendo Mar 6, 2026
4540850
fix passing proper schema paramaeters
royendo Mar 6, 2026
52299f8
prettier
royendo Mar 6, 2026
60f0b4d
prettier 2
royendo Mar 6, 2026
791f5cb
gofmt
royendo Mar 6, 2026
ffa4725
clean up
royendo Mar 6, 2026
5135ba1
local Ux / Code review
royendo Mar 6, 2026
7813bf3
local review UX and code
royendo Mar 6, 2026
b3781c9
fix managed CH, and nit display name fixes
royendo Mar 6, 2026
d044a24
supabase
royendo Mar 6, 2026
2b26ac2
review
royendo Mar 6, 2026
3a739b7
Delete docs/brainstorms/2026-02-16-generate-template-api-brainstorm.md
royendo Mar 9, 2026
8dd3922
Delete docs/plans/2026-02-16-feat-generate-template-rpc-plan.md
royendo Mar 9, 2026
3a0be90
chc support table engines
royendo Mar 23, 2026
a7a27d2
git merge main
royendo Apr 2, 2026
cb59678
Merge branch 'main' into feat-template-connector
royendo Apr 2, 2026
c51a731
fix: restore toConnectorDriver and getDocsCategory exports
royendo Apr 2, 2026
c25a987
fix: restore code-utils.ts exports from main
royendo Apr 2, 2026
3f212cf
fix: restore schema-utils.ts from main
royendo Apr 2, 2026
0bb6293
fix: restore missing imports in submitAddDataForm.ts
royendo Apr 2, 2026
97f8e25
Fix Icons svelte
royendo Apr 2, 2026
112f2aa
prettier
royendo Apr 2, 2026
83d2bb5
web code
royendo Apr 3, 2026
81f2533
Merge branch 'main' into feat-template-connector
royendo Apr 6, 2026
fb0c6db
re-implement
royendo Apr 6, 2026
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
260 changes: 260 additions & 0 deletions docs/brainstorms/2026-02-16-generate-template-api-brainstorm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
# Tech Design: YAML Generation — Imperative Functions vs. GenerateTemplate API

**Date:** 2026-02-16
**Status:** Brainstorm
**Author:** Cyrus Goh

---

## Problem Statement

The frontend has two imperative YAML builder functions — `compileSourceYAML()` and `compileConnectorYAML()` — that share overlapping logic but diverge in subtle ways. This creates:

1. **Duplication:** Both functions handle secret extraction (`{{ .env.* }}` placeholders), string quoting, empty-value filtering, and property ordering — but with separate, slightly different implementations.
2. **Maintenance burden:** Adding a new connector or field type requires touching complex conditional logic with many special cases (ClickHouse `managed: false` exclusion, DuckDB SQL rewriting, HTTP header formatting, etc.).
3. **Frontend owns YAML format:** The frontend knows too much about what valid YAML looks like for each resource type. This knowledge should live closer to the backend, which actually parses and validates these files.

### Current Architecture

```
Form Data → compileSourceYAML() / compileConnectorYAML() → YAML string → PutFile RPC → Backend
(imperative string builder)
```

**Key files:**
- `web-common/src/features/sources/sourceUtils.ts` — `compileSourceYAML()` (~90 lines)
- `web-common/src/features/connectors/code-utils.ts` — `compileConnectorYAML()` (~100 lines)
- `web-common/src/features/sources/modal/submitAddDataForm.ts` — primary caller
- `web-common/src/features/sources/modal/AddDataFormManager.ts` — YAML preview caller

### Shared Logic (duplicated between the two functions)

| Concern | compileSourceYAML | compileConnectorYAML |
|---------|-------------------|----------------------|
| YAML header with doc link | Yes | Yes |
| Secret → `{{ .env.VAR }}` | Yes | Yes |
| String property quoting | Yes | Yes |
| Empty value filtering | Yes | Yes |
| Env var name generation | Via `makeEnvVarKey()` | Via `makeEnvVarKey()` |
| SQL multi-line formatting | Yes | No |
| Headers map formatting | No | Yes |
| Property ordering | Implicit (object key order) | Explicit (orderedProperties) |
| Field filtering | Implicit (step-based) | Explicit (fieldFilter function) |
| Dev section | Yes (with Redshift exception) | No |

---

## Approach A: Consolidate Imperative Functions (Frontend-Only Refactor)

Merge `compileConnectorYAML` and `compileSourceYAML` into a unified `compileResourceYAML()` function that handles all resource types through configuration.

### Design

```typescript
interface ResourceYAMLOptions {
resourceType: "connector" | "source" | "model";
driver: string;
formValues: Record<string, unknown>;
orderedProperties?: ConnectorDriverProperty[];
fieldFilter?: (property: ConnectorDriverProperty) => boolean;
secretKeys?: string[];
stringKeys?: string[];
connectorInstanceName?: string;
schema?: { properties?: Record<string, { "x-env-var-name"?: string }> };
existingEnvBlob?: string;
includeDevSection?: boolean;
originalDriverName?: string;
}

function compileResourceYAML(opts: ResourceYAMLOptions): string {
// Unified logic:
// 1. Generate header (type + driver + doc link)
// 2. Filter properties (by step, field filter, empty values)
// 3. Order properties (explicit or implicit)
// 4. Format each property:
// - Secrets → {{ .env.VAR }}
// - Strings → quoted
// - SQL → multi-line
// - Headers → YAML map
// - Default → raw value
// 5. Optional dev section
// 6. Return assembled YAML
}
```

### Migration Path

1. Create `compileResourceYAML()` with all shared logic
2. Re-implement `compileSourceYAML()` and `compileConnectorYAML()` as thin wrappers
3. Gradually migrate callers to the unified function
4. Remove wrappers once all callers are migrated

### Pros

- No backend changes required
- Incremental, low-risk refactor
- Eliminates duplication between the two functions
- Can be done in a single PR

### Cons

- Frontend still owns YAML format knowledge — can drift from backend expectations
- Special cases keep accumulating as connectors are added
- Doesn't extend to other resource types (explores, dashboards, etc.) without growing the function
- YAML string building is inherently fragile (indentation, quoting, escaping)

### Effort Estimate

Small — 1-2 days of frontend work.

---

## Approach B: Backend `GenerateTemplate` RPC (Recommended)

New backend endpoint that accepts structured data and returns rendered YAML. The frontend becomes a thin form layer.

### Design

#### New RPC

```protobuf
// GenerateTemplate renders a YAML file from structured input.
// Supports all resource types: connector, source, model, explore, dashboard, etc.
rpc GenerateTemplate(GenerateTemplateRequest) returns (GenerateTemplateResponse) {
option (google.api.http) = {
post: "/v1/instances/{instance_id}/generate/template",
body: "*"
};
}

message GenerateTemplateRequest {
string instance_id = 1;
// Resource type: "connector", "source", "model", "explore", "metrics_view", etc.
string resource_type = 2;
// Driver name (for connectors/sources): "clickhouse", "s3", "duckdb", etc.
string driver = 3;
// Structured key-value properties from the form
google.protobuf.Struct properties = 4;
// Optional: connector instance name (for sources that reference a connector)
string connector_name = 5;
// Optional: fields that should be treated as secrets (extracted to .env)
repeated string secret_keys = 6;
}

message GenerateTemplateResponse {
// Rendered YAML blob, ready to write via PutFile
string blob = 1;
// Environment variables to write to .env (key → value)
map<string, string> env_vars = 2;
}
```

#### New Architecture

```
Form Data → GenerateTemplate RPC → { blob, env_vars } → PutFile RPC (blob) + update .env (env_vars)
(backend)
```

#### Backend Implementation

The backend would:
1. Look up a Go template for the given `resource_type` + `driver` combination
2. Apply structured properties to the template
3. Handle secret extraction: replace secret values with `{{ .env.VAR }}` and return the real values in `env_vars`
4. Return the rendered YAML blob

Templates could be Go `text/template` files or a simple struct-to-YAML mapper using the `gopkg.in/yaml.v3` library.

#### Frontend Changes

```typescript
// Before (imperative)
const blob = compileConnectorYAML(connector, formValues, { ...options });
await runtimeServicePutFile(instanceId, { path, blob, create: true });
await updateDotEnvWithSecrets(instanceId, connector, formValues, options);

// After (declarative)
const { blob, envVars } = await runtimeServiceGenerateTemplate(instanceId, {
resourceType: "connector",
driver: connector.name,
properties: formValues,
secretKeys: schemaSecretKeys,
});
await runtimeServicePutFile(instanceId, { path, blob, create: true });
await writeDotEnv(instanceId, envVars);
```

### Migration Path

1. **Phase 1:** Implement `GenerateTemplate` RPC for connectors
2. **Phase 2:** Migrate `compileConnectorYAML()` callers to use the RPC
3. **Phase 3:** Add source/model support, migrate `compileSourceYAML()` callers
4. **Phase 4:** Extend to other resource types (explores, dashboards, etc.)
5. **Phase 5:** Remove frontend compile functions

Each phase can be a separate PR. Old and new paths can coexist during migration.

### Pros

- Backend owns the canonical YAML format — single source of truth
- Frontend complexity drops dramatically — no YAML string building, quoting, or escaping
- Generalizes to all resource types (connectors, sources, models, explores, dashboards, APIs, canvases, themes, reports, alerts)
- Aligns with existing backend generation patterns (`GenerateMetricsViewFile`, `GenerateCanvas`, `GenerateResolver`)
- Secret handling can be co-located with template logic
- Backend can validate property completeness before rendering

### Cons

- Requires backend work (new RPC, template registry, tests)
- Larger scope — multi-phase migration across frontend and backend
- YAML preview in the form is lost (acceptable per discussion — not critical)
- Network round-trip for template rendering (acceptable — only on submit)
- Need to decide where templates live (Go code, embedded files, or database)

### Effort Estimate

Medium — 1-2 weeks across backend + frontend.

---

## Open Questions

1. **Secret handling ownership:** Should the backend fully own env var naming and `.env` file writes? Or should it just return `env_vars` and let the frontend write them?
- Option A: Backend returns `env_vars` map, frontend writes to `.env` (simpler, frontend already has this code)
- Option B: Backend writes `.env` directly as part of `GenerateTemplate` (cleaner, but couples template rendering with file I/O)

2. **Template storage:** Where do the YAML templates live?
- Go struct-to-YAML mapping (type-safe, no template files)
- Embedded Go `text/template` files (flexible, easy to edit)
- Hardcoded strings in Go (simple, like current frontend approach but server-side)

3. **DuckDB rewriting:** Currently `maybeRewriteToDuckDb()` transforms S3/GCS/HTTPS sources into DuckDB SQL. Should this logic move to the backend too, or stay as a frontend preprocessing step?

4. **Connector property metadata:** The frontend schemas define `x-secret`, `x-string`, `x-env-var-name` extensions. Should the backend be the source of truth for these, or should the frontend continue to pass `secretKeys`/`stringKeys` in the request?

5. **Backward compatibility:** Do we need to support both old (imperative) and new (RPC) paths simultaneously during migration? Or can we cut over all at once?

---

## Recommendation

**Approach B (Backend `GenerateTemplate` RPC)** is recommended because:

- It addresses the root cause: the frontend shouldn't own YAML format knowledge
- It's a natural extension of existing backend patterns (`GenerateMetricsViewFile`, etc.)
- It generalizes to all resource types, not just connectors/sources
- The migration can be incremental (phase by phase)
- Loss of YAML preview is acceptable

Approach A is a valid stopgap if backend bandwidth is limited, but it doesn't solve the fundamental problem of frontend-owned YAML format.

---

## Key Decisions Made

- **Motivation:** Duplication and maintenance burden in `compileSourceYAML`/`compileConnectorYAML`
- **Direction:** Backend RPC over frontend-only refactor
- **Scope:** All resource types (design wide), implement connectors/sources first
- **YAML Preview:** Can be dropped — not critical for users
- **Secret handling:** Open question to resolve during planning
Loading
Loading