Skip to content

Commit a222955

Browse files
committed
refactor: modernize types and reduce allocation overhead
1 parent 89a6068 commit a222955

10 files changed

Lines changed: 115 additions & 75 deletions

File tree

internal/adapters/ai/claude_client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func (c *ClaudeClient) ChatWithTools(ctx context.Context, req *domain.ChatReques
126126
args, ok := content.Input.(map[string]any)
127127
if !ok {
128128
// Best-effort conversion: re-serialize and deserialize to get map[string]any.
129-
// json.Marshal can't fail on a valid interface{} from API response.
129+
// json.Marshal can't fail on a valid any from API response.
130130
// Unmarshal errors ignored - empty args is acceptable fallback.
131131
inputBytes, _ := json.Marshal(content.Input)
132132
_ = json.Unmarshal(inputBytes, &args)

internal/air/server_lifecycle.go

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,10 @@ func NewServer(addr string) *Server {
4848
tmpl = nil
4949
}
5050

51-
// Initialize cache
51+
// Load cache settings; runtime cache components are initialized at server start.
5252
cacheCfg := cache.DefaultConfig()
53-
cacheManager, _ := cache.NewManager(cacheCfg)
5453
cacheSettings, _ := cache.LoadSettings(cacheCfg.BasePath)
5554

56-
// Initialize photo store with shared database
57-
var photoStore *cache.PhotoStore
58-
photoDB, err := cache.OpenSharedDB(cacheCfg.BasePath, "photos.db")
59-
if err == nil {
60-
photoStore, _ = cache.NewPhotoStore(photoDB, cacheCfg.BasePath, cache.DefaultPhotoTTL)
61-
// Prune expired photos on startup
62-
if photoStore != nil {
63-
go func() {
64-
if pruned, err := photoStore.Prune(); err == nil && pruned > 0 {
65-
fmt.Fprintf(os.Stderr, "Pruned %d expired photos from cache\n", pruned)
66-
}
67-
}()
68-
}
69-
}
70-
7155
return &Server{
7256
addr: addr,
7357
demoMode: false,
@@ -78,15 +62,63 @@ func NewServer(addr string) *Server {
7862
nylasClient: nylasClient,
7963
templates: tmpl,
8064
hasAPIKey: hasAPIKey,
81-
cacheManager: cacheManager,
8265
cacheSettings: cacheSettings,
83-
photoStore: photoStore,
8466
offlineQueues: make(map[string]*cache.OfflineQueue),
8567
syncStopCh: make(chan struct{}),
8668
isOnline: true,
8769
}
8870
}
8971

72+
// initCacheRuntime initializes runtime cache components for the server.
73+
// This is intentionally deferred until Start() so NewServer remains lightweight.
74+
func (s *Server) initCacheRuntime() {
75+
if s.demoMode || s.cacheManager != nil {
76+
return
77+
}
78+
79+
cacheCfg := cache.DefaultConfig()
80+
81+
// If settings weren't loaded during construction, best-effort load them now.
82+
if s.cacheSettings == nil {
83+
settings, err := cache.LoadSettings(cacheCfg.BasePath)
84+
if err != nil {
85+
return
86+
}
87+
s.cacheSettings = settings
88+
}
89+
90+
// Respect cache enablement from settings.
91+
if !s.cacheSettings.IsCacheEnabled() {
92+
return
93+
}
94+
95+
cacheCfg = s.cacheSettings.ToConfig(cacheCfg.BasePath)
96+
97+
cacheManager, err := cache.NewManager(cacheCfg)
98+
if err != nil {
99+
return
100+
}
101+
s.cacheManager = cacheManager
102+
103+
photoDB, err := cache.OpenSharedDB(cacheCfg.BasePath, "photos.db")
104+
if err != nil {
105+
return
106+
}
107+
108+
photoStore, err := cache.NewPhotoStore(photoDB, cacheCfg.BasePath, cache.DefaultPhotoTTL)
109+
if err != nil {
110+
return
111+
}
112+
s.photoStore = photoStore
113+
114+
// Prune expired photos asynchronously after startup.
115+
go func() {
116+
if pruned, err := photoStore.Prune(); err == nil && pruned > 0 {
117+
fmt.Fprintf(os.Stderr, "Pruned %d expired photos from cache\n", pruned)
118+
}
119+
}()
120+
}
121+
90122
// NewDemoServer creates an Air server in demo mode with sample data.
91123
func NewDemoServer(addr string) *Server {
92124
tmpl, err := loadTemplates()
@@ -256,7 +288,10 @@ func (s *Server) Start() error {
256288
// Template-rendered index page
257289
mux.HandleFunc("/", s.handleIndex)
258290

259-
// Start background sync if not in demo mode and cache is enabled
291+
// Initialize cache runtime components after routes are wired.
292+
s.initCacheRuntime()
293+
294+
// Start background sync if cache is available and enabled.
260295
if !s.demoMode && s.cacheManager != nil && s.cacheSettings != nil && s.cacheSettings.IsCacheEnabled() {
261296
s.startBackgroundSync()
262297
}

internal/air/server_template.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package air
33
import (
44
"html/template"
55
"net/http"
6+
"sync"
67

78
"github.com/nylas/cli/internal/domain"
89
)
@@ -150,12 +151,16 @@ func initials(email string) string {
150151

151152
// loadTemplates parses all template files.
152153
func loadTemplates() (*template.Template, error) {
153-
return template.New("").Funcs(templateFuncs).ParseFS(
154-
templateFiles,
155-
"templates/*.gohtml",
156-
"templates/partials/*.gohtml",
157-
"templates/pages/*.gohtml",
158-
)
154+
templatesOnce.Do(func() {
155+
parsedTemplates, parsedTemplatesErr = template.New("").Funcs(templateFuncs).ParseFS(
156+
templateFiles,
157+
"templates/*.gohtml",
158+
"templates/partials/*.gohtml",
159+
"templates/pages/*.gohtml",
160+
)
161+
})
162+
163+
return parsedTemplates, parsedTemplatesErr
159164
}
160165

161166
// Template functions.
@@ -165,3 +170,9 @@ var templateFuncs = template.FuncMap{
165170
return template.HTML(s)
166171
},
167172
}
173+
174+
var (
175+
templatesOnce sync.Once
176+
parsedTemplates *template.Template
177+
parsedTemplatesErr error
178+
)

internal/cli/common/crud.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -280,23 +280,25 @@ func NewDeleteCommand(config DeleteCommandConfig) *cobra.Command {
280280
// Aliases: []string{"get", "read"},
281281
// Short: "Show contact details",
282282
// ResourceName: "contact",
283-
// GetFunc: client.GetContact,
284-
// DisplayFunc: func(resource interface{}) error {
283+
// GetFunc: func(ctx context.Context, client ports.NylasClient, grantID, resourceID string) (any, error) {
284+
// return client.GetContact(ctx, grantID, resourceID)
285+
// },
286+
// DisplayFunc: func(resource any) error {
285287
// contact := resource.(*domain.Contact)
286288
// fmt.Printf("Name: %s\n", contact.DisplayName())
287289
// return nil
288290
// },
289291
// GetClient: getClient,
290292
// })
291293
type ShowCommandConfig struct {
292-
Use string // Cobra Use string
293-
Aliases []string // Cobra aliases
294-
Short string // Short description
295-
Long string // Long description
296-
ResourceName string // Resource name for error messages
297-
GetFunc func(ctx context.Context, grantID, resourceID string) (interface{}, error) // Get function
298-
DisplayFunc func(resource interface{}) error // Custom display function
299-
GetClient func() (ports.NylasClient, error) // Client getter function
294+
Use string // Cobra Use string
295+
Aliases []string // Cobra aliases
296+
Short string // Short description
297+
Long string // Long description
298+
ResourceName string // Resource name for error messages
299+
GetFunc func(ctx context.Context, client ports.NylasClient, grantID, resourceID string) (any, error) // Get function
300+
DisplayFunc func(resource any) error // Custom display function
301+
GetClient func() (ports.NylasClient, error) // Client getter function
300302
}
301303

302304
// NewShowCommand creates a fully configured show command with custom display logic.
@@ -319,7 +321,7 @@ func NewShowCommand(config ShowCommandConfig) *cobra.Command {
319321
}
320322

321323
// Get client
322-
_, err = config.GetClient()
324+
client, err := config.GetClient()
323325
if err != nil {
324326
return err
325327
}
@@ -329,7 +331,7 @@ func NewShowCommand(config ShowCommandConfig) *cobra.Command {
329331
defer cancel()
330332

331333
// Fetch resource
332-
resource, err := config.GetFunc(ctx, resourceArgs.GrantID, resourceArgs.ResourceID)
334+
resource, err := config.GetFunc(ctx, client, resourceArgs.GrantID, resourceArgs.ResourceID)
333335
if err != nil {
334336
return WrapGetError(config.ResourceName, err)
335337
}

internal/cli/contacts/groups.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,10 @@ func newGroupsShowCmd() *cobra.Command {
7676
Use: "show <group-id> [grant-id]",
7777
Short: "Show contact group details",
7878
ResourceName: "contact group",
79-
GetFunc: func(ctx context.Context, grantID, resourceID string) (interface{}, error) {
80-
client, err := common.GetNylasClient()
81-
if err != nil {
82-
return nil, err
83-
}
79+
GetFunc: func(ctx context.Context, client ports.NylasClient, grantID, resourceID string) (any, error) {
8480
return client.GetContactGroup(ctx, grantID, resourceID)
8581
},
86-
DisplayFunc: func(resource interface{}) error {
82+
DisplayFunc: func(resource any) error {
8783
group := resource.(*domain.ContactGroup)
8884

8985
fmt.Println("════════════════════════════════════════════════════════════")

internal/cli/email/drafts.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,16 +281,14 @@ func detectContentType(filename string, content []byte) string {
281281
}
282282

283283
func newDraftsShowCmd() *cobra.Command {
284-
client, _ := common.GetNylasClient()
285-
286284
return common.NewShowCommand(common.ShowCommandConfig{
287285
Use: "show <draft-id> [grant-id]",
288286
Short: "Show draft details",
289287
ResourceName: "draft",
290-
GetFunc: func(ctx context.Context, grantID, resourceID string) (interface{}, error) {
288+
GetFunc: func(ctx context.Context, client ports.NylasClient, grantID, resourceID string) (any, error) {
291289
return client.GetDraft(ctx, grantID, resourceID)
292290
},
293-
DisplayFunc: func(resource interface{}) error {
291+
DisplayFunc: func(resource any) error {
294292
draft := resource.(*domain.Draft)
295293

296294
fmt.Println("════════════════════════════════════════════════════════════")

internal/cli/email/folders.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,16 +104,14 @@ func newFoldersListCmd() *cobra.Command {
104104
}
105105

106106
func newFoldersShowCmd() *cobra.Command {
107-
client, _ := common.GetNylasClient()
108-
109107
return common.NewShowCommand(common.ShowCommandConfig{
110108
Use: "show <folder-id> [grant-id]",
111109
Short: "Show folder details",
112110
ResourceName: "folder",
113-
GetFunc: func(ctx context.Context, grantID, resourceID string) (interface{}, error) {
111+
GetFunc: func(ctx context.Context, client ports.NylasClient, grantID, resourceID string) (any, error) {
114112
return client.GetFolder(ctx, grantID, resourceID)
115113
},
116-
DisplayFunc: func(resource interface{}) error {
114+
DisplayFunc: func(resource any) error {
117115
folder := resource.(*domain.Folder)
118116

119117
fmt.Println("════════════════════════════════════════════════════════════")

internal/cli/integration/ai_break_awareness_test_advanced.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestCLI_AI_FocusTime_BreakAwareness(t *testing.T) {
2929
// Load AI config from user's config file
3030
aiConfig := getAIConfigFromUserConfig()
3131
if aiConfig == nil {
32-
aiConfig = map[string]interface{}{
32+
aiConfig = map[string]any{
3333
"default_provider": getAvailableProvider(),
3434
}
3535
}
@@ -46,7 +46,7 @@ func TestCLI_AI_FocusTime_BreakAwareness(t *testing.T) {
4646
t.Fatalf("Failed to create config directory: %v", err)
4747
}
4848

49-
config := map[string]interface{}{
49+
config := map[string]any{
5050
"region": "us",
5151
"callback_port": 8080,
5252
"grants": []map[string]string{
@@ -56,8 +56,8 @@ func TestCLI_AI_FocusTime_BreakAwareness(t *testing.T) {
5656
"provider": "google",
5757
},
5858
},
59-
"working_hours": map[string]interface{}{
60-
"default": map[string]interface{}{
59+
"working_hours": map[string]any{
60+
"default": map[string]any{
6161
"enabled": true,
6262
"start": "09:00",
6363
"end": "17:00",
@@ -145,7 +145,7 @@ func TestCLI_AI_Scheduling_BreakAwareness(t *testing.T) {
145145
// Load AI config from user's config file
146146
aiConfig := getAIConfigFromUserConfig()
147147
if aiConfig == nil {
148-
aiConfig = map[string]interface{}{
148+
aiConfig = map[string]any{
149149
"default_provider": getAvailableProvider(),
150150
}
151151
}
@@ -162,7 +162,7 @@ func TestCLI_AI_Scheduling_BreakAwareness(t *testing.T) {
162162
t.Fatalf("Failed to create config directory: %v", err)
163163
}
164164

165-
config := map[string]interface{}{
165+
config := map[string]any{
166166
"region": "us",
167167
"callback_port": 8080,
168168
"grants": []map[string]string{
@@ -172,8 +172,8 @@ func TestCLI_AI_Scheduling_BreakAwareness(t *testing.T) {
172172
"provider": "google",
173173
},
174174
},
175-
"working_hours": map[string]interface{}{
176-
"default": map[string]interface{}{
175+
"working_hours": map[string]any{
176+
"default": map[string]any{
177177
"enabled": true,
178178
"start": "09:00",
179179
"end": "17:00",
@@ -272,7 +272,7 @@ func TestCLI_AI_ConflictDetection_BreakAwareness(t *testing.T) {
272272
// Load AI config from user's config file
273273
aiConfig := getAIConfigFromUserConfig()
274274
if aiConfig == nil {
275-
aiConfig = map[string]interface{}{
275+
aiConfig = map[string]any{
276276
"default_provider": getAvailableProvider(),
277277
}
278278
}
@@ -289,7 +289,7 @@ func TestCLI_AI_ConflictDetection_BreakAwareness(t *testing.T) {
289289
t.Fatalf("Failed to create config directory: %v", err)
290290
}
291291

292-
config := map[string]interface{}{
292+
config := map[string]any{
293293
"region": "us",
294294
"callback_port": 8080,
295295
"grants": []map[string]string{
@@ -299,8 +299,8 @@ func TestCLI_AI_ConflictDetection_BreakAwareness(t *testing.T) {
299299
"provider": "google",
300300
},
301301
},
302-
"working_hours": map[string]interface{}{
303-
"default": map[string]interface{}{
302+
"working_hours": map[string]any{
303+
"default": map[string]any{
304304
"enabled": true,
305305
"start": "09:00",
306306
"end": "17:00",

internal/cli/integration/ai_break_awareness_test_basic.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestCLI_AI_BreakTimeAwareness(t *testing.T) {
3737
aiConfig := getAIConfigFromUserConfig()
3838
if aiConfig == nil {
3939
// Fallback to minimal config if user config not found
40-
aiConfig = map[string]interface{}{
40+
aiConfig = map[string]any{
4141
"default_provider": aiProvider,
4242
}
4343
}
@@ -51,7 +51,7 @@ func TestCLI_AI_BreakTimeAwareness(t *testing.T) {
5151
configDir := t.TempDir()
5252
configPath := filepath.Join(configDir, "config.yaml")
5353

54-
config := map[string]interface{}{
54+
config := map[string]any{
5555
"region": "us",
5656
"callback_port": 8080,
5757
"grants": []map[string]string{
@@ -61,8 +61,8 @@ func TestCLI_AI_BreakTimeAwareness(t *testing.T) {
6161
"provider": "google",
6262
},
6363
},
64-
"working_hours": map[string]interface{}{
65-
"default": map[string]interface{}{
64+
"working_hours": map[string]any{
65+
"default": map[string]any{
6666
"enabled": true,
6767
"start": "09:00",
6868
"end": "17:00",

0 commit comments

Comments
 (0)