@hienlh/ppm 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/web/assets/api-client-BHpHp5Lz.js +1 -0
  3. package/dist/web/assets/{chat-tab-DjE_8Csw.js → chat-tab-CDVCDw_H.js} +3 -3
  4. package/dist/web/assets/{code-editor-witrClmz.js → code-editor-wmS73ejX.js} +1 -1
  5. package/dist/web/assets/{diff-viewer-DSU--yFW.js → diff-viewer-BsYccTx1.js} +1 -1
  6. package/dist/web/assets/{git-graph-HpcOYt3G.js → git-graph-BbWb6_Jq.js} +1 -1
  7. package/dist/web/assets/{index-CcXQ5iQw.js → index-DhuAmTQ1.js} +6 -6
  8. package/dist/web/assets/index-aIGuIMQ8.css +2 -0
  9. package/dist/web/assets/keybindings-store-BqgrTQAC.js +1 -0
  10. package/dist/web/assets/{markdown-renderer-DSw-4oxk.js → markdown-renderer-aPdw9BhU.js} +1 -1
  11. package/dist/web/assets/postgres-viewer-V4hKmmzV.js +1 -0
  12. package/dist/web/assets/settings-store-DgOSmeGL.js +1 -0
  13. package/dist/web/assets/settings-tab-DwsKpk9T.js +1 -0
  14. package/dist/web/assets/sqlite-viewer-BRsj8GXc.js +1 -0
  15. package/dist/web/assets/{terminal-tab-CAQvs2wj.js → terminal-tab-3tDV4RCn.js} +1 -1
  16. package/dist/web/assets/{use-monaco-theme-GX0lrqac.js → use-monaco-theme-Ccqh1RD4.js} +1 -1
  17. package/dist/web/index.html +4 -4
  18. package/dist/web/sw.js +1 -1
  19. package/docs/codebase-summary.md +41 -14
  20. package/docs/project-roadmap.md +31 -6
  21. package/docs/system-architecture.md +222 -7
  22. package/package.json +1 -1
  23. package/src/cli/commands/db-cmd.ts +21 -4
  24. package/src/server/index.ts +6 -0
  25. package/src/server/routes/database.ts +259 -0
  26. package/src/services/database/adapter-registry.ts +13 -0
  27. package/src/services/database/init-adapters.ts +9 -0
  28. package/src/services/database/postgres-adapter.ts +42 -0
  29. package/src/services/database/readonly-check.ts +17 -0
  30. package/src/services/database/sqlite-adapter.ts +55 -0
  31. package/src/services/db.service.ts +77 -4
  32. package/src/services/table-cache.service.ts +75 -0
  33. package/src/types/database.ts +50 -0
  34. package/src/web/app.tsx +9 -4
  35. package/src/web/components/database/connection-color-picker.tsx +67 -0
  36. package/src/web/components/database/connection-form-dialog.tsx +234 -0
  37. package/src/web/components/database/connection-list.tsx +208 -0
  38. package/src/web/components/database/database-sidebar.tsx +100 -0
  39. package/src/web/components/database/use-connections.ts +99 -0
  40. package/src/web/components/layout/command-palette.tsx +57 -6
  41. package/src/web/components/layout/draggable-tab.tsx +13 -2
  42. package/src/web/components/layout/mobile-drawer.tsx +7 -2
  43. package/src/web/components/layout/sidebar.tsx +6 -1
  44. package/src/web/components/postgres/postgres-viewer.tsx +12 -3
  45. package/src/web/components/postgres/use-postgres.ts +57 -21
  46. package/src/web/components/sqlite/sqlite-viewer.tsx +27 -3
  47. package/src/web/components/sqlite/use-sqlite.ts +21 -12
  48. package/src/web/lib/api-client.ts +7 -1
  49. package/src/web/lib/color-utils.ts +23 -0
  50. package/src/web/stores/settings-store.ts +2 -2
  51. package/dist/web/assets/api-client-D0pZeYY8.js +0 -1
  52. package/dist/web/assets/index-DyEgsogR.css +0 -2
  53. package/dist/web/assets/keybindings-store-C_KQKrsc.js +0 -1
  54. package/dist/web/assets/postgres-viewer-BnkGPi0L.js +0 -1
  55. package/dist/web/assets/settings-store-B5g1Gis-.js +0 -1
  56. package/dist/web/assets/settings-tab-DpQdg9OW.js +0 -1
  57. package/dist/web/assets/sqlite-viewer-JZvegGV-.js +0 -1
@@ -9,7 +9,7 @@ ppm/
9
9
  ├── src/
10
10
  │ ├── index.ts # CLI entry point (Commander.js program)
11
11
  │ ├── cli/
12
- │ │ ├── commands/ # CLI command implementations (13 files, 1541 LOC)
12
+ │ │ ├── commands/ # CLI command implementations (14 files, 1700 LOC)
13
13
  │ │ │ ├── start.ts # Start server (background by default, --foreground/-f, --share/-s for tunnel)
14
14
  │ │ │ ├── stop.ts # Stop daemon (reads status.json or ppm.pid, graceful shutdown)
