[object Object]

← back to Cli Printing Press

fix(cli): route live cache through typed upserts (#751)

c332b534e59ac6e4d786c48924be81457c85f1a1 · 2026-05-08 15:26:49 -0700 · Trevin Chow

Files touched

Diff

commit c332b534e59ac6e4d786c48924be81457c85f1a1
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Fri May 8 15:26:49 2026 -0700

    fix(cli): route live cache through typed upserts (#751)
---
 internal/generator/generator_test.go             | 72 ++++++++++++++++++++++--
 internal/generator/templates/data_source.go.tmpl | 17 ++----
 2 files changed, 72 insertions(+), 17 deletions(-)

diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index a2723862..b5c4d6a0 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -2364,10 +2364,8 @@ func TestExtractPageItemsNoCursor(t *testing.T) {
 	runGoCommandRequired(t, outputDir, "test", "-run", "TestExtractPageItems", "./internal/cli")
 }
 
-func TestGenerateStoreUpsertBatchDispatchesToTypedTable(t *testing.T) {
-	t.Parallel()
-
-	apiSpec := &spec.APISpec{
+func adsCampaignSpec() *spec.APISpec {
+	return &spec.APISpec{
 		Name:    "ads",
 		Version: "0.1.0",
 		BaseURL: "https://api.example.com",
@@ -2403,7 +2401,12 @@ func TestGenerateStoreUpsertBatchDispatchesToTypedTable(t *testing.T) {
 			},
 		},
 	}
+}
 
+func TestGenerateStoreUpsertBatchDispatchesToTypedTable(t *testing.T) {
+	t.Parallel()
+
+	apiSpec := adsCampaignSpec()
 	outputDir := filepath.Join(t.TempDir(), naming.CLI(apiSpec.Name))
 	gen := New(apiSpec, outputDir)
 	gen.VisionSet = VisionTemplateSet{Store: true}
@@ -2426,6 +2429,67 @@ func TestGenerateStoreUpsertBatchDispatchesToTypedTable(t *testing.T) {
 	runGoCommand(t, outputDir, "test", "./internal/store")
 }
 
+func TestLiveFetchWriteThroughCachePopulatesTypedTable(t *testing.T) {
+	t.Parallel()
+
+	apiSpec := adsCampaignSpec()
+	outputDir := filepath.Join(t.TempDir(), naming.CLI(apiSpec.Name))
+	gen := New(apiSpec, outputDir)
+	gen.VisionSet = VisionTemplateSet{Store: true}
+	require.NoError(t, gen.Generate())
+
+	inlineTest := `package cli
+
+import (
+	"context"
+	"encoding/json"
+	"testing"
+
+	"` + naming.CLI(apiSpec.Name) + `/internal/store"
+)
+
+func TestWriteThroughCachePopulatesTypedTable(t *testing.T) {
+	t.Setenv("HOME", t.TempDir())
+
+	writeThroughCache(context.Background(), "campaigns", json.RawMessage(` + "`" + `[
+		{"id":"camp_1","name":"Launch","status":"active","account_id":"acct_1"}
+	]` + "`" + `))
+
+	db, err := store.Open(defaultDBPath("ads-pp-cli"))
+	if err != nil {
+		t.Fatalf("open cache store: %v", err)
+	}
+	defer db.Close()
+
+	var typedCount int
+	var accountID string
+	if err := db.DB().QueryRow(` + "`" + `SELECT COUNT(*), COALESCE(MAX(account_id), '') FROM campaigns` + "`" + `).Scan(&typedCount, &accountID); err != nil {
+		t.Fatalf("query campaigns: %v", err)
+	}
+	if typedCount != 1 || accountID != "acct_1" {
+		t.Fatalf("typed campaigns = %d/%q, want 1/acct_1", typedCount, accountID)
+	}
+
+	writeThroughCache(context.Background(), "events", json.RawMessage(` + "`" + `[
+		{"id":"evt_1","name":"Seen"}
+	]` + "`" + `))
+
+	var genericCount int
+	if err := db.DB().QueryRow(` + "`" + `SELECT COUNT(*) FROM resources WHERE resource_type = 'events'` + "`" + `).Scan(&genericCount); err != nil {
+		t.Fatalf("query generic resources: %v", err)
+	}
+	if genericCount != 1 {
+		t.Fatalf("generic events count = %d, want 1", genericCount)
+	}
+}
+`
+	testPath := filepath.Join(outputDir, "internal", "cli", "write_through_cache_test.go")
+	require.NoError(t, os.WriteFile(testPath, []byte(inlineTest), 0o644))
+
+	runGoCommandRequired(t, outputDir, "mod", "tidy")
+	runGoCommandRequired(t, outputDir, "test", "-run", "TestWriteThroughCachePopulatesTypedTable", "./internal/cli")
+}
+
 func TestSyncDiscriminatorDispatchRoutesMixedItemsToTypedTables(t *testing.T) {
 	t.Parallel()
 
diff --git a/internal/generator/templates/data_source.go.tmpl b/internal/generator/templates/data_source.go.tmpl
index 234f7cbe..b2397b58 100644
--- a/internal/generator/templates/data_source.go.tmpl
+++ b/internal/generator/templates/data_source.go.tmpl
@@ -187,25 +187,16 @@ func writeThroughCache(ctx context.Context, resourceType string, data json.RawMe
 			}
 			// Single object with an id field (e.g., detail response)
 			if items == nil {
-				if idRaw, ok := envelope["id"]; ok {
-					id := strings.Trim(string(idRaw), "\"")
-					_ = db.Upsert(resourceType, id, data)
+				if _, ok := envelope["id"]; ok {
+					_, _, _ = db.UpsertBatch(resourceType, []json.RawMessage{data})
 					return
 				}
 			}
 		}
 	}
 
-	// Upsert each item individually
-	for _, item := range items {
-		var obj map[string]json.RawMessage
-		if json.Unmarshal(item, &obj) != nil {
-			continue
-		}
-		if idRaw, ok := obj["id"]; ok {
-			id := strings.Trim(string(idRaw), "\"")
-			_ = db.Upsert(resourceType, id, item)
-		}
+	if len(items) > 0 {
+		_, _, _ = db.UpsertBatch(resourceType, items)
 	}
 }
 

← c397021f fix(cli): harden force-generate preservation (#750)  ·  back to Cli Printing Press  ·  Add issue ownership guidance to AGENTS.md (#754) 7990021b →