From 983306e00416d093f7f189a070ffc869b1c34f9d Mon Sep 17 00:00:00 2001
From: altair823 <kimth0734@gmail.com>
Date: Tue, 5 May 2026 01:33:30 +0900
Subject: [PATCH] =?UTF-8?q?refactor(v026):=20#22=20NoteRepository=20hydrat?=
 =?UTF-8?q?e=20row=20type=20=ED=86=B5=EC=9D=BC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

db.prepare().all() 의 row type cast s any[] / s unknown[] →
s Record<string, unknown>[] 일괄 통일. hydrate() signature 도 동일
(이미 그렇거나 갱신).

- TS strict 환경 친화 (any 보다 narrow)
- 향후 explicit row interface 추가 시 base 형 명확
- runtime 동작 변경 0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 src/main/repository/NoteRepository.ts | 64 +++++++++++++--------------
 1 file changed, 32 insertions(+), 32 deletions(-)

diff --git a/src/main/repository/NoteRepository.ts b/src/main/repository/NoteRepository.ts
index 291c4eb..3d47050 100644
--- a/src/main/repository/NoteRepository.ts
+++ b/src/main/repository/NoteRepository.ts
@@ -78,7 +78,7 @@ export class NoteRepository {
   }
 
   findById(id: string): Note | null {
-    const row = this.db.prepare('SELECT * FROM notes WHERE id=?').get(id) as any;
+    const row = this.db.prepare('SELECT * FROM notes WHERE id=?').get(id) as Record<string, unknown>;
     if (!row) return null;
     return this.hydrate(row);
   }
@@ -92,21 +92,21 @@ export class NoteRepository {
               WHERE deleted_at IS NULL AND created_at < ?
               ORDER BY created_at DESC, id DESC LIMIT ?`
           )
-          .all(opts.cursor, limit) as any[])
+          .all(opts.cursor, limit) as Record<string, unknown>[])
       : (this.db
           .prepare(
             `SELECT * FROM notes
               WHERE deleted_at IS NULL
               ORDER BY created_at DESC, id DESC LIMIT ?`
           )
-          .all(limit) as any[]);
+          .all(limit) as Record<string, unknown>[]);
     return rows.map((r) => this.hydrate(r));
   }
 
   listAll(): Note[] {
     const rows = this.db
       .prepare(`SELECT * FROM notes WHERE deleted_at IS NULL ORDER BY created_at ASC, id ASC`)
-      .all() as any[];
+      .all() as Record<string, unknown>[];
     return rows.map((r) => this.hydrate(r));
   }
 
@@ -453,7 +453,7 @@ export class NoteRepository {
     const limit = Math.max(1, Math.min(200, opts.limit));
     const rows = this.db
       .prepare(`SELECT * FROM notes WHERE deleted_at IS NOT NULL ORDER BY deleted_at DESC, id DESC LIMIT ?`)
-      .all(limit) as any[];
+      .all(limit) as Record<string, unknown>[];
     return rows.map((r) => this.hydrate(r));
   }
 
@@ -611,18 +611,18 @@ export class NoteRepository {
             AND ai_status = 'done'
           ORDER BY created_at DESC, id DESC`
       )
-      .all(today) as any[];
+      .all(today) as Record<string, unknown>[];
     return rows.map((r) => this.hydrate(r));
   }
 
   getAllPendingJobs(): Array<{ noteId: string; attempts: number; nextRunAt: string }> {
     const rows = this.db
       .prepare(`SELECT note_id, attempts, next_run_at FROM pending_jobs`)
-      .all() as any[];
+      .all() as Record<string, unknown>[];
     return rows.map((r) => ({
-      noteId: r.note_id,
-      attempts: r.attempts,
-      nextRunAt: r.next_run_at
+      noteId: r.note_id as string,
+      attempts: r.attempts as number,
+      nextRunAt: r.next_run_at as string
     }));
   }
 
@@ -638,39 +638,39 @@ export class NoteRepository {
       .run(nextRunAt, lastError.slice(0, 500), noteId);
   }
 
-  private hydrate(row: any): Note {
+  private hydrate(row: Record<string, unknown>): Note {
     const tags = this.db
       .prepare(
         `SELECT t.name, nt.source
            FROM note_tags nt JOIN tags t ON t.id = nt.tag_id
           WHERE nt.note_id = ? ORDER BY t.name`
       )
-      .all(row.id) as Array<{ name: string; source: 'ai' | 'user' }>;
+      .all(row.id as string) as Array<{ name: string; source: 'ai' | 'user' }>;
     const media = this.db
       .prepare(
         `SELECT id, kind, rel_path as relPath, mime, bytes FROM media WHERE note_id=?`
       )
-      .all(row.id) as NoteMedia[];
+      .all(row.id as string) as NoteMedia[];
     return {
-      id: row.id,
-      rawText: row.raw_text,
-      aiTitle: row.ai_title,
-      aiSummary: row.ai_summary,
-      aiStatus: row.ai_status,
-      aiError: row.ai_error,
-      aiProvider: row.ai_provider,
-      aiGeneratedAt: row.ai_generated_at,
-      titleEditedByUser: row.title_edited_by_user === 1,
-      summaryEditedByUser: row.summary_edited_by_user === 1,
-      userIntent: row.user_intent,
-      intentPromptedAt: row.intent_prompted_at,
-      dueDate: row.due_date ?? null,
-      dueDateEditedByUser: row.due_date_edited_by_user === 1,
-      deletedAt: row.deleted_at ?? null,
-      lastRecalledAt: row.last_recalled_at ?? null,
-      recallDismissedAt: row.recall_dismissed_at ?? null,
-      createdAt: row.created_at,
-      updatedAt: row.updated_at,
+      id: row.id as string,
+      rawText: row.raw_text as string,
+      aiTitle: row.ai_title as string | null,
+      aiSummary: row.ai_summary as string | null,
+      aiStatus: row.ai_status as 'pending' | 'done' | 'failed',
+      aiError: row.ai_error as string | null,
+      aiProvider: row.ai_provider as string | null,
+      aiGeneratedAt: row.ai_generated_at as string | null,
+      titleEditedByUser: (row.title_edited_by_user as number) === 1,
+      summaryEditedByUser: (row.summary_edited_by_user as number) === 1,
+      userIntent: row.user_intent as string | null,
+      intentPromptedAt: row.intent_prompted_at as string | null,
+      dueDate: (row.due_date as string | null) ?? null,
+      dueDateEditedByUser: (row.due_date_edited_by_user as number) === 1,
+      deletedAt: (row.deleted_at as string | null) ?? null,
+      lastRecalledAt: (row.last_recalled_at as string | null) ?? null,
+      recallDismissedAt: (row.recall_dismissed_at as string | null) ?? null,
+      createdAt: row.created_at as string,
+      updatedAt: row.updated_at as string,
       tags: tags as NoteTag[],
       media
     };