15
15
  │ │ │ ├── restart.ts # Restart daemon (keeps tunnel alive)
@@ -21,7 +21,8 @@ ppm/
21
21
  │ │ │ ├── projects.ts # Add/remove/list/scan projects
22
22
  │ │ │ ├── config-cmd.ts # View/set config values
23
23
  │ │ │ ├── git-cmd.ts # Git operations (status, diff, log, commit)
24
- │ │ │ └── chat-cmd.ts # Chat CLI (send messages, manage sessions)
24
+ │ │ │ ├── chat-cmd.ts # Chat CLI (send messages, manage sessions)
25
+ │ │ │ └── db-cmd.ts # Database CLI (list, query, manage connections)
25
26
  │ │ └── utils/
26
27
  │ │ └── project-resolver.ts # Resolve project name -> path
27
28
  │ ├── server/
@@ -35,6 +36,7 @@ ppm/
35
36
  │ │ │ ├── chat.ts # GET/POST/DELETE sessions, GET messages, usage, slash-items
36
37
  │ │ │ ├── git.ts # GET status, diff, log, graph; POST commit, stage, discard
37
38
  │ │ │ ├── files.ts # GET tree, read, diff; PUT write; POST mkdir, delete
39
+ │ │ │ ├── database.ts # GET/POST/PUT/DELETE /api/db/connections (CRUD), query execution
38
40
  │ │ │ └── static.ts # Serve dist/web/index.html (frontend)
39
41
  │ │ ├── helpers/
40
42
  │ │ │ └── resolve-project.ts # Helper to resolve project from request params
@@ -46,10 +48,10 @@ ppm/
46
48
  │ │ ├── claude-agent-sdk.ts # Primary: SDK integration, tool approval, Windows CLI fallback, .env poisoning mitigation
47
49
  │ │ ├── mock-provider.ts # Test provider (ignores config)
48
50
  │ │ └── registry.ts # ProviderRegistry (singleton, router to active provider)
49
- │ ├── services/ # Business logic (14 files, 2502 LOC)
51
+ │ ├── services/ # Business logic (18 files, 3100 LOC)
50
52
  │ │ ├── chat.service.ts # Session lifecycle, message streaming
51
53
  │ │ ├── config.service.ts # Config loading (YAML→SQLite migration)
52
- │ │ ├── db.service.ts # SQLite persistence (schema v1, WAL mode, 6 tables)
54
+ │ │ ├── db.service.ts # SQLite persistence (schema v3, WAL mode, 8 tables, connection CRUD)
53
55
  │ │ ├── project.service.ts # Project CRUD, scanning, resolution
54
56
  │ │ ├── file.service.ts # File ops with path validation
55
57
  │ │ ├── git.service.ts # Git operations (status, diff, log, graph)
@@ -60,11 +62,19 @@ ppm/
60
62
  │ │ ├── slash-items.service.ts # /slash command detection & completion
61
63
  │ │ ├── git-dirs.service.ts # Cached git directory discovery
62
64
  │ │ ├── cloudflared.service.ts # Download cloudflared binary (platform-specific)
63
- │ │ └── tunnel.service.ts # Cloudflare Quick Tunnel lifecycle
64
- │ ├── types/ # TypeScript interfaces (6 files, 357 LOC)
65
+ │ │ ├── tunnel.service.ts # Cloudflare Quick Tunnel lifecycle
66
+ ├── table-cache.service.ts # Table metadata cache & search for DB connections
67
+ │ │ └── database/ # Database adapters & registry
68
+ │ │ ├── adapter-registry.ts # DatabaseAdapter registry (extensible)
69
+ │ │ ├── sqlite-adapter.ts # SQLite connection, query execution
70
+ │ │ ├── postgres-adapter.ts # PostgreSQL connection, query execution
71
+ │ │ ├── init-adapters.ts # Initialize adapters at server start
72
+ │ │ └── readonly-check.ts # isReadOnlyQuery() safety regex (CTE-safe)
73
+ │ ├── types/ # TypeScript interfaces (7 files, 450 LOC)
65
74
  │ │ ├── api.ts # ApiResponse envelope, WebSocket message types
66
75
  │ │ ├── chat.ts # Session, Message, ChatEvent types
67
76
  │ │ ├── config.ts # Config schema
77
+ │ │ ├── database.ts # DatabaseAdapter, DbConnectionConfig, DbTableInfo, etc.
68
78
  │ │ ├── git.ts # GitStatus, GitDiff, GitCommit types
69
79
  │ │ ├── project.ts # Project interface
70
80
  │ │ └── terminal.ts # Terminal types
@@ -88,7 +98,7 @@ ppm/
88
98
  │ │ ├── use-health-check.ts # Detect server crashes/restarts via health endpoint
89
99
  │ │ ├── use-usage.ts # Fetch token usage from backend
90
100
  │ │ └── use-push-notification.ts # Web push notifications via Service Worker
91
- │ ├── lib/ # Utilities (10 files)
101
+ │ ├── lib/ # Utilities (11 files)
92
102
  │ │ ├── api-client.ts # Fetch wrapper with auth token, envelope unwrapping
