@hienlh/ppm 0.9.0-beta.8 → 0.9.1
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.
- package/CHANGELOG.md +238 -0
- package/bun.lock +17 -0
- package/dist/web/assets/api-settings-BUvk6Saw.js +1 -0
- package/dist/web/assets/arrow-up-BYhx9ckd.js +1 -0
- package/dist/web/assets/browser-tab-CrkhFCaw.js +1 -0
- package/dist/web/assets/chat-tab-C6jpiwh7.js +8 -0
- package/dist/web/assets/chevron-right-5HgK6l7K.js +1 -0
- package/dist/web/assets/code-editor-CBIPzlP2.js +2 -0
- package/dist/web/assets/columns-2-cEVJHYd7.js +1 -0
- package/dist/web/assets/createLucideIcon-PuMiQgHl.js +1 -0
- package/dist/web/assets/{csv-preview-DLqYtXxt.js → csv-preview-ncSOnJSC.js} +2 -2
- package/dist/web/assets/database-viewer-BqOJR_zi.js +1 -0
- package/dist/web/assets/diff-viewer-CcLyp4eY.js +4 -0
- package/dist/web/assets/{dist-CALwEtco.js → dist-DIV6WgAG.js} +1 -1
- package/dist/web/assets/{dist-DGDPTxs1.js → dist-ovWkrgO-.js} +1 -1
- package/dist/web/assets/extension-webview-NiZ7Ybvv.js +3 -0
- package/dist/web/assets/git-graph-CoTvMrIo.js +1 -0
- package/dist/web/assets/index-C8byznLO.js +37 -0
- package/dist/web/assets/index-KwC2YrG4.css +2 -0
- package/dist/web/assets/jsx-runtime-kMwlnEGE.js +1 -0
- package/dist/web/assets/keybindings-store-DPYzBe_M.js +1 -0
- package/dist/web/assets/{markdown-renderer-DklUd_Gv.js → markdown-renderer-DPLdR9xc.js} +4 -4
- package/dist/web/assets/postgres-viewer-BeiK4lCa.js +1 -0
- package/dist/web/assets/settings-tab-D3AvU4lu.js +1 -0
- package/dist/web/assets/sqlite-viewer-nA2sD4Yv.js +1 -0
- package/dist/web/assets/tab-store-BOgTrqRr.js +1 -0
- package/dist/web/assets/table-DFevCOMd.js +1 -0
- package/dist/web/assets/tag-CXMT0QB6.js +1 -0
- package/dist/web/assets/{terminal-tab-CqRuiIFn.js → terminal-tab-BBi0pEji.js} +2 -2
- package/dist/web/assets/{use-monaco-theme-Dcz3aLAE.js → use-monaco-theme-B5pG2d1w.js} +1 -1
- package/dist/web/index.html +8 -8
- package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
- package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
- package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
- package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
- package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
- package/dist/web/sw.js +1 -1
- package/docs/code-standards.md +128 -1
- package/docs/codebase-summary.md +79 -12
- package/docs/extension-development-guide.md +532 -0
- package/docs/project-changelog.md +51 -1
- package/docs/project-roadmap.md +9 -3
- package/docs/streaming-input-guide.md +267 -0
- package/docs/system-architecture.md +432 -3
- package/package.json +6 -3
- package/packages/ext-database/package.json +41 -0
- package/packages/ext-database/src/connection-tree.ts +142 -0
- package/packages/ext-database/src/extension.ts +346 -0
- package/packages/ext-database/src/query-panel.ts +120 -0
- package/packages/ext-database/src/table-viewer-panel.ts +410 -0
- package/packages/ext-database/tsconfig.json +8 -0
- package/packages/vscode-compat/package.json +16 -0
- package/packages/vscode-compat/src/commands.ts +39 -0
- package/packages/vscode-compat/src/context.ts +65 -0
- package/packages/vscode-compat/src/disposable.ts +21 -0
- package/packages/vscode-compat/src/env.ts +20 -0
- package/packages/vscode-compat/src/event-emitter.ts +28 -0
- package/packages/vscode-compat/src/index.ts +93 -0
- package/packages/vscode-compat/src/not-supported.ts +15 -0
- package/packages/vscode-compat/src/types.ts +167 -0
- package/packages/vscode-compat/src/uri.ts +65 -0
- package/packages/vscode-compat/src/window.ts +229 -0
- package/packages/vscode-compat/src/workspace.ts +76 -0
- package/packages/vscode-compat/tsconfig.json +10 -0
- package/snapshot-state.md +1526 -0
- package/src/cli/commands/autostart.ts +1 -1
- package/src/cli/commands/ext-cmd.ts +121 -0
- package/src/cli/commands/restart.ts +9 -1
- package/src/cli/commands/status.ts +19 -0
- package/src/index.ts +5 -3
- package/src/providers/claude-agent-sdk.ts +221 -17
- package/src/providers/cli-provider-base.ts +6 -0
- package/src/server/index.ts +55 -155
- package/src/server/routes/chat.ts +81 -11
- package/src/server/routes/extensions.ts +81 -0
- package/src/server/routes/project-scoped.ts +2 -0
- package/src/server/routes/settings.ts +27 -0
- package/src/server/routes/workspace.ts +35 -0
- package/src/server/ws/chat.ts +9 -3
- package/src/server/ws/extensions.ts +175 -0
- package/src/services/account-selector.service.ts +14 -5
- package/src/services/account.service.ts +20 -15
- package/src/services/claude-usage.service.ts +29 -24
- package/src/services/cloud-ws.service.ts +228 -0
- package/src/services/cloud.service.ts +11 -6
- package/src/services/contribution-registry.ts +110 -0
- package/src/services/db.service.ts +181 -4
- package/src/services/extension-host-worker.ts +160 -0
- package/src/services/extension-installer.ts +112 -0
- package/src/services/extension-manifest.ts +65 -0
- package/src/services/extension-rpc-handlers.ts +235 -0
- package/src/services/extension-rpc.ts +105 -0
- package/src/services/extension.service.ts +228 -0
- package/src/services/mcp-config.service.ts +15 -6
- package/src/services/supervisor.ts +271 -25
- package/src/types/api.ts +1 -0
- package/src/types/chat.ts +4 -0
- package/src/types/extension-messages.ts +64 -0
- package/src/types/extension.ts +131 -0
- package/src/web/app.tsx +69 -48
- package/src/web/components/chat/account-rotation-settings.tsx +163 -0
- package/src/web/components/chat/chat-history-bar.tsx +106 -10
- package/src/web/components/chat/chat-tab.tsx +15 -10
- package/src/web/components/chat/chat-welcome.tsx +148 -0
- package/src/web/components/chat/message-list.tsx +19 -6
- package/src/web/components/chat/session-picker.tsx +80 -32
- package/src/web/components/chat/usage-badge.tsx +68 -8
- package/src/web/components/editor/editor-breadcrumb.tsx +20 -29
- package/src/web/components/extensions/extension-inputbox.tsx +92 -0
- package/src/web/components/extensions/extension-quickpick.tsx +194 -0
- package/src/web/components/extensions/extension-tree-view.tsx +240 -0
- package/src/web/components/extensions/extension-webview.tsx +83 -0
- package/src/web/components/layout/command-palette.tsx +22 -2
- package/src/web/components/layout/editor-panel.tsx +163 -18
- package/src/web/components/layout/mobile-nav.tsx +2 -1
- package/src/web/components/layout/sidebar.tsx +21 -3
- package/src/web/components/layout/status-bar.tsx +64 -0
- package/src/web/components/layout/tab-bar.tsx +2 -0
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/components/layout/upgrade-banner.tsx +15 -5
- package/src/web/components/settings/change-password-section.tsx +128 -0
- package/src/web/components/settings/extension-manager-section.tsx +214 -0
- package/src/web/components/settings/settings-tab.tsx +9 -2
- package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
- package/src/web/hooks/use-chat.ts +28 -0
- package/src/web/hooks/use-extension-ws.ts +181 -0
- package/src/web/hooks/use-global-keybindings.ts +18 -2
- package/src/web/hooks/use-server-reload.ts +9 -0
- package/src/web/hooks/use-url-sync.ts +173 -21
- package/src/web/stores/connection-store.ts +39 -0
- package/src/web/stores/extension-store.ts +204 -0
- package/src/web/stores/panel-store.ts +63 -9
- package/src/web/stores/panel-utils.ts +145 -3
- package/src/web/stores/settings-store.ts +7 -2
- package/src/web/stores/tab-store.ts +2 -1
- package/test-session-ops.mjs +444 -0
- package/test-tokens.mjs +212 -0
- package/tsconfig.json +3 -1
- package/dist/web/assets/api-settings-D21InCnR.js +0 -1
- package/dist/web/assets/arrow-up--LjUXLEt.js +0 -1
- package/dist/web/assets/browser-tab-BEe89aSD.js +0 -1
- package/dist/web/assets/chat-tab-9lqvWozA.js +0 -7
- package/dist/web/assets/chevron-right-CHnjJt4E.js +0 -1
- package/dist/web/assets/code-editor-COAIZx-B.js +0 -2
- package/dist/web/assets/columns-2-DbesTfa7.js +0 -1
- package/dist/web/assets/database-viewer-aRR9n_Ui.js +0 -1
- package/dist/web/assets/diff-viewer-C4KMvpHr.js +0 -4
- package/dist/web/assets/dist-CVTST7Gc.js +0 -1
- package/dist/web/assets/git-graph-CfJjl4E3.js +0 -1
- package/dist/web/assets/index-Db8uky1a.css +0 -2
- package/dist/web/assets/index-DxZuwBDe.js +0 -37
- package/dist/web/assets/jsx-runtime-BRW_vwa9.js +0 -1
- package/dist/web/assets/keybindings-store-_uWVCZMv.js +0 -1
- package/dist/web/assets/postgres-viewer-DEAvAyaX.js +0 -1
- package/dist/web/assets/settings-tab-BQedc-No.js +0 -1
- package/dist/web/assets/sqlite-viewer-BPA5idzT.js +0 -1
- package/dist/web/assets/tab-store-DhK6EpBT.js +0 -1
- package/dist/web/assets/table-CQVQM2SB.js +0 -1
- package/dist/web/assets/tag-Q2dZiSPX.js +0 -1
|
@@ -140,10 +140,24 @@ POST /api/db/connections/:id/query → Execute query (readonly ch
|
|
|
140
140
|
PATCH /api/db/connections/:id/cell → Update cell value (single)
|
|
141
141
|
GET /api/upgrade/status → Get current + available versions, install method
|
|
142
142
|
POST /api/upgrade/apply → Install new version, trigger supervisor self-replace
|
|
143
|
+
GET /api/project/:name/workspace → Get saved workspace layout + metadata
|
|
144
|
+
PUT /api/project/:name/workspace → Save workspace layout (layout JSON)
|
|
143
145
|
WS /ws/project/:name/chat/:sessionId → Chat streaming
|
|
144
146
|
WS /ws/project/:name/terminal/:id → Terminal I/O
|
|
145
147
|
```
|
|
146
148
|
|
|
149
|
+
**URL Format (Deterministic Tabs, v0.8.77+):**
|
|
150
|
+
```
|
|
151
|
+
/project/{name} → Project root (project switcher)
|
|
152
|
+
/project/{name}/editor/{filePath} → Open editor tab (e.g., src/index.ts)
|
|
153
|
+
/project/{name}/chat/{provider}/{sessionId} → Open chat tab
|
|
154
|
+
/project/{name}/terminal/{index} → Open terminal tab
|
|
155
|
+
/project/{name}/database/{connId}/{table} → Open database browser
|
|
156
|
+
/project/{name}/git-graph → Git history graph (singleton)
|
|
157
|
+
/project/{name}/settings → Settings panel (singleton)
|
|
158
|
+
```
|
|
159
|
+
Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `chat:claude/abc123`). Deep links auto-create missing tabs.
|
|
160
|
+
|
|
147
161
|
---
|
|
148
162
|
|
|
149
163
|
### Service Layer (Business Logic)
|
|
@@ -161,7 +175,7 @@ WS /ws/project/:name/terminal/:id → Terminal I/O
|
|
|
161
175
|
|---------|---------|-------------|
|
|
162
176
|
| **ChatService** | Session management, message streaming | createSession, streamMessage, getHistory |
|
|
163
177
|
| **ConfigService** | Config loading (YAML→SQLite migration) | load, save, getToken |
|
|
164
|
-
| **DbService** | SQLite persistence (
|
|
178
|
+
| **DbService** | SQLite persistence (10 tables, WAL, connections/accounts/workspace CRUD) | getDb, openTestDb, getWorkspace, setWorkspace, getConnections, insertConnection, deleteConnection, getTableCache |
|
|
165
179
|
| **TableCacheService** | Cache table metadata, search tables | syncTables, searchTables, invalidateCache |
|
|
166
180
|
| **GitService** | Git command execution | status, diff, commit, stage, branch |
|
|
167
181
|
| **FileService** | File operations with validation | read, write, tree, delete, mkdir |
|
|
@@ -260,11 +274,11 @@ PPM supports multiple AI providers through a generic `AIProvider` interface and
|
|
|
260
274
|
- Enforce security (no parent directory access)
|
|
261
275
|
|
|
262
276
|
**Key Patterns:**
|
|
263
|
-
- SQLite: WAL mode, foreign keys, lazy init, schema
|
|
277
|
+
- SQLite: WAL mode, foreign keys, lazy init, schema v10 (10 tables: config, connections, accounts, usage_history, session_logs, push_subscriptions, session_map, table_metadata, session_logs, workspace_state)
|
|
264
278
|
- Path validation: `projectPath/relativePath` only, reject `..`
|
|
265
279
|
- Caching: Directory trees cached with TTL
|
|
266
280
|
- Error handling: Descriptive messages (file not found, permission denied)
|
|
267
|
-
- Migration: Automatic YAML→SQLite migration on first run with new db.service
|
|
281
|
+
- Migration: Automatic YAML→SQLite migration on first run with new db.service; schema auto-upgrade on version bump
|
|
268
282
|
|
|
269
283
|
---
|
|
270
284
|
|
|
@@ -284,6 +298,24 @@ PPM supports multiple AI providers through a generic `AIProvider` interface and
|
|
|
284
298
|
const messages = chatStore((s) => s.messages); // Subscribe to messages only
|
|
285
299
|
```
|
|
286
300
|
|
|
301
|
+
#### Workspace Sync (v0.8.77+)
|
|
302
|
+
|
|
303
|
+
**Deterministic Tab IDs & URL Routing:**
|
|
304
|
+
- Tab IDs derived from type + metadata: `deriveTabId(type, metadata) → {type}:{identifier}`
|
|
305
|
+
- Examples: `editor:src/index.ts`, `chat:claude/abc123`, `terminal:1`, `git-graph`
|
|
306
|
+
- URLs rebuilt from active tab: `/project/{name}/{type}/{identifier}`
|
|
307
|
+
- Deep linking: URL → `parseUrlState()` → auto-create tabs if missing
|
|
308
|
+
|
|
309
|
+
**Workspace Persistence:**
|
|
310
|
+
1. **Client**: PanelStore layout (grid, panels, tabs) cached in localStorage per project
|
|
311
|
+
2. **Server**: Workspace JSON persisted in `workspace_state` SQLite table
|
|
312
|
+
3. **Sync Flow:**
|
|
313
|
+
- User loads project → fetch workspace from server (GET `/api/project/:name/workspace`)
|
|
314
|
+
- Latest-wins: server `updated_at` vs client localStorage timestamp
|
|
315
|
+
- Panel layout changes debounced (1.5s) → POST to server
|
|
316
|
+
- On reconnect: server layout restored, client edits queued
|
|
317
|
+
4. **Cross-Device:** Any device can load workspace, browser restores exact grid + active tabs
|
|
318
|
+
|
|
287
319
|
---
|
|
288
320
|
|
|
289
321
|
## Communication Protocols
|
|
@@ -1066,6 +1098,403 @@ const queryConfig = {
|
|
|
1066
1098
|
const query = new Query(messages, queryConfig);
|
|
1067
1099
|
```
|
|
1068
1100
|
|
|
1101
|
+
---
|
|
1102
|
+
|
|
1103
|
+
## Extension System (v0.9.0+)
|
|
1104
|
+
|
|
1105
|
+
### Overview
|
|
1106
|
+
|
|
1107
|
+
PPM Extension System enables VSCode-compatible, npm-installable extensions that run in isolated Bun Worker threads. Crash-safe, permission-based, with RPC messaging between main process and worker, and WebSocket bridge for real-time UI updates.
|
|
1108
|
+
|
|
1109
|
+
**Architecture (3-tier):**
|
|
1110
|
+
```
|
|
1111
|
+
Extension Code (Bun Worker) ← @ppm/vscode-compat API
|
|
1112
|
+
│ RPC (postMessage)
|
|
1113
|
+
▼
|
|
1114
|
+
Main Process (Hono/Bun) ← extension-rpc-handlers.ts
|
|
1115
|
+
│ WebSocket (/ws/extensions)
|
|
1116
|
+
▼
|
|
1117
|
+
Browser (React) ← Zustand store + React components
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
**Key components:**
|
|
1121
|
+
- **Package Format:** npm packages (`@ppm/ext-database`, `@ppm/ext-docker`, etc.)
|
|
1122
|
+
- **Installation:** `~/.ppm/extensions/node_modules/{id}/`
|
|
1123
|
+
- **Lifecycle:** Install → Enable → Activate → Deactivate → Remove
|
|
1124
|
+
- **Worker Isolation:** Each activated extension runs in a Bun Worker (crash-safe, 10s activation timeout)
|
|
1125
|
+
- **Communication:** RPC (Worker↔Main) + WebSocket (Main↔Browser)
|
|
1126
|
+
- **API Shim:** `@ppm/vscode-compat` — VSCode-compatible API (commands, window, workspace)
|
|
1127
|
+
- **State Storage:** globalState + workspaceState in SQLite via Memento
|
|
1128
|
+
- **UI Bridge:** StatusBar, TreeView, WebviewPanel, QuickPick, InputBox, Notifications
|
|
1129
|
+
- **Contributions:** Commands, views, configuration contributed via manifest
|
|
1130
|
+
|
|
1131
|
+
### Manifest Format
|
|
1132
|
+
|
|
1133
|
+
Extension metadata defined in `package.json` under `ppm` key:
|
|
1134
|
+
|
|
1135
|
+
```json
|
|
1136
|
+
{
|
|
1137
|
+
"name": "@ppm/ext-database",
|
|
1138
|
+
"version": "1.0.0",
|
|
1139
|
+
"main": "dist/extension.js",
|
|
1140
|
+
"ppm": {
|
|
1141
|
+
"displayName": "Database Browser",
|
|
1142
|
+
"description": "Browse and query databases",
|
|
1143
|
+
"icon": "database.svg",
|
|
1144
|
+
"engines": { "ppm": ">=0.9.0" },
|
|
1145
|
+
"activationEvents": ["onView:databases"],
|
|
1146
|
+
"contributes": {
|
|
1147
|
+
"commands": [
|
|
1148
|
+
{
|
|
1149
|
+
"command": "ppm.database.openConnection",
|
|
1150
|
+
"title": "Open Database Connection",
|
|
1151
|
+
"category": "Database"
|
|
1152
|
+
}
|
|
1153
|
+
],
|
|
1154
|
+
"views": {
|
|
1155
|
+
"explorer": [
|
|
1156
|
+
{
|
|
1157
|
+
"id": "databases",
|
|
1158
|
+
"name": "Databases",
|
|
1159
|
+
"type": "tree"
|
|
1160
|
+
}
|
|
1161
|
+
]
|
|
1162
|
+
},
|
|
1163
|
+
"configuration": {
|
|
1164
|
+
"properties": {
|
|
1165
|
+
"ppm.database.maxRows": {
|
|
1166
|
+
"type": "number",
|
|
1167
|
+
"default": 1000,
|
|
1168
|
+
"description": "Max rows to fetch per query"
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
**Fields:**
|
|
1178
|
+
- `engines.ppm` — PPM version requirement
|
|
1179
|
+
- `activationEvents` — When extension activates (e.g., `onView:databases`, `onCommand:ext.activate`)
|
|
1180
|
+
- `contributes` — UI elements + commands contributed by extension
|
|
1181
|
+
|
|
1182
|
+
### Installation & Lifecycle
|
|
1183
|
+
|
|
1184
|
+
**Installation** (`ppm ext install @ppm/ext-database`):
|
|
1185
|
+
1. Fetch package from npm
|
|
1186
|
+
2. Extract to `~/.ppm/extensions/node_modules/{id}/`
|
|
1187
|
+
3. Parse manifest from `package.json`
|
|
1188
|
+
4. Store in SQLite `extensions` table (enabled=1)
|
|
1189
|
+
5. Discover contributions
|
|
1190
|
+
|
|
1191
|
+
**Activation** (`ppm ext enable @ppm/ext-database` or automatic):
|
|
1192
|
+
1. Load manifest + entry point from disk
|
|
1193
|
+
2. Spawn Bun Worker (process isolation)
|
|
1194
|
+
3. Create scoped `@ppm/vscode-compat` API instance (RPC-backed)
|
|
1195
|
+
4. Call `activate(context, vscodeApi)` with 10s timeout
|
|
1196
|
+
5. Register contributions in `contributionRegistry`
|
|
1197
|
+
6. Broadcast `contributions:update` via WS to all connected browsers
|
|
1198
|
+
7. Mark as activated
|
|
1199
|
+
|
|
1200
|
+
**Deactivation:**
|
|
1201
|
+
1. Unregister contributions
|
|
1202
|
+
2. Terminate worker
|
|
1203
|
+
3. Clear persisted state if needed
|
|
1204
|
+
|
|
1205
|
+
**Removal** (`ppm ext remove @ppm/ext-database`):
|
|
1206
|
+
1. Deactivate if active
|
|
1207
|
+
2. Delete from `~/.ppm/extensions/`
|
|
1208
|
+
3. Remove from SQLite
|
|
1209
|
+
4. Unregister contributions
|
|
1210
|
+
|
|
1211
|
+
### RPC Protocol (Extension ↔ Main Process)
|
|
1212
|
+
|
|
1213
|
+
**Message Types:**
|
|
1214
|
+
|
|
1215
|
+
1. **Request** (extension → main)
|
|
1216
|
+
```json
|
|
1217
|
+
{
|
|
1218
|
+
"type": "request",
|
|
1219
|
+
"id": 1,
|
|
1220
|
+
"method": "storage:get",
|
|
1221
|
+
"params": ["extId", "global", "key"]
|
|
1222
|
+
}
|
|
1223
|
+
```
|
|
1224
|
+
|
|
1225
|
+
2. **Response** (main → extension)
|
|
1226
|
+
```json
|
|
1227
|
+
{
|
|
1228
|
+
"type": "response",
|
|
1229
|
+
"id": 1,
|
|
1230
|
+
"result": "value"
|
|
1231
|
+
}
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
3. **Event** (both directions)
|
|
1235
|
+
```json
|
|
1236
|
+
{
|
|
1237
|
+
"type": "event",
|
|
1238
|
+
"event": "file:changed",
|
|
1239
|
+
"data": { "path": "/path/to/file" }
|
|
1240
|
+
}
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
**Built-in Methods:**
|
|
1244
|
+
- `storage:get(extId, scope, key)` — Get persistent value
|
|
1245
|
+
- `storage:set(extId, scope, key, value)` — Set persistent value
|
|
1246
|
+
- `storage:delete(extId, scope, key)` — Delete key
|
|
1247
|
+
- Extension can define custom RPC methods via `rpc.onRequest(method, handler)`
|
|
1248
|
+
|
|
1249
|
+
### State Storage
|
|
1250
|
+
|
|
1251
|
+
**Database Schema:**
|
|
1252
|
+
|
|
1253
|
+
```sql
|
|
1254
|
+
CREATE TABLE extension_storage (
|
|
1255
|
+
ext_id TEXT NOT NULL,
|
|
1256
|
+
scope TEXT NOT NULL, -- 'global' | 'workspace'
|
|
1257
|
+
key TEXT NOT NULL,
|
|
1258
|
+
value TEXT, -- JSON-serialized
|
|
1259
|
+
PRIMARY KEY (ext_id, scope, key)
|
|
1260
|
+
);
|
|
1261
|
+
```
|
|
1262
|
+
|
|
1263
|
+
**Scopes:**
|
|
1264
|
+
- **globalState** — Persists across all projects (e.g., user settings, cache)
|
|
1265
|
+
- **workspaceState** — Project-specific state (e.g., open panel state)
|
|
1266
|
+
|
|
1267
|
+
**API** (inside extension):
|
|
1268
|
+
```typescript
|
|
1269
|
+
// In activate(context: ExtensionContext)
|
|
1270
|
+
const globalVal = context.globalState.get("lastConnection", "default");
|
|
1271
|
+
await context.globalState.update("lastConnection", "my-db");
|
|
1272
|
+
|
|
1273
|
+
const wsVal = context.workspaceState.get("selectedTable");
|
|
1274
|
+
await context.workspaceState.update("selectedTable", "users");
|
|
1275
|
+
```
|
|
1276
|
+
|
|
1277
|
+
### WebSocket Bridge (Extension ↔ Browser)
|
|
1278
|
+
|
|
1279
|
+
Extensions interact with the browser UI via a dedicated WebSocket at `/ws/extensions`. The main process translates between Worker RPC and WS messages.
|
|
1280
|
+
|
|
1281
|
+
**Server → Client (ExtServerMsg):** `tree:update`, `tree:refresh`, `statusbar:update/remove`, `notification`, `quickpick:show`, `inputbox:show`, `webview:create/html/dispose/postMessage`, `contributions:update`
|
|
1282
|
+
|
|
1283
|
+
**Client → Server (ExtClientMsg):** `ready`, `command:execute`, `tree:expand/click`, `webview:message`, `quickpick:resolve`, `inputbox:resolve`, `notification:action`
|
|
1284
|
+
|
|
1285
|
+
**Message routing:**
|
|
1286
|
+
- Extension calls `vscode.window.showInformationMessage()` → RPC → `extension-rpc-handlers.ts` → `broadcastExtMsg()` → WS → `use-extension-ws` hook → toast notification
|
|
1287
|
+
- Browser user clicks tree item → WS `tree:click` → `extensions.ts` → Worker RPC `ext:command:execute` → CommandService → extension handler
|
|
1288
|
+
- Webview iframe postMessage → parent → CustomEvent → WS `webview:message` → Worker RPC `ext:webview:message` → EventEmitter → extension's `onDidReceiveMessage` handler
|
|
1289
|
+
|
|
1290
|
+
**Request/response pattern:** QuickPick, InputBox, and notification actions use `requestFromBrowser(msg, trackingId, 30s timeout)` — sends WS message and awaits browser response via pending Promise map.
|
|
1291
|
+
|
|
1292
|
+
### UI Components
|
|
1293
|
+
|
|
1294
|
+
Extension UI state lives in Zustand (`extension-store.ts`) and renders via React:
|
|
1295
|
+
- **StatusBar** — Fixed bottom bar with left/right aligned items
|
|
1296
|
+
- **TreeView** — Recursive tree with expand/collapse, renders in sidebar for `ext:*` tabs
|
|
1297
|
+
- **WebviewPanel** — Sandboxed iframe (`allow-scripts` only), `acquireVsCodeApi()` shim auto-injected
|
|
1298
|
+
- **QuickPick** — Filterable picker with keyboard nav, bottom-sheet on mobile
|
|
1299
|
+
- **InputBox** — Text input dialog with password mode support
|
|
1300
|
+
- **Command Palette** — Extension commands merged with built-in commands
|
|
1301
|
+
|
|
1302
|
+
### Contribution Registry
|
|
1303
|
+
|
|
1304
|
+
**Purpose:** Central registry of all extension contributions (commands, views, etc.)
|
|
1305
|
+
|
|
1306
|
+
**Storage:** In-memory map during runtime
|
|
1307
|
+
|
|
1308
|
+
**Endpoints:**
|
|
1309
|
+
- `GET /api/extensions/contributions` — List all active contributions
|
|
1310
|
+
|
|
1311
|
+
**Contribution Types:**
|
|
1312
|
+
1. **Commands** — Callable actions (e.g., `ppm.database.openConnection`)
|
|
1313
|
+
- Registered: `registry.registerCommand(extId, command)`
|
|
1314
|
+
- Invoked: `POST /api/extensions/{extId}/commands/{command}`
|
|
1315
|
+
|
|
1316
|
+
2. **Views** — Sidebar panels or tree views
|
|
1317
|
+
- Registered: `registry.registerView(extId, view)`
|
|
1318
|
+
- Rendered in UI based on `type` (tree, webview)
|
|
1319
|
+
|
|
1320
|
+
3. **Configuration** — Settings schema
|
|
1321
|
+
- Registered: `registry.registerConfig(extId, schema)`
|
|
1322
|
+
- Merged with global settings
|
|
1323
|
+
|
|
1324
|
+
### CLI Commands
|
|
1325
|
+
|
|
1326
|
+
```bash
|
|
1327
|
+
ppm ext list # List installed extensions
|
|
1328
|
+
ppm ext install @ppm/ext-database # Install from npm
|
|
1329
|
+
ppm ext remove @ppm/ext-database # Uninstall
|
|
1330
|
+
ppm ext enable @ppm/ext-database # Enable extension
|
|
1331
|
+
ppm ext disable @ppm/ext-database # Disable extension
|
|
1332
|
+
ppm ext dev /path/to/ext-src # Symlink local extension for dev
|
|
1333
|
+
ppm ext config <ext-id> <key> <value> # Set config value
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
**Dev Mode** (`ppm ext dev /path/to/src`):
|
|
1337
|
+
- Symlinks local extension to `~/.ppm/extensions/node_modules/`
|
|
1338
|
+
- Auto-reloads on file change
|
|
1339
|
+
- Extension runs from source (TypeScript not compiled)
|
|
1340
|
+
|
|
1341
|
+
### REST API
|
|
1342
|
+
|
|
1343
|
+
**Endpoints** (`src/server/routes/extensions.ts`):
|
|
1344
|
+
|
|
1345
|
+
| Method | Endpoint | Description |
|
|
1346
|
+
|--------|----------|-------------|
|
|
1347
|
+
| **GET** | `/api/extensions` | List installed extensions |
|
|
1348
|
+
| **POST** | `/api/extensions` | Install extension (body: {name, version?}) |
|
|
1349
|
+
| **GET** | `/api/extensions/:id` | Get extension info (manifest, status) |
|
|
1350
|
+
| **DELETE** | `/api/extensions/:id` | Remove extension |
|
|
1351
|
+
| **PATCH** | `/api/extensions/:id` | Update extension (body: {enabled}) |
|
|
1352
|
+
| **GET** | `/api/extensions/contributions` | List all contributions (commands, views, config) |
|
|
1353
|
+
| **POST** | `/api/extensions/:id/commands/:cmd` | Invoke extension command |
|
|
1354
|
+
|
|
1355
|
+
**Example: Install Extension**
|
|
1356
|
+
```bash
|
|
1357
|
+
POST /api/extensions
|
|
1358
|
+
Content-Type: application/json
|
|
1359
|
+
|
|
1360
|
+
{ "name": "@ppm/ext-database", "version": "1.0.0" }
|
|
1361
|
+
|
|
1362
|
+
# Response
|
|
1363
|
+
{
|
|
1364
|
+
"ok": true,
|
|
1365
|
+
"data": {
|
|
1366
|
+
"id": "@ppm/ext-database",
|
|
1367
|
+
"version": "1.0.0",
|
|
1368
|
+
"displayName": "Database Browser",
|
|
1369
|
+
"enabled": true,
|
|
1370
|
+
"activated": false
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
```
|
|
1374
|
+
|
|
1375
|
+
### Service Layer
|
|
1376
|
+
|
|
1377
|
+
**ExtensionService** (`src/services/extension.service.ts`):
|
|
1378
|
+
- `discover()` — Scan `~/.ppm/extensions/` for installed packages
|
|
1379
|
+
- `install(name)` — Fetch from npm, install locally
|
|
1380
|
+
- `remove(id)` — Uninstall extension
|
|
1381
|
+
- `activate(id)` — Load + run extension in worker
|
|
1382
|
+
- `deactivate(id)` — Terminate worker, cleanup
|
|
1383
|
+
- `parseManifest(pkg)` — Extract manifest from package.json
|
|
1384
|
+
- `setExtensionState(extId, scope, key, value)` — Persist state
|
|
1385
|
+
|
|
1386
|
+
**ExtensionInstaller** (`src/services/extension-installer.ts`):
|
|
1387
|
+
- `installExtension(name, dir)` — npm install + verify
|
|
1388
|
+
- `removeExtension(id, dir)` — rm -rf extension directory
|
|
1389
|
+
- `devLinkExtension(localPath)` — Symlink for local dev
|
|
1390
|
+
|
|
1391
|
+
**ExtensionManifest** (`src/services/extension-manifest.ts`):
|
|
1392
|
+
- `parseManifest(pkg)` — Validate + parse ppm section
|
|
1393
|
+
- `discoverManifests(dir)` — Scan all installed extensions
|
|
1394
|
+
|
|
1395
|
+
**RpcChannel** (`src/services/extension-rpc.ts`):
|
|
1396
|
+
- Bidirectional RPC messaging
|
|
1397
|
+
- Request/response matching by ID
|
|
1398
|
+
- Event broadcasting
|
|
1399
|
+
- Timeout handling
|
|
1400
|
+
|
|
1401
|
+
### Worker Integration
|
|
1402
|
+
|
|
1403
|
+
**ExtensionHostWorker** (`src/services/extension-host-worker.ts`):
|
|
1404
|
+
- Worker-side code that loads + activates extension
|
|
1405
|
+
- Loads extension code into worker context
|
|
1406
|
+
- Exposes ExtensionContext API (globalState, workspaceState, subscriptions)
|
|
1407
|
+
- Handles incoming RPC messages
|
|
1408
|
+
- Communicates back to main process
|
|
1409
|
+
|
|
1410
|
+
**Design:**
|
|
1411
|
+
```
|
|
1412
|
+
Main Process Worker
|
|
1413
|
+
↓ ↓
|
|
1414
|
+
ExtensionService ExtensionHostWorker
|
|
1415
|
+
↓ ↓
|
|
1416
|
+
RpcChannel ←────────────→ RpcChannel
|
|
1417
|
+
↓ ↓
|
|
1418
|
+
Sends: { Extension Code
|
|
1419
|
+
type: "request", (User's ext.ts)
|
|
1420
|
+
method: "..." ↓
|
|
1421
|
+
} activate(context)
|
|
1422
|
+
↓ ↓
|
|
1423
|
+
Handlers respond context.storage.get()
|
|
1424
|
+
↑ ↑
|
|
1425
|
+
└─────────────────┘
|
|
1426
|
+
```
|
|
1427
|
+
|
|
1428
|
+
### Dev Workflow
|
|
1429
|
+
|
|
1430
|
+
**Creating an Extension:**
|
|
1431
|
+
|
|
1432
|
+
1. Create npm package:
|
|
1433
|
+
```bash
|
|
1434
|
+
npm init -y @ppm/ext-my-feature
|
|
1435
|
+
npm install @ppm/extension-api
|
|
1436
|
+
```
|
|
1437
|
+
|
|
1438
|
+
2. Write `src/extension.ts`:
|
|
1439
|
+
```typescript
|
|
1440
|
+
import type { ExtensionContext } from "@ppm/extension-api";
|
|
1441
|
+
|
|
1442
|
+
export async function activate(context: ExtensionContext) {
|
|
1443
|
+
console.log(`Extension ${context.extensionId} activated!`);
|
|
1444
|
+
|
|
1445
|
+
const val = context.globalState.get("count", 0);
|
|
1446
|
+
await context.globalState.update("count", val + 1);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
export function deactivate() {
|
|
1450
|
+
console.log("Extension deactivated");
|
|
1451
|
+
}
|
|
1452
|
+
```
|
|
1453
|
+
|
|
1454
|
+
3. Add to `package.json`:
|
|
1455
|
+
```json
|
|
1456
|
+
{
|
|
1457
|
+
"ppm": {
|
|
1458
|
+
"displayName": "My Feature",
|
|
1459
|
+
"main": "dist/extension.js",
|
|
1460
|
+
"contributes": {
|
|
1461
|
+
"commands": [...]
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
```
|
|
1466
|
+
|
|
1467
|
+
4. Install locally for dev:
|
|
1468
|
+
```bash
|
|
1469
|
+
ppm ext dev /path/to/ext-my-feature
|
|
1470
|
+
```
|
|
1471
|
+
|
|
1472
|
+
5. Extension auto-activates based on `activationEvents`, state persists
|
|
1473
|
+
|
|
1474
|
+
### Crash Safety
|
|
1475
|
+
|
|
1476
|
+
**Worker Isolation:**
|
|
1477
|
+
- Each extension in isolated Bun Worker thread
|
|
1478
|
+
- Worker crash doesn't crash main process
|
|
1479
|
+
- Error events logged, extension marked as failed
|
|
1480
|
+
- Main process continues operating
|
|
1481
|
+
|
|
1482
|
+
**Cleanup:**
|
|
1483
|
+
- Worker terminates → cleanup timer expires after 5min
|
|
1484
|
+
- Persisted state preserved in SQLite (not lost on crash)
|
|
1485
|
+
- Next activation reloads from disk, state auto-restored
|
|
1486
|
+
|
|
1487
|
+
### Future Enhancements (Phase 2+)
|
|
1488
|
+
|
|
1489
|
+
- **UI Webview Support** — Extensions define HTML/React UI panels
|
|
1490
|
+
- **Extension Settings UI** — Auto-generate UI from `contributes.configuration`
|
|
1491
|
+
- **Hot Reload** — Auto-reload extension on file change during dev
|
|
1492
|
+
- **Marketplace** — Browse, rate, publish extensions (v1.0+)
|
|
1493
|
+
- **Permissions** — User prompt for sensitive operations
|
|
1494
|
+
- **Inter-Extension API** — Extensions can call each other via RPC
|
|
1495
|
+
|
|
1496
|
+
---
|
|
1497
|
+
|
|
1069
1498
|
**Tool Allow List:**
|
|
1070
1499
|
- All MCP tools automatically allowed via wildcard `mcp__*`
|
|
1071
1500
|
- MCP server connection failures don't block chat (logged as warning)
|
package/package.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hienlh/ppm",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Personal Project Manager — mobile-first web IDE with AI assistance",
|
|
5
5
|
"author": "hienlh",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"module": "src/index.ts",
|
|
8
8
|
"type": "module",
|
|
9
|
+
"workspaces": ["packages/*"],
|
|
9
10
|
"bin": {
|
|
10
11
|
"ppm": "src/index.ts"
|
|
11
12
|
},
|
|
12
13
|
"scripts": {
|
|
13
14
|
"dev": "concurrently \"bun run dev:server\" \"bun run dev:web\"",
|
|
14
|
-
"dev:server": "bun run --hot src/index.ts
|
|
15
|
+
"dev:server": "bun run --hot src/server/index.ts __serve__ 8081 0.0.0.0 '' dev",
|
|
15
16
|
"dev:web": "bun run vite --config vite.config.ts",
|
|
16
17
|
"build:web": "bun run vite build --config vite.config.ts",
|
|
17
18
|
"build": "bun run build:web && bun build src/index.ts --compile --outfile dist/ppm",
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
"esbuild": "^0.27.4",
|
|
33
34
|
"tailwindcss": "^4.2.1",
|
|
34
35
|
"vite": "^8.0.0",
|
|
35
|
-
"vite-plugin-pwa": "^1.2.0"
|
|
36
|
+
"vite-plugin-pwa": "^1.2.0",
|
|
37
|
+
"workbox-precaching": "^7.4.0"
|
|
36
38
|
},
|
|
37
39
|
"peerDependencies": {
|
|
38
40
|
"typescript": "^5.9.3"
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
"lucide-react": "^0.577.0",
|
|
61
63
|
"marked": "^17.0.4",
|
|
62
64
|
"mermaid": "^11.13.0",
|
|
65
|
+
"monaco-editor": "0.55.1",
|
|
63
66
|
"next-themes": "^0.4.6",
|
|
64
67
|
"postgres": "^3.4.8",
|
|
65
68
|
"qrcode-terminal": "^0.12.0",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ppm/ext-database",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "src/extension.ts",
|
|
5
|
+
"engines": { "ppm": ">=0.9.0" },
|
|
6
|
+
"activationEvents": ["onCommand:ppm-db.openViewer", "onView:ppm-db.connections"],
|
|
7
|
+
"contributes": {
|
|
8
|
+
"commands": [
|
|
9
|
+
{ "command": "ppm-db.openViewer", "title": "Database: Open Viewer" },
|
|
10
|
+
{ "command": "ppm-db.runQuery", "title": "Database: Run SQL Query" },
|
|
11
|
+
{ "command": "ppm-db.refreshConnections", "title": "Database: Refresh Connections", "icon": "refresh" },
|
|
12
|
+
{ "command": "ppm-db.addConnection", "title": "Add connection", "icon": "plus" }
|
|
13
|
+
],
|
|
14
|
+
"views": {
|
|
15
|
+
"sidebar": [
|
|
16
|
+
{ "id": "ppm-db.connections", "name": "DB Connections", "type": "tree" }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"menus": {
|
|
20
|
+
"commandPalette": [
|
|
21
|
+
{ "command": "ppm-db.openViewer" },
|
|
22
|
+
{ "command": "ppm-db.runQuery" }
|
|
23
|
+
],
|
|
24
|
+
"view/title": [
|
|
25
|
+
{ "command": "ppm-db.refreshConnections", "when": "view == ppm-db.connections", "group": "navigation" },
|
|
26
|
+
{ "command": "ppm-db.addConnection", "when": "view == ppm-db.connections", "group": "navigation" }
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"configuration": {
|
|
30
|
+
"properties": {
|
|
31
|
+
"ppm-db.maxRows": { "type": "number", "default": 100, "description": "Max rows to display" },
|
|
32
|
+
"ppm-db.autoConnect": { "type": "boolean", "default": true, "description": "Auto-connect on open" }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"ppm": {
|
|
37
|
+
"displayName": "Database Viewer",
|
|
38
|
+
"icon": "database",
|
|
39
|
+
"webviewDir": "webview"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TreeDataProvider for database connections → tables → columns.
|
|
3
|
+
* Fetches data from PPM REST API (/api/db/*).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
interface ConnectionNode {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
type: "connection" | "table" | "column";
|
|
10
|
+
connectionId?: number;
|
|
11
|
+
connectionName?: string;
|
|
12
|
+
connectionType?: string;
|
|
13
|
+
connectionColor?: string | null;
|
|
14
|
+
schemaName?: string;
|
|
15
|
+
dataType?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ApiConnection {
|
|
19
|
+
id: number;
|
|
20
|
+
name: string;
|
|
21
|
+
type: string;
|
|
22
|
+
color: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ApiTable {
|
|
26
|
+
name: string;
|
|
27
|
+
schema: string;
|
|
28
|
+
rowCount: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ApiColumn {
|
|
32
|
+
name: string;
|
|
33
|
+
type: string;
|
|
34
|
+
nullable: boolean;
|
|
35
|
+
pk: boolean;
|
|
36
|
+
defaultValue: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class ConnectionTreeProvider {
|
|
40
|
+
private _onDidChange: { fire: (el?: ConnectionNode) => void; event: unknown };
|
|
41
|
+
private baseUrl: string;
|
|
42
|
+
|
|
43
|
+
constructor(eventEmitter: { fire: (el?: ConnectionNode) => void; event: unknown }, baseUrl = "") {
|
|
44
|
+
this._onDidChange = eventEmitter;
|
|
45
|
+
this.baseUrl = baseUrl;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get onDidChangeTreeData() {
|
|
49
|
+
return this._onDidChange.event;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
refresh(): void {
|
|
53
|
+
this._onDidChange.fire(undefined);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getChildren(element?: ConnectionNode): Promise<ConnectionNode[]> {
|
|
57
|
+
if (!element) return this.getConnections();
|
|
58
|
+
if (element.type === "connection") return this.getTables(element);
|
|
59
|
+
if (element.type === "table") return this.getColumns(element);
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getTreeItem(element: ConnectionNode): Record<string, unknown> {
|
|
64
|
+
const isConn = element.type === "connection";
|
|
65
|
+
const isTable = element.type === "table";
|
|
66
|
+
const isCol = element.type === "column";
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
id: element.id,
|
|
70
|
+
label: element.name,
|
|
71
|
+
description: isCol ? element.dataType : undefined,
|
|
72
|
+
collapsibleState: isCol ? "none" : "collapsed",
|
|
73
|
+
contextValue: element.type,
|
|
74
|
+
command: isTable ? "ppm-db.openViewer" : undefined,
|
|
75
|
+
commandArgs: isTable
|
|
76
|
+
? [element.connectionId, element.connectionName ?? "Database", element.name, element.schemaName ?? "public"]
|
|
77
|
+
: undefined,
|
|
78
|
+
color: isConn ? (element.connectionColor ?? undefined) : undefined,
|
|
79
|
+
badge: isConn ? (element.connectionType === "postgres" ? "PG" : "DB") : undefined,
|
|
80
|
+
actions: isConn ? [
|
|
81
|
+
{ icon: "refresh", tooltip: "Refresh tables", command: "ppm-db.refreshConnection", commandArgs: [element.connectionId] },
|
|
82
|
+
] : undefined,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private async getConnections(): Promise<ConnectionNode[]> {
|
|
87
|
+
try {
|
|
88
|
+
const res = await fetch(`${this.baseUrl}/api/db/connections`);
|
|
89
|
+
const json = await res.json() as { ok: boolean; data?: ApiConnection[] };
|
|
90
|
+
if (!json.ok || !json.data) return [];
|
|
91
|
+
return json.data.map((c) => ({
|
|
92
|
+
id: `conn:${c.id}`,
|
|
93
|
+
name: c.name,
|
|
94
|
+
type: "connection" as const,
|
|
95
|
+
connectionId: c.id,
|
|
96
|
+
connectionType: c.type,
|
|
97
|
+
connectionColor: c.color,
|
|
98
|
+
}));
|
|
99
|
+
} catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async getTables(conn: ConnectionNode): Promise<ConnectionNode[]> {
|
|
105
|
+
try {
|
|
106
|
+
const res = await fetch(`${this.baseUrl}/api/db/connections/${conn.connectionId}/tables`);
|
|
107
|
+
const json = await res.json() as { ok: boolean; data?: ApiTable[] };
|
|
108
|
+
if (!json.ok || !json.data) return [];
|
|
109
|
+
return json.data.map((t) => ({
|
|
110
|
+
id: `table:${conn.connectionId}:${t.schema}.${t.name}`,
|
|
111
|
+
name: t.name,
|
|
112
|
+
type: "table" as const,
|
|
113
|
+
connectionId: conn.connectionId,
|
|
114
|
+
connectionName: conn.name,
|
|
115
|
+
connectionType: conn.connectionType,
|
|
116
|
+
schemaName: t.schema,
|
|
117
|
+
}));
|
|
118
|
+
} catch {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private async getColumns(table: ConnectionNode): Promise<ConnectionNode[]> {
|
|
124
|
+
try {
|
|
125
|
+
const schema = table.schemaName ?? "public";
|
|
126
|
+
const res = await fetch(
|
|
127
|
+
`${this.baseUrl}/api/db/connections/${table.connectionId}/schema?table=${encodeURIComponent(table.name)}&schema=${schema}`,
|
|
128
|
+
);
|
|
129
|
+
const json = await res.json() as { ok: boolean; data?: ApiColumn[] };
|
|
130
|
+
if (!json.ok || !json.data) return [];
|
|
131
|
+
return json.data.map((c) => ({
|
|
132
|
+
id: `col:${table.connectionId}:${table.name}.${c.name}`,
|
|
133
|
+
name: c.name,
|
|
134
|
+
type: "column" as const,
|
|
135
|
+
connectionId: table.connectionId,
|
|
136
|
+
dataType: c.type + (c.pk ? " PK" : ""),
|
|
137
|
+
}));
|
|
138
|
+
} catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|