93
103
  │ │ ├── api-settings.ts # AI settings API client (GET/PUT /api/settings/ai)
94
104
  │ │ ├── ws-client.ts # WebSocket with exponential backoff + Cloudflare handshake
@@ -96,6 +106,7 @@ ppm/
96
106
  │ │ ├── project-avatar.ts # Smart project initials (collision resolution)
97
107
  │ │ ├── project-palette.ts # 12-color palette for project avatars
98
108
  │ │ ├── use-monaco-theme.ts # Sync Monaco Editor theme with app theme
109
+ │ │ ├── color-utils.ts # WCAG color contrast helper
99
110
  │ │ └── utils.ts # Helpers (cn, randomId, basename, etc.)
100
111
  │ ├── styles/
101
112
  │ │ └── globals.css # Tailwind directives, custom CSS
@@ -130,21 +141,33 @@ ppm/
130
141
  │ │ ├── editor-panel.tsx # Wrapper for tab content within a panel
131
142
  │ │ ├── project-bar.tsx # 52px sidebar with project avatars, share popover
132
143
  │ │ ├── project-bottom-sheet.tsx # Mobile project switcher
133
- │ │ ├── sidebar.tsx # Left sidebar (Explorer/Git/Settings tabs)
134
- │ │ ├── tab-bar.tsx # Tab bar with icons
135
- │ │ ├── draggable-tab.tsx # Draggable tab with context menu, rename
144
+ │ │ ├── sidebar.tsx # Left sidebar (Explorer/Git/Database/Settings tabs)
145
+ │ │ ├── tab-bar.tsx # Tab bar with icons, connection color display
146
+ │ │ ├── draggable-tab.tsx # Draggable tab with context menu, rename, connection color
136
147
  │ │ ├── tab-content.tsx # Router for tab content
137
148
  │ │ ├── split-drop-overlay.tsx # Drop zone for tab splitting
138
- │ │ ├── command-palette.tsx # Global command palette (Shift+Shift)
149
+ │ │ ├── command-palette.tsx # Global command palette (Shift+Shift, DB table search)
139
150
  │ │ ├── add-project-form.tsx # Modal form to add projects
140
151
  │ │ ├── mobile-nav.tsx # Bottom navigation for mobile
141
152
  │ │ └── mobile-drawer.tsx # Mobile overlay drawer
153
+ │ ├── database/ # Database management (5 files, 300+ LOC)
154
+ │ │ ├── database-sidebar.tsx # Sidebar tab container (connection list, form)
155
+ │ │ ├── connection-list.tsx # Connections list with actions, color badges
156
+ │ │ ├── connection-form-dialog.tsx # Create/edit connection form (SQLite/Postgres)
157
+ │ │ ├── connection-color-picker.tsx # WCAG contrast-aware color picker
158
+ │ │ └── use-connections.ts # Hook for connection CRUD operations
142
159
  │ ├── projects/ # Project management (339 LOC, 2 files)
143
160
  │ ├── settings/ # Settings panel (theme + AI provider config UI)
144
161
  │ ├── terminal/ # xterm.js wrapper (143 LOC, 2 files)
145
162
  │ ├── shared/ # Shared components (2 files)
146
163
  │ │ ├── markdown-renderer.tsx # Render Markdown with syntax highlighting
147
164
  │ │ └── bug-report-popup.tsx # Global bug report popup
165
+ │ ├── sqlite/ # SQLite viewer (unified connectionId API mode)
166
+ │ │ ├── sqlite-viewer.tsx # Display table data, execute queries
167
+ │ │ └── use-sqlite.ts # Hook for SQLite operations via /api/db routes
168
+ │ ├── postgres/ # PostgreSQL viewer (unified connectionId API mode)
169
+ │ │ ├── postgres-viewer.tsx # Display table data, execute queries
170
+ │ │ └── use-postgres.ts # Hook for Postgres operations via /api/db routes
148
171
  │ └── ui/ # Radix + shadcn primitives (14 files)
149
172
  │ └── button, input, label, dialog, dropdown-menu, select, tabs, tooltip, etc.
150
173
  ├── tests/
@@ -201,11 +224,11 @@ ppm/
201
224
  - **Pattern:** Project-scoped routing via ProviderRegistry
202
225
 
203
226
  ### Service Layer (src/services/)
204
- - **Responsibility:** Business logic, data operations, infrastructure (tunneling)
227
+ - **Responsibility:** Business logic, data operations, infrastructure (tunneling, database connections)
205
228
  - **Services:**
206
229
  - **ChatService** — Session lifecycle, message queueing, streaming
207
230
  - **ConfigService** — Config loading (YAML→SQLite migration)
208
- - **DbService** — SQLite persistence (6 tables, WAL mode, schema migrations)
231
+ - **DbService** — SQLite persistence (8 tables, WAL mode, schema v3, connection CRUD, table cache)
209
232
  - **GitService** — Git commands via simple-git
210
233
  - **FileService** — File ops with path validation
211
234
  - **ProjectService** — Project CRUD, scanning, resolution
@@ -215,7 +238,11 @@ ppm/
215
238
  - **SessionLogService** — Audit logs with sensitive data redaction
216
239
  - **CloudflaredService** — Download/cache cloudflared binary (platform-aware)
217
240
  - **TunnelService** — Spawn tunnel, extract URL, cleanup on exit
218
- - **Pattern:** Singleton services, dependency injection via imports
241
+ - **TableCacheService** Cache table metadata across connections, search tables by name
242
+ - **DatabaseAdapterRegistry** — Register/retrieve DatabaseAdapter implementations (extensible pattern)
243
+ - **SQLiteAdapter** — SQLite connection/query execution with readonly checks
244
+ - **PostgresAdapter** — PostgreSQL connection/query execution with readonly checks
245
+ - **Pattern:** Singleton services, dependency injection via imports, adapter registry for extensibility
219
246
 
220
247
  ### Provider Layer (src/providers/)
221
248
  - **Responsibility:** AI model abstraction, config-driven initialization
@@ -142,7 +142,31 @@ Multi-project, project-scoped API refactor with improved UX, Monaco Editor, auto
142
142
 
143
143
  ---
144
144
 
145
- ### Phase 9: PWA & Build ✅ Complete
145
+ ### Phase 9: Database Management ✅ Complete (260319)
146
+
147
+ **Features:**
148
+ - Unified database viewer for SQLite & PostgreSQL
149
+ - DatabaseAdapter extensible pattern
150
+ - Connection CRUD (create, edit, delete, color-code)
151
+ - Query execution with readonly safety
152
+ - Table browser with pagination & schema inspection
153
+ - CLI support (ppm db commands)
154
+ - Credentials stored securely in SQLite (never exposed in API)
155
+ - readonly=true by default (safe-by-default)
156
+
157
+ **Latest Work (260319):**
158
+ - SQLiteAdapter & PostgresAdapter implementations
159
+ - /api/db routes with connection sanitization
160
+ - Database sidebar UI with connection form, color picker
161
+ - TableCacheService for metadata caching & search
162
+ - isReadOnlyQuery() CTE-safe safety checks
163
+ - CLI db-cmd for database management
164
+
165
+ **Status:** Complete, fully integrated with v0.6.3
166
+
167
+ ---
168
+
169
+ ### Phase 10: PWA & Build ✅ Complete
146
170
  - Vite build configuration
147
171
  - Service worker (vite-plugin-pwa)
148
172
  - Offline support (cached assets)
@@ -153,7 +177,7 @@ Multi-project, project-scoped API refactor with improved UX, Monaco Editor, auto
153
177
 
154
178
  ---
155
179
 
156
- ### Phase 10: Testing ✅ In Progress (65%)
180
+ ### Phase 11: Testing ✅ In Progress (65%)
157
181
 
158
182
  #### Unit Tests (50% complete)
159
183
  - [x] Mock provider tests
@@ -229,17 +253,18 @@ Multi-project, project-scoped API refactor with improved UX, Monaco Editor, auto
229
253
  - [x] URL sync for bookmarking/sharing
230
254
  - [x] Project Switcher Bar (52px sidebar, avatars, colors, reordering) (260317)
231
255
  - [x] Keep-alive workspace switching (preserve xterm DOM) (260317)
232
- - [x] Sidebar tab system (Explorer/Git/History) (260317)
256
+ - [x] Sidebar tab system (Explorer/Git/History/Database tabs) (260317, 260319)
233
257
  - [x] Monaco Editor migration (CodeMirror → Monaco, fully removed) (260317-260319)
234
258
  - [x] Project color customization (12-color palette + custom hex) (260317)
235
259
  - [x] Auto-generate chat session titles from SDK summary (260319)
236
260
  - [x] Inline session rename UI (260319)
237
- - [x] SQLite migration (db.service.ts, backward YAML compat) (in progress)
261
+ - [x] Database Management (SQLite/PostgreSQL, adapters, UI, CLI) (260319)
262
+ - [x] SQLite migration (db.service.ts, backward YAML compat, connection tables v3) (260319)
238
263
  - [ ] Complete test coverage (65% complete)
239
264
  - [x] Documentation updates (260319)
240
265
  - [ ] Security audit (planned)
241
266
 
242
- **Release Status:** v0.5.21 released, v2.0 essentially complete
267
+ **Release Status:** v0.6.3 released, v2.0 + database management complete
243
268
 
244
269
  ---
245
270
 
@@ -366,7 +391,7 @@ Multi-project, project-scoped API refactor with improved UX, Monaco Editor, auto
366
391
  | Version | Status | Features | Target Date |
367
392
  |---------|--------|----------|-------------|
368
393
  | **v1.0** | Released | Single project, basic chat, terminal | Feb 28, 2025 |
369
- | **v2.0** | Complete (v0.5.21) | Multi-project, project-scoped API, improved UX, Monaco Editor, auto-title | Mar 19, 2026 |
394
+ | **v2.0** | Complete (v0.6.3) | Multi-project, project-scoped API, improved UX, Monaco Editor, auto-title, database management | Mar 19, 2026 |
370
395
  | **v2.1** | Planned | Complete test coverage, SQLite finalization, bug fixes | Apr 15, 2026 |
371
396
  | **v3.0** | Planned | Collaborative editing, custom tools, plugins | Jun 30, 2026 |
372
397
  | **v4.0** | Planned | Cloud sync, advanced git, profiling UI | Sep 30, 2026 |
@@ -18,16 +18,18 @@
18
18
  │ │ Hono HTTP Framework (Port 8080) │ │
19
19
  │ ├────────────────────────────────────────────────────────────────┤ │
20
20
  │ │ Routes (src/server/routes/) │ │
21
- │ │ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────┐ │ │
22
- │ │ │ /api/projects │ │ /api/project/:n/ │ │ /api/health │ │ │
23
- │ │ │ (CRUD projects) │ │ (scoped routes) │ │ (status) │ │ │
24
- │ │ └──────────────────┘ └──────────────────┘ └─────────────┘ │ │
21
+ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │ │
22
+ │ │ │ /api/projects │ │ /api/project/:n/ │ │ /api/db/* │ │ │
23
+ │ │ │ (CRUD projects) │ │ (scoped routes) │ │ (connections)│ │ │
24
+ │ │ └──────────────────┘ └──────────────────┘ └──────────────┘ │ │
25
25
  │ ├────────────────────────────────────────────────────────────────┤ │
26
26
  │ │ Services (src/services/) │ │
27
27
  │ │ ┌───────────────────────────────────────────────────────────┐│ │
28
28
  │ │ │ ChatService │ GitService │ FileService │ TerminalService ││ │
29
29
  │ │ │ (streaming │ (simple- │ (read/write │ (PTY/shell) ││ │
30
30
  │ │ │ messages) │ git) │ files) │ (Bun.spawn) ││ │
31
+ │ │ │ TableCache │ DbService │ DatabaseAdapterRegistry ││ │
32
+ │ │ │ (metadata) │ (SQLite) │ (SQLite, PostgreSQL adapters) ││ │
31
33
  │ │ └───────────────────────────────────────────────────────────┘│ │
32
34
  │ ├────────────────────────────────────────────────────────────────┤ │
33
35
  │ │ Providers (src/providers/) │ │
@@ -45,8 +47,10 @@
45
47
  │ │ SQLite DB │ │ Git Repos │ │ Session Storage │ │
46
48
  │ │ (config, projs) │ │ (local disk) │ │ (SQLite + SDK) │ │
47
49
  │ │ (session map) │ │ │ │ (session_map, │ │
48
- │ │ (push subs, │ │ │ │ session_logs, │ │
49
- │ │ usage, logs) │ │ │ │ usage_history) │ │
50
+ │ │ (push subs, │ │ Connections: │ │ session_logs, │ │
51
+ │ │ usage, logs) │ │ • SQLite files │ │ usage_history) │ │
52
+ │ │ (connections) │ │ • PostgreSQL svr │ │ (connections) │ │
53
+ │ │ (table metadata) │ │ via connStr │ │ │ │
50
54
  │ └──────────────────┘ └──────────────────┘ └─────────────────┘ │
51
55
  └──────────────────────────────────────────────────────────────────────┘
52
56
  ↓↑
@@ -119,6 +123,15 @@ POST /api/project/:name/git/commit → Commit
119
123
  GET /api/project/:name/files/tree → Directory tree
120
124
  GET /api/project/:name/files/raw → File content
121
125
  PUT /api/project/:name/files/write → Write file
126
+ GET /api/db/connections → List all connections
127
+ POST /api/db/connections → Create connection (SQLite/PostgreSQL)
128
+ GET /api/db/connections/:id → Get connection (sanitized)
129
+ PUT /api/db/connections/:id → Update connection (toggle readonly, UI-only)
130
+ DELETE /api/db/connections/:id → Delete connection
131
+ GET /api/db/connections/:id/tables → List tables (with sync)
132
+ GET /api/db/connections/:id/tables/:table → Get table schema + data
133
+ POST /api/db/connections/:id/query → Execute query (readonly checked)
134
+ PATCH /api/db/connections/:id/cell → Update cell value (single)
122
135
  WS /ws/project/:name/chat/:sessionId → Chat streaming
123
136
  WS /ws/project/:name/terminal/:id → Terminal I/O
124
137
  ```
@@ -140,7 +153,8 @@ WS /ws/project/:name/terminal/:id → Terminal I/O
140
153
  |---------|---------|-------------|
141
154
  | **ChatService** | Session management, message streaming | createSession, streamMessage, getHistory |
142
155
  | **ConfigService** | Config loading (YAML→SQLite migration) | load, save, getToken |
143
- | **DbService** | SQLite persistence (6 tables, WAL) | getDb, openTestDb, schema migrations |
156
+ | **DbService** | SQLite persistence (8 tables, WAL, connections CRUD) | getDb, openTestDb, getConnections, insertConnection, updateConnection, deleteConnection, getTableCache |
157
+ | **TableCacheService** | Cache table metadata, search tables | syncTables, searchTables, invalidateCache |
144
158
  | **GitService** | Git command execution | status, diff, commit, stage, branch |
145
159
  | **FileService** | File operations with validation | read, write, tree, delete, mkdir |
146
160
  | **TerminalService** | PTY lifecycle, shell spawning | spawn, write, kill |
@@ -151,6 +165,9 @@ WS /ws/project/:name/terminal/:id → Terminal I/O
151
165
  | **ProviderRegistry** | AI provider routing | getDefault, send (delegates) |
152
166
  | **CloudflaredService** | Download cloudflared binary | ensureCloudflared, getCloudflaredPath |
153
167
  | **TunnelService** | Cloudflare Quick Tunnel lifecycle | startTunnel, stopTunnel, getTunnelUrl |
168
+ | **DatabaseAdapterRegistry** | Register/retrieve DB adapters (extensible) | registerAdapter, getAdapter |
169
+ | **SQLiteAdapter** | SQLite connection, query execution, readonly checks | testConnection, getTables, getTableSchema, getTableData, executeQuery, updateCell |
170
+ | **PostgresAdapter** | PostgreSQL connection, query execution, readonly checks | testConnection, getTables, getTableSchema, getTableData, executeQuery, updateCell |
154
171
 
155
172
  **Key Files:** `src/services/*.service.ts`
156
173
 
@@ -544,6 +561,204 @@ UI updates: "src/index.ts" moves from "Unstaged" to "Staged"
544
561
 
545
562
  ---
546
563
 
564
+ ## Database Management (v2.0+)
565
+
566
+ ### Architecture Overview
567
+
568
+ PPM now supports managing external databases (SQLite & PostgreSQL) through a unified adapter pattern:
569
+
570
+ ```
571
+ ┌─────────────────────────────────────────────────────────────────┐
572
+ │ Web UI (React) │
573
+ │ ┌──────────────────────────────────────────────────────────┐ │
574
+ │ │ Database Sidebar │ │
575
+ │ │ • Connection List (with color badges) │ │
576
+ │ │ • Create/Edit Connection Form │ │
577
+ │ │ • Color Picker (WCAG contrast-aware) │ │
578
+ │ │ • Query Execution UI │ │
579
+ │ └──────────────────────────────────────────────────────────┘ │
580
+ └─────────────────┬───────────────────────────────────────────────┘
581
+ │ HTTP REST / WebSocket
582
+ ┌─────────────────┴───────────────────────────────────────────────┐
583
+ │ PPM Server (Hono) │
584
+ │ ┌──────────────────────────────────────────────────────────┐ │
585
+ │ │ /api/db Routes │ │
586
+ │ │ • GET /connections → List all connections │ │
587
+ │ │ • POST /connections → Create connection │ │
588
+ │ │ • GET /connections/:id → Get connection (sanitized) │ │
589
+ │ │ • PUT /connections/:id → Update (readonly toggle) │ │
590
+ │ │ • DELETE /connections/:id → Remove connection │ │
591
+ │ │ • GET /connections/:id/tables → List + sync tables │ │
592
+ │ │ • GET /connections/:id/tables/:tbl → Schema + data │ │
593
+ │ │ • POST /connections/:id/query → Execute query │ │
594
+ │ │ • PATCH /connections/:id/cell → Update cell │ │
595
+ │ └──────────────────────────────────────────────────────────┘ │
596
+ │ ┌──────────────────────────────────────────────────────────┐ │
597
+ │ │ Service Layer │ │
598
+ │ │ • DbService (connection CRUD, caching) │ │
599
+ │ │ • TableCacheService (metadata cache, search) │ │
600
+ │ │ • DatabaseAdapterRegistry (extensible) │ │
601
+ │ └──────────────────────────────────────────────────────────┘ │
602
+ │ ┌──────────────────────────────────────────────────────────┐ │
603
+ │ │ Adapters (Pluggable Pattern) │ │
604
+ │ │ • SQLiteAdapter → Uses `bun:sqlite` for local files │ │
605
+ │ │ • PostgresAdapter → Uses postgres driver for servers │ │
606
+ │ │ • isReadOnlyQuery() → Safety check (CTE-safe regex) │ │
607
+ │ │ • readonly=1 by default (safe-by-default) │ │
608
+ │ └──────────────────────────────────────────────────────────┘ │
609
+ └──────────────────────────────────────────────────────────────────┘
610
+ ↓↑
611
+ ┌────────────────────────────────────────────┐
612
+ │ External Databases │
613
+ │ • SQLite files (path: /path/to/db.db) │
614
+ │ • PostgreSQL servers (connStr: postgres://)│
615
+ └────────────────────────────────────────────┘
616
+ ```
617
+
618
+ ### DatabaseAdapter Pattern (Extensible)
619
+
620
+ **Interface** (`src/types/database.ts`):
621
+ ```typescript
622
+ interface DatabaseAdapter {
623
+ testConnection(config: DbConnectionConfig): Promise<{ ok: boolean; error?: string }>;
624
+ getTables(config: DbConnectionConfig): Promise<DbTableInfo[]>;
625
+ getTableSchema(config: DbConnectionConfig, table: string, schema?: string): Promise<DbColumnInfo[]>;
626
+ getTableData(config: DbConnectionConfig, table: string, opts: {...}): Promise<DbPagedData>;
627
+ executeQuery(config: DbConnectionConfig, sql: string): Promise<DbQueryResult>;
628
+ updateCell(config: DbConnectionConfig, table: string, opts: {...}): Promise<void>;
629
+ }
630
+ ```
631
+
632
+ **Implementations:**
633
+ 1. **SQLiteAdapter** — Local file-based SQLite via `bun:sqlite`
634
+ - testConnection: Opens file, runs pragma check
635
+ - Supports: SELECT, INSERT, UPDATE, DELETE (if writable), CREATE TABLE
636
+
637
+ 2. **PostgresAdapter** — Remote PostgreSQL servers via postgres driver
638
+ - testConnection: Attempts connection with credentials
639
+ - Supports: Full SQL except DDL on readonly connections
640
+
641
+ **Registry Pattern** (`src/services/database/adapter-registry.ts`):
642
+ ```typescript
643
+ registerAdapter("sqlite", new SQLiteAdapter());
644
+ registerAdapter("postgres", new PostgresAdapter());
645
+ // Can be extended: registerAdapter("mysql", new MysqlAdapter());
646
+ ```
647
+
648
+ ### Security Design
649
+
650
+ **Readonly by Default:**
651
+ - All connections created with `readonly = true` in database
652
+ - Default: read-only query execution (safe-by-default)
653
+ - Web UI toggle: Switch to writable (admin decision only)
654
+ - CLI: Cannot disable readonly via command-line (browser only)
655
+
656
+ **Readonly Query Detection:**
657
+ ```typescript
658
+ // isReadOnlyQuery() in src/services/database/readonly-check.ts
659
+ // Checks for: SELECT, PRAGMA, EXPLAIN, WITH (CTE)
660
+ // Rejects: INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, etc.
661
+ // CTE-safe: Handles "WITH AS SELECT" (wraps CTE result check)
662
+ ```
663
+
664
+ **Credential Handling:**
665
+ - Connection credentials stored in SQLite `connections` table as `connection_config` JSON
666
+ - **NEVER** returned in API responses (stripped by `sanitizeConn()` in routes)
667
+ - Only used internally by adapters when executing queries
668
+ - Frontend never sees passwords/connection strings
669
+
670
+ **API Security:**
671
+ - All `/api/db` requests require valid auth token (middleware checked)
672
+ - Connection IDs are numeric (no enumeration risk)
673
+ - Connection color is user-specific (cosmetic only, not sensitive)
674
+
675
+ ### Data Flow: Query Execution
676
+
677
+ ```
678
+ User opens Database tab
679
+
680
+ DatabaseSidebar fetches: GET /api/db/connections
681
+
682
+ ConnectionList displays (sanitized, no credentials)
683
+
684
+ User clicks connection → GET /api/db/connections/:id/tables
685
+
686
+ DbService.getConnections() reads from SQLite
687
+
688
+ TableCacheService.syncTables() calls adapter.getTables()
689
+
690
+ SQLiteAdapter/PostgresAdapter queries database
691
+
692
+ Results cached in table_metadata table
693
+
694
+ UI displays table list + schema
695
+
696
+ User selects table → GET /api/db/connections/:id/tables/:table
697
+
698
+ Adapter.getTableData() executes paginated query
699
+
700
+ Results returned: { columns, rows, total, page, limit }
701
+
702
+ UI renders table grid with pagination
703
+
704
+ User executes custom query → POST /api/db/connections/:id/query
705
+
706
+ isReadOnlyQuery() checks SQL (rejects writes if readonly=true)
707
+
708
+ Adapter.executeQuery() runs SQL
709
+
710
+ Results returned: { columns, rows, rowsAffected, changeType }
711
+
712
+ UI displays results (read-only highlight if mutation was blocked)
713
+ ```
714
+
715
+ ### Connection Storage
716
+
717
+ **SQLite Schema** (in `~/.ppm/ppm.db`):
718
+ ```sql
719
+ CREATE TABLE connections (
720
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
721
+ type TEXT NOT NULL, -- 'sqlite' | 'postgres'
722
+ name TEXT NOT NULL,
723
+ connection_config TEXT NOT NULL, -- JSON: { path, connectionString, ... }
724
+ readonly INTEGER DEFAULT 1, -- 1 = readonly, 0 = writable (UI-only toggle)
725
+ group_name TEXT,
726
+ color TEXT, -- Optional hex color (#3b82f6)
727
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
728
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
729
+ );
730
+
731
+ CREATE TABLE table_metadata (
732
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
733
+ connection_id INTEGER NOT NULL REFERENCES connections(id) ON DELETE CASCADE,
734
+ table_name TEXT NOT NULL,
735
+ schema_name TEXT DEFAULT 'public',
736
+ row_count INTEGER,
737
+ last_synced TEXT,
738
+ UNIQUE(connection_id, table_name, schema_name)
739
+ );
740
+ ```
741
+
742
+ ### CLI Support (ppm db)
743
+
744
+ **Commands** (`src/cli/commands/db-cmd.ts`):
745
+ ```bash
746
+ ppm db connections # List all connections
747
+ ppm db connect # Add new connection (interactive)
748
+ ppm db remove <name> # Delete connection
749
+ ppm db query <name> <sql> # Execute query (respects readonly)
750
+ ppm db tables <name> # List tables
751
+ ppm db schema <name> <table> # Show table schema
752
+ ppm db data <name> <table> # Show table data (paginated)
753
+ ```
754
+
755
+ **CLI Safety:**
756
+ - Always respects readonly flag (cannot override via CLI)
757
+ - Uses same adapter/validation as web UI
758
+ - Table formatting for terminal output
759
+
760
+ ---
761
+
547
762
  ## Deployment Architecture
548
763
 
549
764
  ### Single-Machine Deployment (Current)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -1,4 +1,5 @@
1
1
  import { Command } from "commander";
2
+ import { isReadOnlyQuery } from "../../services/database/readonly-check.ts";
2
3
 
3
4
  const C = {
4
5
  reset: "\x1b[0m",
@@ -53,6 +54,12 @@ function parseConfig(row: { type: string; connection_config: string }): { type:
53
54
  return { type: row.type, ...cfg };
54
55
  }
55
56
 
57
+ /** Mask password in postgres connection string: postgresql://user:pass@host → postgresql://user:***@host */
58
+ function maskPassword(connectionString: string): string {
59
+ return connectionString.replace(/(:\/\/[^:]+:)[^@]+(@)/, "$1***$2");
60
+ }
61
+
62
+
56
63
  export function registerDbCommands(program: Command): void {
57
64
  const db = program.command("db").description("Manage database connections and execute queries");
58
65
 
@@ -69,11 +76,14 @@ export function registerDbCommands(program: Command): void {
69
76
  }
70
77
  const rows = conns.map((c) => {
71
78
  const cfg = parseConfig(c);
72
- const target = cfg.connectionString ?? cfg.path ?? "-";
73
- const display = target.length > 60 ? target.slice(0, 57) + "..." : target;
74
- return [String(c.id), c.name, c.type, c.group_name ?? "-", display];
79
+ let target = cfg.connectionString ?? cfg.path ?? "-";
80
+ // Mask password in postgres connection strings
81
+ if (cfg.connectionString) target = maskPassword(target);
82
+ const display = target.length > 70 ? target.slice(0, 67) + "..." : target;
83
+ const ro = c.readonly ? `${C.yellow}RO${C.reset}` : `${C.green}RW${C.reset}`;
84
+ return [String(c.id), c.name, c.type, c.group_name ?? "-", ro, display];
75
85
  });
76
- printTable(["ID", "Name", "Type", "Group", "Connection"], rows);
86
+ printTable(["ID", "Name", "Type", "Group", "RO", "Connection"], rows);
77
87
  } catch (err) {
78
88
  console.error(`${C.red}Error:${C.reset}`, (err as Error).message);
79
89
  process.exit(1);
@@ -313,6 +323,13 @@ export function registerDbCommands(program: Command): void {
313
323
  }
314
324
  const cfg = parseConfig(conn);
315
325
 
326
+ // Enforce readonly — CLI cannot disable this, only the web UI can toggle it
327
+ if (conn.readonly && !isReadOnlyQuery(sql)) {
328
+ console.error(`${C.red}Error:${C.reset} Connection "${conn.name}" is readonly — only SELECT queries allowed.`);
329
+ console.error(` To allow writes, toggle the readonly switch in the PPM web UI.`);
330
+ process.exit(1);
331
+ }
332
+
316
333
  if (conn.type === "postgres") {
317
334
  const { postgresService } = await import("../../services/postgres.service.ts");
318
335
  const result = await postgresService.executeQuery(cfg.connectionString!, sql);
@@ -10,6 +10,8 @@ import { tunnelRoutes } from "./routes/tunnel.ts";
10
10
  import { staticRoutes } from "./routes/static.ts";
11
11
  import { projectScopedRouter } from "./routes/project-scoped.ts";
12
12
  import { postgresRoutes } from "./routes/postgres.ts";
13
+ import { databaseRoutes } from "./routes/database.ts";
14
+ import { initAdapters } from "../services/database/init-adapters.ts";
13
15
  import { terminalWebSocket } from "./ws/terminal.ts";
14
16
  import { chatWebSocket } from "./ws/chat.ts";
15
17
  import { ok, err } from "../types/api.ts";
@@ -57,6 +59,9 @@ async function setupLogFile() {
57
59
  });
58
60
  }
59
61
 
62
+ // Register database adapters at module load time
63
+ initAdapters();
64
+
60
65
  export const app = new Hono();
61
66
 
62
67
  // CORS for dev
@@ -185,6 +190,7 @@ app.route("/api/push", pushRoutes);
185
190
  app.route("/api/projects", projectRoutes);
186
191
  app.route("/api/project/:projectName", projectScopedRouter);
187
192
  app.route("/api/postgres", postgresRoutes);
193
+ app.route("/api/db", databaseRoutes);
188
194
 
189
195
  // Static files / SPA fallback (non-API routes)
190
196
  app.route("/", staticRoutes);