@agent-native/core 0.26.9 → 0.28.0
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/dist/agent/run-ownership.d.ts +12 -0
- package/dist/agent/run-ownership.d.ts.map +1 -0
- package/dist/agent/run-ownership.js +39 -0
- package/dist/agent/run-ownership.js.map +1 -0
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +1 -0
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +9 -6
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/db-admin/DataGrid.d.ts +42 -0
- package/dist/client/db-admin/DataGrid.d.ts.map +1 -0
- package/dist/client/db-admin/DataGrid.js +204 -0
- package/dist/client/db-admin/DataGrid.js.map +1 -0
- package/dist/client/db-admin/DbAdminPage.d.ts +2 -0
- package/dist/client/db-admin/DbAdminPage.d.ts.map +1 -0
- package/dist/client/db-admin/DbAdminPage.js +72 -0
- package/dist/client/db-admin/DbAdminPage.js.map +1 -0
- package/dist/client/db-admin/DevDatabaseLink.d.ts +19 -0
- package/dist/client/db-admin/DevDatabaseLink.d.ts.map +1 -0
- package/dist/client/db-admin/DevDatabaseLink.js +25 -0
- package/dist/client/db-admin/DevDatabaseLink.js.map +1 -0
- package/dist/client/db-admin/EditableCell.d.ts +26 -0
- package/dist/client/db-admin/EditableCell.d.ts.map +1 -0
- package/dist/client/db-admin/EditableCell.js +150 -0
- package/dist/client/db-admin/EditableCell.js.map +1 -0
- package/dist/client/db-admin/FilterBar.d.ts +8 -0
- package/dist/client/db-admin/FilterBar.d.ts.map +1 -0
- package/dist/client/db-admin/FilterBar.js +68 -0
- package/dist/client/db-admin/FilterBar.js.map +1 -0
- package/dist/client/db-admin/ResultsGrid.d.ts +6 -0
- package/dist/client/db-admin/ResultsGrid.d.ts.map +1 -0
- package/dist/client/db-admin/ResultsGrid.js +41 -0
- package/dist/client/db-admin/ResultsGrid.js.map +1 -0
- package/dist/client/db-admin/RowSidePanel.d.ts +18 -0
- package/dist/client/db-admin/RowSidePanel.d.ts.map +1 -0
- package/dist/client/db-admin/RowSidePanel.js +104 -0
- package/dist/client/db-admin/RowSidePanel.js.map +1 -0
- package/dist/client/db-admin/SqlEditor.d.ts +8 -0
- package/dist/client/db-admin/SqlEditor.d.ts.map +1 -0
- package/dist/client/db-admin/SqlEditor.js +350 -0
- package/dist/client/db-admin/SqlEditor.js.map +1 -0
- package/dist/client/db-admin/TableBrowser.d.ts +10 -0
- package/dist/client/db-admin/TableBrowser.d.ts.map +1 -0
- package/dist/client/db-admin/TableBrowser.js +61 -0
- package/dist/client/db-admin/TableBrowser.js.map +1 -0
- package/dist/client/db-admin/TableEditor.d.ts +9 -0
- package/dist/client/db-admin/TableEditor.d.ts.map +1 -0
- package/dist/client/db-admin/TableEditor.js +254 -0
- package/dist/client/db-admin/TableEditor.js.map +1 -0
- package/dist/client/db-admin/cell-format.d.ts +55 -0
- package/dist/client/db-admin/cell-format.d.ts.map +1 -0
- package/dist/client/db-admin/cell-format.js +223 -0
- package/dist/client/db-admin/cell-format.js.map +1 -0
- package/dist/client/db-admin/changeset.d.ts +74 -0
- package/dist/client/db-admin/changeset.d.ts.map +1 -0
- package/dist/client/db-admin/changeset.js +169 -0
- package/dist/client/db-admin/changeset.js.map +1 -0
- package/dist/client/db-admin/export-utils.d.ts +15 -0
- package/dist/client/db-admin/export-utils.d.ts.map +1 -0
- package/dist/client/db-admin/export-utils.js +62 -0
- package/dist/client/db-admin/export-utils.js.map +1 -0
- package/dist/client/db-admin/index.d.ts +7 -0
- package/dist/client/db-admin/index.d.ts.map +1 -0
- package/dist/client/db-admin/index.js +8 -0
- package/dist/client/db-admin/index.js.map +1 -0
- package/dist/client/db-admin/sql-storage.d.ts +35 -0
- package/dist/client/db-admin/sql-storage.d.ts.map +1 -0
- package/dist/client/db-admin/sql-storage.js +117 -0
- package/dist/client/db-admin/sql-storage.js.map +1 -0
- package/dist/client/db-admin/storage.d.ts +24 -0
- package/dist/client/db-admin/storage.d.ts.map +1 -0
- package/dist/client/db-admin/storage.js +50 -0
- package/dist/client/db-admin/storage.js.map +1 -0
- package/dist/client/db-admin/useAgentSync.d.ts +22 -0
- package/dist/client/db-admin/useAgentSync.d.ts.map +1 -0
- package/dist/client/db-admin/useAgentSync.js +120 -0
- package/dist/client/db-admin/useAgentSync.js.map +1 -0
- package/dist/client/db-admin/useDbAdmin.d.ts +20 -0
- package/dist/client/db-admin/useDbAdmin.d.ts.map +1 -0
- package/dist/client/db-admin/useDbAdmin.js +154 -0
- package/dist/client/db-admin/useDbAdmin.js.map +1 -0
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +8 -4
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/use-dev-mode.d.ts +20 -2
- package/dist/client/use-dev-mode.d.ts.map +1 -1
- package/dist/client/use-dev-mode.js +49 -14
- package/dist/client/use-dev-mode.js.map +1 -1
- package/dist/credentials/index.d.ts.map +1 -1
- package/dist/credentials/index.js +25 -5
- package/dist/credentials/index.js.map +1 -1
- package/dist/db-admin/agent-tools.d.ts +15 -0
- package/dist/db-admin/agent-tools.d.ts.map +1 -0
- package/dist/db-admin/agent-tools.js +147 -0
- package/dist/db-admin/agent-tools.js.map +1 -0
- package/dist/db-admin/operations.d.ts +17 -0
- package/dist/db-admin/operations.d.ts.map +1 -0
- package/dist/db-admin/operations.js +541 -0
- package/dist/db-admin/operations.js.map +1 -0
- package/dist/db-admin/routes.d.ts +5 -0
- package/dist/db-admin/routes.d.ts.map +1 -0
- package/dist/db-admin/routes.js +134 -0
- package/dist/db-admin/routes.js.map +1 -0
- package/dist/db-admin/types.d.ts +85 -0
- package/dist/db-admin/types.d.ts.map +1 -0
- package/dist/db-admin/types.js +9 -0
- package/dist/db-admin/types.js.map +1 -0
- package/dist/extensions/url-safety.d.ts +20 -0
- package/dist/extensions/url-safety.d.ts.map +1 -1
- package/dist/extensions/url-safety.js +43 -0
- package/dist/extensions/url-safety.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts.map +1 -1
- package/dist/file-upload/actions/upload-image.js +6 -1
- package/dist/file-upload/actions/upload-image.js.map +1 -1
- package/dist/integrations/adapters/email.d.ts.map +1 -1
- package/dist/integrations/adapters/email.js +112 -0
- package/dist/integrations/adapters/email.js.map +1 -1
- package/dist/integrations/types.d.ts +11 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/scripts/db/exec.d.ts.map +1 -1
- package/dist/scripts/db/exec.js +2 -1
- package/dist/scripts/db/exec.js.map +1 -1
- package/dist/scripts/db/index.d.ts.map +1 -1
- package/dist/scripts/db/index.js +1 -0
- package/dist/scripts/db/index.js.map +1 -1
- package/dist/scripts/db/migrate-encrypt-credentials.d.ts +28 -0
- package/dist/scripts/db/migrate-encrypt-credentials.d.ts.map +1 -0
- package/dist/scripts/db/migrate-encrypt-credentials.js +190 -0
- package/dist/scripts/db/migrate-encrypt-credentials.js.map +1 -0
- package/dist/scripts/db/query.d.ts.map +1 -1
- package/dist/scripts/db/query.js +2 -1
- package/dist/scripts/db/query.js.map +1 -1
- package/dist/scripts/db/safety.d.ts +1 -0
- package/dist/scripts/db/safety.d.ts.map +1 -1
- package/dist/scripts/db/safety.js +32 -0
- package/dist/scripts/db/safety.js.map +1 -1
- package/dist/scripts/db/scoping.d.ts.map +1 -1
- package/dist/scripts/db/scoping.js +11 -1
- package/dist/scripts/db/scoping.js.map +1 -1
- package/dist/secrets/crypto.d.ts +28 -0
- package/dist/secrets/crypto.d.ts.map +1 -0
- package/dist/secrets/crypto.js +81 -0
- package/dist/secrets/crypto.js.map +1 -0
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +3 -61
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +5 -2
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +24 -7
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +49 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.js +3 -3
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +5 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/csrf.js +9 -1
- package/dist/server/csrf.js.map +1 -1
- package/dist/server/design-token-utils.d.ts +8 -1
- package/dist/server/design-token-utils.d.ts.map +1 -1
- package/dist/server/design-token-utils.js +12 -4
- package/dist/server/design-token-utils.js.map +1 -1
- package/dist/templates/default/AGENTS.md +4 -4
- package/dist/templates/default/app/routes/database.tsx +13 -0
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +4 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/a2a-protocol.md +2 -2
- package/docs/content/actions.md +2 -54
- package/docs/content/agent-mentions.md +1 -1
- package/docs/content/agent-teams.md +1 -1
- package/docs/content/authentication.md +2 -2
- package/docs/content/cli-adapters.md +33 -17
- package/docs/content/client.md +11 -20
- package/docs/content/code-agents-ui.md +19 -6
- package/docs/content/context-awareness.md +36 -20
- package/docs/content/database.md +3 -3
- package/docs/content/deployment.md +8 -8
- package/docs/content/dispatch.md +1 -1
- package/docs/content/external-agents.md +5 -1
- package/docs/content/faq.md +1 -0
- package/docs/content/frames.md +116 -30
- package/docs/content/getting-started.md +15 -14
- package/docs/content/mcp-clients.md +1 -1
- package/docs/content/mcp-protocol.md +11 -88
- package/docs/content/messaging.md +1 -1
- package/docs/content/migration-workbench.md +13 -87
- package/docs/content/multi-app-workspace.md +2 -38
- package/docs/content/multi-tenancy.md +3 -26
- package/docs/content/onboarding.md +10 -3
- package/docs/content/recurring-jobs.md +2 -2
- package/docs/content/security.md +33 -1
- package/docs/content/server.md +1 -1
- package/docs/content/template-assets.md +9 -9
- package/docs/content/template-brain.md +114 -388
- package/docs/content/template-clips.md +42 -2
- package/docs/content/template-content.md +1 -1
- package/docs/content/template-design.md +27 -0
- package/docs/content/template-dispatch.md +3 -3
- package/docs/content/template-forms.md +6 -6
- package/docs/content/template-starter.md +2 -2
- package/docs/content/using-your-agent.md +56 -0
- package/docs/content/workspace-management.md +6 -6
- package/docs/content/workspace.md +28 -9
- package/package.json +10 -3
- package/src/templates/default/AGENTS.md +4 -4
- package/src/templates/default/app/routes/database.tsx +13 -0
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-mode database admin operations.
|
|
3
|
+
*
|
|
4
|
+
* Pure, dialect-agnostic, RAW/UNSCOPED helpers backing the Supabase-Studio-like
|
|
5
|
+
* DB admin. These run the FULL database with no per-user `accessFilter`
|
|
6
|
+
* scoping — they are a developer tool, gated to dev + localhost at the route
|
|
7
|
+
* and agent-tool layers. Do NOT call these from any production-reachable
|
|
8
|
+
* surface.
|
|
9
|
+
*
|
|
10
|
+
* All access goes through the unified `getDbExec()` client, which uses `?`
|
|
11
|
+
* placeholders (auto-converted to `$1,$2,…` for Postgres) and returns rows
|
|
12
|
+
* keyed by column name. Identifiers are validated against a strict pattern and
|
|
13
|
+
* always double-quoted; values are ALWAYS parameterized — never interpolated.
|
|
14
|
+
*/
|
|
15
|
+
import { getDbExec, getDialect, isPostgres } from "../db/client.js";
|
|
16
|
+
import { notifyActionChange } from "../server/action-change.js";
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Identifier validation + quoting
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
21
|
+
/** Throw on any identifier that isn't a plain `[A-Za-z_][A-Za-z0-9_]*`. */
|
|
22
|
+
function assertIdent(name, kind = "identifier") {
|
|
23
|
+
if (typeof name !== "string" || !IDENT_RE.test(name)) {
|
|
24
|
+
throw new Error(`Invalid ${kind}: ${JSON.stringify(name)}`);
|
|
25
|
+
}
|
|
26
|
+
return name;
|
|
27
|
+
}
|
|
28
|
+
/** Double-quote an already-validated identifier (valid in PG and SQLite). */
|
|
29
|
+
function quoteIdent(name) {
|
|
30
|
+
return `"${assertIdent(name)}"`;
|
|
31
|
+
}
|
|
32
|
+
function dialect() {
|
|
33
|
+
return getDialect();
|
|
34
|
+
}
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Notify the UI after a real mutation so polling refetches.
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
async function notifyDbAdminChange() {
|
|
39
|
+
// The UI keys on useChangeVersions(["db-admin","action"]). notifyActionChange
|
|
40
|
+
// records a "db-admin" change AND a marker; the action route layer's generic
|
|
41
|
+
// "action" source is covered by passing the db-admin action name through the
|
|
42
|
+
// same primitive that the action surface uses.
|
|
43
|
+
await notifyActionChange({ actionName: "db-admin" }).catch(() => { });
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// listTables
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
export async function listTables() {
|
|
49
|
+
const db = getDbExec();
|
|
50
|
+
const summaries = [];
|
|
51
|
+
if (isPostgres()) {
|
|
52
|
+
const res = await db.execute(`SELECT table_name AS name, table_type AS type
|
|
53
|
+
FROM information_schema.tables
|
|
54
|
+
WHERE table_schema = 'public'
|
|
55
|
+
AND table_type IN ('BASE TABLE', 'VIEW')
|
|
56
|
+
ORDER BY table_name`);
|
|
57
|
+
for (const row of res.rows) {
|
|
58
|
+
const name = String(row.name);
|
|
59
|
+
const type = row.type === "VIEW" ? "view" : "table";
|
|
60
|
+
summaries.push({
|
|
61
|
+
name,
|
|
62
|
+
type,
|
|
63
|
+
rowCount: type === "view" ? null : await safeRowCount(name),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const res = await db.execute(`SELECT name, type FROM sqlite_master
|
|
69
|
+
WHERE type IN ('table', 'view')
|
|
70
|
+
AND name NOT LIKE 'sqlite_%'
|
|
71
|
+
ORDER BY name`);
|
|
72
|
+
for (const row of res.rows) {
|
|
73
|
+
const name = String(row.name);
|
|
74
|
+
const type = row.type === "view" ? "view" : "table";
|
|
75
|
+
summaries.push({
|
|
76
|
+
name,
|
|
77
|
+
type,
|
|
78
|
+
rowCount: type === "view" ? null : await safeRowCount(name),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { dialect: dialect(), tables: summaries };
|
|
83
|
+
}
|
|
84
|
+
async function safeRowCount(table) {
|
|
85
|
+
try {
|
|
86
|
+
const db = getDbExec();
|
|
87
|
+
const res = await db.execute(`SELECT COUNT(*) AS c FROM ${quoteIdent(table)}`);
|
|
88
|
+
const c = res.rows[0]?.c;
|
|
89
|
+
const n = Number(c);
|
|
90
|
+
return Number.isFinite(n) ? n : null;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// getTableSchema
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
export async function getTableSchema(table) {
|
|
100
|
+
assertIdent(table, "table name");
|
|
101
|
+
return isPostgres()
|
|
102
|
+
? getTableSchemaPostgres(table)
|
|
103
|
+
: getTableSchemaSqlite(table);
|
|
104
|
+
}
|
|
105
|
+
async function getTableSchemaPostgres(table) {
|
|
106
|
+
const db = getDbExec();
|
|
107
|
+
const typeRes = await db.execute({
|
|
108
|
+
sql: `SELECT table_type AS type FROM information_schema.tables
|
|
109
|
+
WHERE table_schema = 'public' AND table_name = ?`,
|
|
110
|
+
args: [table],
|
|
111
|
+
});
|
|
112
|
+
const type = typeRes.rows[0]?.type === "VIEW" ? "view" : "table";
|
|
113
|
+
const colRes = await db.execute({
|
|
114
|
+
sql: `SELECT
|
|
115
|
+
column_name AS name,
|
|
116
|
+
data_type AS type,
|
|
117
|
+
CASE WHEN is_nullable = 'NO' THEN 0 ELSE 1 END AS nullable,
|
|
118
|
+
column_default AS dflt
|
|
119
|
+
FROM information_schema.columns
|
|
120
|
+
WHERE table_schema = 'public' AND table_name = ?
|
|
121
|
+
ORDER BY ordinal_position`,
|
|
122
|
+
args: [table],
|
|
123
|
+
});
|
|
124
|
+
const pkRes = await db.execute({
|
|
125
|
+
sql: `SELECT kcu.column_name AS col
|
|
126
|
+
FROM information_schema.table_constraints tc
|
|
127
|
+
JOIN information_schema.key_column_usage kcu
|
|
128
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
129
|
+
AND tc.table_schema = kcu.table_schema
|
|
130
|
+
WHERE tc.table_name = ?
|
|
131
|
+
AND tc.table_schema = 'public'
|
|
132
|
+
AND tc.constraint_type = 'PRIMARY KEY'
|
|
133
|
+
ORDER BY kcu.ordinal_position`,
|
|
134
|
+
args: [table],
|
|
135
|
+
});
|
|
136
|
+
const primaryKey = pkRes.rows.map((r) => String(r.col));
|
|
137
|
+
const pkSet = new Set(primaryKey);
|
|
138
|
+
const fkRes = await db.execute({
|
|
139
|
+
sql: `SELECT
|
|
140
|
+
kcu.column_name AS col,
|
|
141
|
+
ccu.table_name AS ref_table,
|
|
142
|
+
ccu.column_name AS ref_col
|
|
143
|
+
FROM information_schema.table_constraints tc
|
|
144
|
+
JOIN information_schema.key_column_usage kcu
|
|
145
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
146
|
+
AND tc.table_schema = kcu.table_schema
|
|
147
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
148
|
+
ON tc.constraint_name = ccu.constraint_name
|
|
149
|
+
AND tc.table_schema = ccu.table_schema
|
|
150
|
+
WHERE tc.table_name = ?
|
|
151
|
+
AND tc.table_schema = 'public'
|
|
152
|
+
AND tc.constraint_type = 'FOREIGN KEY'`,
|
|
153
|
+
args: [table],
|
|
154
|
+
});
|
|
155
|
+
const foreignKeys = fkRes.rows.map((r) => ({
|
|
156
|
+
column: String(r.col),
|
|
157
|
+
refTable: String(r.ref_table),
|
|
158
|
+
refColumn: String(r.ref_col),
|
|
159
|
+
}));
|
|
160
|
+
const idxRes = await db.execute({
|
|
161
|
+
sql: `SELECT indexname AS name, indexdef AS def
|
|
162
|
+
FROM pg_indexes
|
|
163
|
+
WHERE schemaname = 'public' AND tablename = ?`,
|
|
164
|
+
args: [table],
|
|
165
|
+
});
|
|
166
|
+
const indexes = idxRes.rows.map((r) => {
|
|
167
|
+
const def = String(r.def ?? "");
|
|
168
|
+
const colMatch = def.match(/\(([^)]+)\)/);
|
|
169
|
+
const columns = colMatch
|
|
170
|
+
? colMatch[1]
|
|
171
|
+
.split(",")
|
|
172
|
+
.map((c) => c.trim().replace(/^"|"$/g, ""))
|
|
173
|
+
.filter(Boolean)
|
|
174
|
+
: [];
|
|
175
|
+
return {
|
|
176
|
+
name: String(r.name),
|
|
177
|
+
unique: /\bUNIQUE\b/i.test(def),
|
|
178
|
+
columns,
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
const columns = colRes.rows.map((r) => {
|
|
182
|
+
const name = String(r.name);
|
|
183
|
+
const dflt = r.dflt;
|
|
184
|
+
const defaultValue = dflt == null ? null : String(dflt);
|
|
185
|
+
return {
|
|
186
|
+
name,
|
|
187
|
+
type: String(r.type ?? "ANY"),
|
|
188
|
+
nullable: Number(r.nullable) === 1,
|
|
189
|
+
pk: pkSet.has(name),
|
|
190
|
+
defaultValue,
|
|
191
|
+
// Postgres serial/identity columns default to a sequence call.
|
|
192
|
+
autoIncrement: defaultValue != null &&
|
|
193
|
+
(/nextval\(/i.test(defaultValue) || /identity/i.test(defaultValue)),
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
name: table,
|
|
198
|
+
type,
|
|
199
|
+
columns,
|
|
200
|
+
primaryKey,
|
|
201
|
+
foreignKeys,
|
|
202
|
+
indexes,
|
|
203
|
+
rowCount: type === "view" ? null : await safeRowCount(table),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
async function getTableSchemaSqlite(table) {
|
|
207
|
+
const db = getDbExec();
|
|
208
|
+
const typeRes = await db.execute({
|
|
209
|
+
sql: `SELECT type FROM sqlite_master WHERE name = ? AND type IN ('table','view')`,
|
|
210
|
+
args: [table],
|
|
211
|
+
});
|
|
212
|
+
const type = typeRes.rows[0]?.type === "view" ? "view" : "table";
|
|
213
|
+
const colRes = await db.execute(`PRAGMA table_info(${quoteIdent(table)})`);
|
|
214
|
+
const columns = colRes.rows.map((r) => {
|
|
215
|
+
const name = String(r.name);
|
|
216
|
+
const dflt = r.dflt_value;
|
|
217
|
+
return {
|
|
218
|
+
name,
|
|
219
|
+
type: String(r.type ?? "ANY") || "ANY",
|
|
220
|
+
nullable: Number(r.notnull) === 0,
|
|
221
|
+
pk: Number(r.pk) > 0,
|
|
222
|
+
defaultValue: dflt == null ? null : String(dflt),
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
// PK order follows the pk index from table_info (1-based, 0 = not pk).
|
|
226
|
+
const primaryKey = colRes.rows
|
|
227
|
+
.filter((r) => Number(r.pk) > 0)
|
|
228
|
+
.sort((a, b) => Number(a.pk) - Number(b.pk))
|
|
229
|
+
.map((r) => String(r.name));
|
|
230
|
+
// INTEGER PRIMARY KEY in SQLite is an alias for the rowid (autoincrementing).
|
|
231
|
+
if (primaryKey.length === 1) {
|
|
232
|
+
const pkCol = columns.find((c) => c.name === primaryKey[0]);
|
|
233
|
+
if (pkCol && /^integer$/i.test(pkCol.type)) {
|
|
234
|
+
pkCol.autoIncrement = true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const fkRes = await db.execute(`PRAGMA foreign_key_list(${quoteIdent(table)})`);
|
|
238
|
+
const foreignKeys = fkRes.rows.map((r) => ({
|
|
239
|
+
column: String(r.from),
|
|
240
|
+
refTable: String(r.table),
|
|
241
|
+
refColumn: String(r.to),
|
|
242
|
+
}));
|
|
243
|
+
const idxListRes = await db.execute(`PRAGMA index_list(${quoteIdent(table)})`);
|
|
244
|
+
const indexes = [];
|
|
245
|
+
for (const idx of idxListRes.rows) {
|
|
246
|
+
const idxName = String(idx.name);
|
|
247
|
+
if (idxName.startsWith("sqlite_"))
|
|
248
|
+
continue;
|
|
249
|
+
const infoRes = await db.execute(`PRAGMA index_info(${quoteIdent(idxName)})`);
|
|
250
|
+
indexes.push({
|
|
251
|
+
name: idxName,
|
|
252
|
+
unique: Number(idx.unique) === 1,
|
|
253
|
+
columns: infoRes.rows
|
|
254
|
+
.map((c) => c.name)
|
|
255
|
+
.filter((n) => typeof n === "string"),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
name: table,
|
|
260
|
+
type,
|
|
261
|
+
columns,
|
|
262
|
+
primaryKey,
|
|
263
|
+
foreignKeys,
|
|
264
|
+
indexes,
|
|
265
|
+
rowCount: type === "view" ? null : await safeRowCount(table),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// getRows
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
const SAFE_OPS = new Set([
|
|
272
|
+
"eq",
|
|
273
|
+
"neq",
|
|
274
|
+
"lt",
|
|
275
|
+
"lte",
|
|
276
|
+
"gt",
|
|
277
|
+
"gte",
|
|
278
|
+
"like",
|
|
279
|
+
"ilike",
|
|
280
|
+
"in",
|
|
281
|
+
"is_null",
|
|
282
|
+
"not_null",
|
|
283
|
+
]);
|
|
284
|
+
const OP_SQL = {
|
|
285
|
+
eq: "=",
|
|
286
|
+
neq: "<>",
|
|
287
|
+
lt: "<",
|
|
288
|
+
lte: "<=",
|
|
289
|
+
gt: ">",
|
|
290
|
+
gte: ">=",
|
|
291
|
+
like: "LIKE",
|
|
292
|
+
};
|
|
293
|
+
/** Build a parameterized WHERE clause + args from filters. */
|
|
294
|
+
function buildWhere(filters) {
|
|
295
|
+
if (!filters || filters.length === 0)
|
|
296
|
+
return { clause: "", args: [] };
|
|
297
|
+
const parts = [];
|
|
298
|
+
const args = [];
|
|
299
|
+
for (const f of filters) {
|
|
300
|
+
const col = quoteIdent(f.column);
|
|
301
|
+
if (!SAFE_OPS.has(f.op)) {
|
|
302
|
+
throw new Error(`Invalid filter op: ${JSON.stringify(f.op)}`);
|
|
303
|
+
}
|
|
304
|
+
switch (f.op) {
|
|
305
|
+
case "is_null":
|
|
306
|
+
parts.push(`${col} IS NULL`);
|
|
307
|
+
break;
|
|
308
|
+
case "not_null":
|
|
309
|
+
parts.push(`${col} IS NOT NULL`);
|
|
310
|
+
break;
|
|
311
|
+
case "in": {
|
|
312
|
+
const values = Array.isArray(f.value) ? f.value : [f.value];
|
|
313
|
+
if (values.length === 0) {
|
|
314
|
+
// `col IN ()` is invalid SQL; an empty set matches nothing.
|
|
315
|
+
parts.push(`1 = 0`);
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
parts.push(`${col} IN (${values.map(() => "?").join(", ")})`);
|
|
319
|
+
args.push(...values);
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case "ilike": {
|
|
323
|
+
// SQLite LIKE is case-insensitive for ASCII by default; Postgres has
|
|
324
|
+
// a dedicated ILIKE operator.
|
|
325
|
+
if (isPostgres()) {
|
|
326
|
+
parts.push(`${col} ILIKE ?`);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
parts.push(`${col} LIKE ?`);
|
|
330
|
+
}
|
|
331
|
+
args.push(f.value);
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
default: {
|
|
335
|
+
parts.push(`${col} ${OP_SQL[f.op]} ?`);
|
|
336
|
+
args.push(f.value);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return { clause: parts.length ? ` WHERE ${parts.join(" AND ")}` : "", args };
|
|
342
|
+
}
|
|
343
|
+
function buildOrderBy(sort) {
|
|
344
|
+
if (!sort || sort.length === 0)
|
|
345
|
+
return "";
|
|
346
|
+
const parts = sort.map((s) => {
|
|
347
|
+
const dir = s.dir === "desc" ? "DESC" : "ASC";
|
|
348
|
+
return `${quoteIdent(s.column)} ${dir}`;
|
|
349
|
+
});
|
|
350
|
+
return ` ORDER BY ${parts.join(", ")}`;
|
|
351
|
+
}
|
|
352
|
+
export async function getRows(table, req) {
|
|
353
|
+
assertIdent(table, "table name");
|
|
354
|
+
const db = getDbExec();
|
|
355
|
+
const schema = await getTableSchema(table);
|
|
356
|
+
const page = Math.max(1, Math.floor(req.page) || 1);
|
|
357
|
+
const pageSize = Math.min(1000, Math.max(1, Math.floor(req.pageSize) || 50));
|
|
358
|
+
const offset = (page - 1) * pageSize;
|
|
359
|
+
const where = buildWhere(req.filters);
|
|
360
|
+
const orderBy = buildOrderBy(req.sort);
|
|
361
|
+
const quoted = quoteIdent(table);
|
|
362
|
+
const countRes = await db.execute({
|
|
363
|
+
sql: `SELECT COUNT(*) AS c FROM ${quoted}${where.clause}`,
|
|
364
|
+
args: where.args,
|
|
365
|
+
});
|
|
366
|
+
const total = Number(countRes.rows[0]?.c ?? 0) || 0;
|
|
367
|
+
const rowsRes = await db.execute({
|
|
368
|
+
sql: `SELECT * FROM ${quoted}${where.clause}${orderBy} LIMIT ? OFFSET ?`,
|
|
369
|
+
args: [...where.args, pageSize, offset],
|
|
370
|
+
});
|
|
371
|
+
return {
|
|
372
|
+
columns: schema.columns,
|
|
373
|
+
rows: rowsRes.rows,
|
|
374
|
+
total,
|
|
375
|
+
page,
|
|
376
|
+
pageSize,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
// ---------------------------------------------------------------------------
|
|
380
|
+
// applyMutations
|
|
381
|
+
// ---------------------------------------------------------------------------
|
|
382
|
+
function buildInsert(table, row) {
|
|
383
|
+
const cols = Object.keys(row);
|
|
384
|
+
if (cols.length === 0) {
|
|
385
|
+
throw new Error("Cannot insert an empty row");
|
|
386
|
+
}
|
|
387
|
+
cols.forEach((c) => assertIdent(c, "column name"));
|
|
388
|
+
const sql = `INSERT INTO ${quoteIdent(table)} (${cols
|
|
389
|
+
.map(quoteIdent)
|
|
390
|
+
.join(", ")}) VALUES (${cols.map(() => "?").join(", ")})`;
|
|
391
|
+
return { sql, args: cols.map((c) => row[c]) };
|
|
392
|
+
}
|
|
393
|
+
function buildUpdate(table, set, where) {
|
|
394
|
+
const setCols = Object.keys(set);
|
|
395
|
+
const whereCols = Object.keys(where);
|
|
396
|
+
if (setCols.length === 0)
|
|
397
|
+
throw new Error("Update requires a non-empty set");
|
|
398
|
+
if (whereCols.length === 0) {
|
|
399
|
+
throw new Error("Update requires a non-empty where clause");
|
|
400
|
+
}
|
|
401
|
+
setCols.forEach((c) => assertIdent(c, "column name"));
|
|
402
|
+
whereCols.forEach((c) => assertIdent(c, "column name"));
|
|
403
|
+
const setSql = setCols.map((c) => `${quoteIdent(c)} = ?`).join(", ");
|
|
404
|
+
const whereSql = whereCols.map((c) => `${quoteIdent(c)} = ?`).join(" AND ");
|
|
405
|
+
const sql = `UPDATE ${quoteIdent(table)} SET ${setSql} WHERE ${whereSql}`;
|
|
406
|
+
return {
|
|
407
|
+
sql,
|
|
408
|
+
args: [...setCols.map((c) => set[c]), ...whereCols.map((c) => where[c])],
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function buildDelete(table, where) {
|
|
412
|
+
const whereCols = Object.keys(where);
|
|
413
|
+
if (whereCols.length === 0) {
|
|
414
|
+
throw new Error("Delete requires a non-empty where clause");
|
|
415
|
+
}
|
|
416
|
+
whereCols.forEach((c) => assertIdent(c, "column name"));
|
|
417
|
+
const whereSql = whereCols.map((c) => `${quoteIdent(c)} = ?`).join(" AND ");
|
|
418
|
+
const sql = `DELETE FROM ${quoteIdent(table)} WHERE ${whereSql}`;
|
|
419
|
+
return { sql, args: whereCols.map((c) => where[c]) };
|
|
420
|
+
}
|
|
421
|
+
export async function applyMutations(table, m) {
|
|
422
|
+
assertIdent(table, "table name");
|
|
423
|
+
const statements = [];
|
|
424
|
+
for (const row of m.inserts ?? [])
|
|
425
|
+
statements.push(buildInsert(table, row));
|
|
426
|
+
for (const u of m.updates ?? []) {
|
|
427
|
+
statements.push(buildUpdate(table, u.set, u.where));
|
|
428
|
+
}
|
|
429
|
+
for (const where of m.deletes ?? []) {
|
|
430
|
+
statements.push(buildDelete(table, where));
|
|
431
|
+
}
|
|
432
|
+
const result = {
|
|
433
|
+
sql: statements.map((s) => s.sql),
|
|
434
|
+
inserted: 0,
|
|
435
|
+
updated: 0,
|
|
436
|
+
deleted: 0,
|
|
437
|
+
};
|
|
438
|
+
if (m.dryRun) {
|
|
439
|
+
// dryRun returns the SQL strings WITHOUT executing.
|
|
440
|
+
return result;
|
|
441
|
+
}
|
|
442
|
+
// getDbExec() does not expose a transaction handle, so statements run
|
|
443
|
+
// sequentially. A failure mid-batch surfaces as a thrown error with the
|
|
444
|
+
// counts accumulated so far; callers should treat a partial batch as a
|
|
445
|
+
// failure and re-run with a corrected payload.
|
|
446
|
+
const db = getDbExec();
|
|
447
|
+
const insertCount = m.inserts?.length ?? 0;
|
|
448
|
+
const updateCount = m.updates?.length ?? 0;
|
|
449
|
+
let executed = 0;
|
|
450
|
+
for (const stmt of statements) {
|
|
451
|
+
const res = await db.execute({ sql: stmt.sql, args: stmt.args });
|
|
452
|
+
if (executed < insertCount) {
|
|
453
|
+
result.inserted += 1;
|
|
454
|
+
}
|
|
455
|
+
else if (executed < insertCount + updateCount) {
|
|
456
|
+
result.updated += res.rowsAffected || 0;
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
result.deleted += res.rowsAffected || 0;
|
|
460
|
+
}
|
|
461
|
+
executed += 1;
|
|
462
|
+
}
|
|
463
|
+
if (statements.length > 0)
|
|
464
|
+
await notifyDbAdminChange();
|
|
465
|
+
return result;
|
|
466
|
+
}
|
|
467
|
+
// ---------------------------------------------------------------------------
|
|
468
|
+
// runSql
|
|
469
|
+
// ---------------------------------------------------------------------------
|
|
470
|
+
/** Error thrown when a destructive statement is run without confirmation. */
|
|
471
|
+
export class DbAdminConfirmRequiredError extends Error {
|
|
472
|
+
needsConfirm = true;
|
|
473
|
+
constructor(message) {
|
|
474
|
+
super(message);
|
|
475
|
+
this.name = "DbAdminConfirmRequiredError";
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/** Strip `--` line comments and `/* *\/` block comments from SQL. */
|
|
479
|
+
function stripComments(sql) {
|
|
480
|
+
return sql.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n\r]*/g, " ");
|
|
481
|
+
}
|
|
482
|
+
function isMutatingSql(sql) {
|
|
483
|
+
const head = stripComments(sql).trim().toLowerCase();
|
|
484
|
+
return /^(insert|update|delete|replace|create|alter|drop|truncate|merge|pragma\s+\w+\s*=)/.test(head);
|
|
485
|
+
}
|
|
486
|
+
/** Detect destructive ops on comment-stripped SQL. */
|
|
487
|
+
function isDestructiveSql(sql) {
|
|
488
|
+
const cleaned = stripComments(sql).trim();
|
|
489
|
+
const lower = cleaned.toLowerCase();
|
|
490
|
+
if (/^drop\b/.test(lower))
|
|
491
|
+
return true;
|
|
492
|
+
if (/^truncate\b/.test(lower))
|
|
493
|
+
return true;
|
|
494
|
+
if (/^delete\b/.test(lower) && !/\bwhere\b/.test(lower))
|
|
495
|
+
return true;
|
|
496
|
+
if (/^update\b/.test(lower) && !/\bwhere\b/.test(lower))
|
|
497
|
+
return true;
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
/** A leading SELECT (or CTE that ends in SELECT) with no LIMIT clause. */
|
|
501
|
+
function isBareSelectWithoutLimit(sql) {
|
|
502
|
+
const cleaned = stripComments(sql).trim();
|
|
503
|
+
const lower = cleaned.toLowerCase();
|
|
504
|
+
const isSelect = /^(select|with)\b/.test(lower);
|
|
505
|
+
if (!isSelect)
|
|
506
|
+
return false;
|
|
507
|
+
if (/\blimit\b/.test(lower))
|
|
508
|
+
return false;
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
export async function runSql(sql, params, opts = {}) {
|
|
512
|
+
if (typeof sql !== "string" || !sql.trim()) {
|
|
513
|
+
throw new Error("SQL is required");
|
|
514
|
+
}
|
|
515
|
+
if (isDestructiveSql(sql) && !opts.confirmDestructive) {
|
|
516
|
+
throw new DbAdminConfirmRequiredError("This statement is destructive (DROP / TRUNCATE / unscoped DELETE or UPDATE). Re-run with confirmDestructive: true to proceed.");
|
|
517
|
+
}
|
|
518
|
+
// Guardrail: auto-append LIMIT 100 to a bare SELECT so an accidental
|
|
519
|
+
// full-table scan can't dump a huge result set.
|
|
520
|
+
let finalSql = sql.trim().replace(/;\s*$/, "");
|
|
521
|
+
if (isBareSelectWithoutLimit(finalSql)) {
|
|
522
|
+
finalSql = `${finalSql} LIMIT 100`;
|
|
523
|
+
}
|
|
524
|
+
const db = getDbExec();
|
|
525
|
+
const started = Date.now();
|
|
526
|
+
const res = await db.execute(params && params.length > 0 ? { sql: finalSql, args: params } : finalSql);
|
|
527
|
+
const durationMs = Date.now() - started;
|
|
528
|
+
const rows = (res.rows ?? []);
|
|
529
|
+
const columns = rows.length > 0 && rows[0] && typeof rows[0] === "object"
|
|
530
|
+
? Object.keys(rows[0])
|
|
531
|
+
: [];
|
|
532
|
+
if (isMutatingSql(finalSql))
|
|
533
|
+
await notifyDbAdminChange();
|
|
534
|
+
return {
|
|
535
|
+
columns,
|
|
536
|
+
rows,
|
|
537
|
+
rowsAffected: res.rowsAffected ?? 0,
|
|
538
|
+
durationMs,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../src/db-admin/operations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Dev-mode database admin operations.\n *\n * Pure, dialect-agnostic, RAW/UNSCOPED helpers backing the Supabase-Studio-like\n * DB admin. These run the FULL database with no per-user `accessFilter`\n * scoping — they are a developer tool, gated to dev + localhost at the route\n * and agent-tool layers. Do NOT call these from any production-reachable\n * surface.\n *\n * All access goes through the unified `getDbExec()` client, which uses `?`\n * placeholders (auto-converted to `$1,$2,…` for Postgres) and returns rows\n * keyed by column name. Identifiers are validated against a strict pattern and\n * always double-quoted; values are ALWAYS parameterized — never interpolated.\n */\nimport { getDbExec, getDialect, isPostgres } from \"../db/client.js\";\nimport { notifyActionChange } from \"../server/action-change.js\";\nimport type {\n DbAdminColumn,\n DbAdminDialect,\n DbAdminFilter,\n DbAdminForeignKey,\n DbAdminIndex,\n DbAdminMutation,\n DbAdminMutationResult,\n DbAdminQueryResult,\n DbAdminRowsRequest,\n DbAdminRowsResult,\n DbAdminTableSchema,\n DbAdminTableSummary,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Identifier validation + quoting\n// ---------------------------------------------------------------------------\n\nconst IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/** Throw on any identifier that isn't a plain `[A-Za-z_][A-Za-z0-9_]*`. */\nfunction assertIdent(name: string, kind = \"identifier\"): string {\n if (typeof name !== \"string\" || !IDENT_RE.test(name)) {\n throw new Error(`Invalid ${kind}: ${JSON.stringify(name)}`);\n }\n return name;\n}\n\n/** Double-quote an already-validated identifier (valid in PG and SQLite). */\nfunction quoteIdent(name: string): string {\n return `\"${assertIdent(name)}\"`;\n}\n\nfunction dialect(): DbAdminDialect {\n return getDialect();\n}\n\n// ---------------------------------------------------------------------------\n// Notify the UI after a real mutation so polling refetches.\n// ---------------------------------------------------------------------------\n\nasync function notifyDbAdminChange(): Promise<void> {\n // The UI keys on useChangeVersions([\"db-admin\",\"action\"]). notifyActionChange\n // records a \"db-admin\" change AND a marker; the action route layer's generic\n // \"action\" source is covered by passing the db-admin action name through the\n // same primitive that the action surface uses.\n await notifyActionChange({ actionName: \"db-admin\" }).catch(() => {});\n}\n\n// ---------------------------------------------------------------------------\n// listTables\n// ---------------------------------------------------------------------------\n\nexport async function listTables(): Promise<{\n dialect: DbAdminDialect;\n tables: DbAdminTableSummary[];\n}> {\n const db = getDbExec();\n const summaries: DbAdminTableSummary[] = [];\n\n if (isPostgres()) {\n const res = await db.execute(\n `SELECT table_name AS name, table_type AS type\n FROM information_schema.tables\n WHERE table_schema = 'public'\n AND table_type IN ('BASE TABLE', 'VIEW')\n ORDER BY table_name`,\n );\n for (const row of res.rows) {\n const name = String((row as any).name);\n const type = (row as any).type === \"VIEW\" ? \"view\" : \"table\";\n summaries.push({\n name,\n type,\n rowCount: type === \"view\" ? null : await safeRowCount(name),\n });\n }\n } else {\n const res = await db.execute(\n `SELECT name, type FROM sqlite_master\n WHERE type IN ('table', 'view')\n AND name NOT LIKE 'sqlite_%'\n ORDER BY name`,\n );\n for (const row of res.rows) {\n const name = String((row as any).name);\n const type = (row as any).type === \"view\" ? \"view\" : \"table\";\n summaries.push({\n name,\n type,\n rowCount: type === \"view\" ? null : await safeRowCount(name),\n });\n }\n }\n\n return { dialect: dialect(), tables: summaries };\n}\n\nasync function safeRowCount(table: string): Promise<number | null> {\n try {\n const db = getDbExec();\n const res = await db.execute(\n `SELECT COUNT(*) AS c FROM ${quoteIdent(table)}`,\n );\n const c = (res.rows[0] as any)?.c;\n const n = Number(c);\n return Number.isFinite(n) ? n : null;\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// getTableSchema\n// ---------------------------------------------------------------------------\n\nexport async function getTableSchema(\n table: string,\n): Promise<DbAdminTableSchema> {\n assertIdent(table, \"table name\");\n return isPostgres()\n ? getTableSchemaPostgres(table)\n : getTableSchemaSqlite(table);\n}\n\nasync function getTableSchemaPostgres(\n table: string,\n): Promise<DbAdminTableSchema> {\n const db = getDbExec();\n\n const typeRes = await db.execute({\n sql: `SELECT table_type AS type FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = ?`,\n args: [table],\n });\n const type = (typeRes.rows[0] as any)?.type === \"VIEW\" ? \"view\" : \"table\";\n\n const colRes = await db.execute({\n sql: `SELECT\n column_name AS name,\n data_type AS type,\n CASE WHEN is_nullable = 'NO' THEN 0 ELSE 1 END AS nullable,\n column_default AS dflt\n FROM information_schema.columns\n WHERE table_schema = 'public' AND table_name = ?\n ORDER BY ordinal_position`,\n args: [table],\n });\n\n const pkRes = await db.execute({\n sql: `SELECT kcu.column_name AS col\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n WHERE tc.table_name = ?\n AND tc.table_schema = 'public'\n AND tc.constraint_type = 'PRIMARY KEY'\n ORDER BY kcu.ordinal_position`,\n args: [table],\n });\n const primaryKey = pkRes.rows.map((r) => String((r as any).col));\n const pkSet = new Set(primaryKey);\n\n const fkRes = await db.execute({\n sql: `SELECT\n kcu.column_name AS col,\n ccu.table_name AS ref_table,\n ccu.column_name AS ref_col\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage ccu\n ON tc.constraint_name = ccu.constraint_name\n AND tc.table_schema = ccu.table_schema\n WHERE tc.table_name = ?\n AND tc.table_schema = 'public'\n AND tc.constraint_type = 'FOREIGN KEY'`,\n args: [table],\n });\n const foreignKeys: DbAdminForeignKey[] = fkRes.rows.map((r) => ({\n column: String((r as any).col),\n refTable: String((r as any).ref_table),\n refColumn: String((r as any).ref_col),\n }));\n\n const idxRes = await db.execute({\n sql: `SELECT indexname AS name, indexdef AS def\n FROM pg_indexes\n WHERE schemaname = 'public' AND tablename = ?`,\n args: [table],\n });\n const indexes: DbAdminIndex[] = idxRes.rows.map((r) => {\n const def = String((r as any).def ?? \"\");\n const colMatch = def.match(/\\(([^)]+)\\)/);\n const columns = colMatch\n ? colMatch[1]\n .split(\",\")\n .map((c) => c.trim().replace(/^\"|\"$/g, \"\"))\n .filter(Boolean)\n : [];\n return {\n name: String((r as any).name),\n unique: /\\bUNIQUE\\b/i.test(def),\n columns,\n };\n });\n\n const columns: DbAdminColumn[] = colRes.rows.map((r) => {\n const name = String((r as any).name);\n const dflt = (r as any).dflt;\n const defaultValue = dflt == null ? null : String(dflt);\n return {\n name,\n type: String((r as any).type ?? \"ANY\"),\n nullable: Number((r as any).nullable) === 1,\n pk: pkSet.has(name),\n defaultValue,\n // Postgres serial/identity columns default to a sequence call.\n autoIncrement:\n defaultValue != null &&\n (/nextval\\(/i.test(defaultValue) || /identity/i.test(defaultValue)),\n };\n });\n\n return {\n name: table,\n type,\n columns,\n primaryKey,\n foreignKeys,\n indexes,\n rowCount: type === \"view\" ? null : await safeRowCount(table),\n };\n}\n\nasync function getTableSchemaSqlite(\n table: string,\n): Promise<DbAdminTableSchema> {\n const db = getDbExec();\n\n const typeRes = await db.execute({\n sql: `SELECT type FROM sqlite_master WHERE name = ? AND type IN ('table','view')`,\n args: [table],\n });\n const type = (typeRes.rows[0] as any)?.type === \"view\" ? \"view\" : \"table\";\n\n const colRes = await db.execute(`PRAGMA table_info(${quoteIdent(table)})`);\n const columns: DbAdminColumn[] = colRes.rows.map((r) => {\n const name = String((r as any).name);\n const dflt = (r as any).dflt_value;\n return {\n name,\n type: String((r as any).type ?? \"ANY\") || \"ANY\",\n nullable: Number((r as any).notnull) === 0,\n pk: Number((r as any).pk) > 0,\n defaultValue: dflt == null ? null : String(dflt),\n };\n });\n\n // PK order follows the pk index from table_info (1-based, 0 = not pk).\n const primaryKey = colRes.rows\n .filter((r) => Number((r as any).pk) > 0)\n .sort((a, b) => Number((a as any).pk) - Number((b as any).pk))\n .map((r) => String((r as any).name));\n\n // INTEGER PRIMARY KEY in SQLite is an alias for the rowid (autoincrementing).\n if (primaryKey.length === 1) {\n const pkCol = columns.find((c) => c.name === primaryKey[0]);\n if (pkCol && /^integer$/i.test(pkCol.type)) {\n pkCol.autoIncrement = true;\n }\n }\n\n const fkRes = await db.execute(\n `PRAGMA foreign_key_list(${quoteIdent(table)})`,\n );\n const foreignKeys: DbAdminForeignKey[] = fkRes.rows.map((r) => ({\n column: String((r as any).from),\n refTable: String((r as any).table),\n refColumn: String((r as any).to),\n }));\n\n const idxListRes = await db.execute(\n `PRAGMA index_list(${quoteIdent(table)})`,\n );\n const indexes: DbAdminIndex[] = [];\n for (const idx of idxListRes.rows) {\n const idxName = String((idx as any).name);\n if (idxName.startsWith(\"sqlite_\")) continue;\n const infoRes = await db.execute(\n `PRAGMA index_info(${quoteIdent(idxName)})`,\n );\n indexes.push({\n name: idxName,\n unique: Number((idx as any).unique) === 1,\n columns: infoRes.rows\n .map((c) => (c as any).name)\n .filter((n): n is string => typeof n === \"string\"),\n });\n }\n\n return {\n name: table,\n type,\n columns,\n primaryKey,\n foreignKeys,\n indexes,\n rowCount: type === \"view\" ? null : await safeRowCount(table),\n };\n}\n\n// ---------------------------------------------------------------------------\n// getRows\n// ---------------------------------------------------------------------------\n\nconst SAFE_OPS = new Set([\n \"eq\",\n \"neq\",\n \"lt\",\n \"lte\",\n \"gt\",\n \"gte\",\n \"like\",\n \"ilike\",\n \"in\",\n \"is_null\",\n \"not_null\",\n]);\n\nconst OP_SQL: Record<string, string> = {\n eq: \"=\",\n neq: \"<>\",\n lt: \"<\",\n lte: \"<=\",\n gt: \">\",\n gte: \">=\",\n like: \"LIKE\",\n};\n\n/** Build a parameterized WHERE clause + args from filters. */\nfunction buildWhere(filters: DbAdminFilter[] | undefined): {\n clause: string;\n args: unknown[];\n} {\n if (!filters || filters.length === 0) return { clause: \"\", args: [] };\n const parts: string[] = [];\n const args: unknown[] = [];\n for (const f of filters) {\n const col = quoteIdent(f.column);\n if (!SAFE_OPS.has(f.op)) {\n throw new Error(`Invalid filter op: ${JSON.stringify(f.op)}`);\n }\n switch (f.op) {\n case \"is_null\":\n parts.push(`${col} IS NULL`);\n break;\n case \"not_null\":\n parts.push(`${col} IS NOT NULL`);\n break;\n case \"in\": {\n const values = Array.isArray(f.value) ? f.value : [f.value];\n if (values.length === 0) {\n // `col IN ()` is invalid SQL; an empty set matches nothing.\n parts.push(`1 = 0`);\n break;\n }\n parts.push(`${col} IN (${values.map(() => \"?\").join(\", \")})`);\n args.push(...values);\n break;\n }\n case \"ilike\": {\n // SQLite LIKE is case-insensitive for ASCII by default; Postgres has\n // a dedicated ILIKE operator.\n if (isPostgres()) {\n parts.push(`${col} ILIKE ?`);\n } else {\n parts.push(`${col} LIKE ?`);\n }\n args.push(f.value);\n break;\n }\n default: {\n parts.push(`${col} ${OP_SQL[f.op]} ?`);\n args.push(f.value);\n break;\n }\n }\n }\n return { clause: parts.length ? ` WHERE ${parts.join(\" AND \")}` : \"\", args };\n}\n\nfunction buildOrderBy(sort: DbAdminRowsRequest[\"sort\"]): string {\n if (!sort || sort.length === 0) return \"\";\n const parts = sort.map((s) => {\n const dir = s.dir === \"desc\" ? \"DESC\" : \"ASC\";\n return `${quoteIdent(s.column)} ${dir}`;\n });\n return ` ORDER BY ${parts.join(\", \")}`;\n}\n\nexport async function getRows(\n table: string,\n req: DbAdminRowsRequest,\n): Promise<DbAdminRowsResult> {\n assertIdent(table, \"table name\");\n const db = getDbExec();\n const schema = await getTableSchema(table);\n\n const page = Math.max(1, Math.floor(req.page) || 1);\n const pageSize = Math.min(1000, Math.max(1, Math.floor(req.pageSize) || 50));\n const offset = (page - 1) * pageSize;\n\n const where = buildWhere(req.filters);\n const orderBy = buildOrderBy(req.sort);\n const quoted = quoteIdent(table);\n\n const countRes = await db.execute({\n sql: `SELECT COUNT(*) AS c FROM ${quoted}${where.clause}`,\n args: where.args,\n });\n const total = Number((countRes.rows[0] as any)?.c ?? 0) || 0;\n\n const rowsRes = await db.execute({\n sql: `SELECT * FROM ${quoted}${where.clause}${orderBy} LIMIT ? OFFSET ?`,\n args: [...where.args, pageSize, offset],\n });\n\n return {\n columns: schema.columns,\n rows: rowsRes.rows as Record<string, unknown>[],\n total,\n page,\n pageSize,\n };\n}\n\n// ---------------------------------------------------------------------------\n// applyMutations\n// ---------------------------------------------------------------------------\n\nfunction buildInsert(\n table: string,\n row: Record<string, unknown>,\n): { sql: string; args: unknown[] } {\n const cols = Object.keys(row);\n if (cols.length === 0) {\n throw new Error(\"Cannot insert an empty row\");\n }\n cols.forEach((c) => assertIdent(c, \"column name\"));\n const sql = `INSERT INTO ${quoteIdent(table)} (${cols\n .map(quoteIdent)\n .join(\", \")}) VALUES (${cols.map(() => \"?\").join(\", \")})`;\n return { sql, args: cols.map((c) => row[c]) };\n}\n\nfunction buildUpdate(\n table: string,\n set: Record<string, unknown>,\n where: Record<string, unknown>,\n): { sql: string; args: unknown[] } {\n const setCols = Object.keys(set);\n const whereCols = Object.keys(where);\n if (setCols.length === 0) throw new Error(\"Update requires a non-empty set\");\n if (whereCols.length === 0) {\n throw new Error(\"Update requires a non-empty where clause\");\n }\n setCols.forEach((c) => assertIdent(c, \"column name\"));\n whereCols.forEach((c) => assertIdent(c, \"column name\"));\n const setSql = setCols.map((c) => `${quoteIdent(c)} = ?`).join(\", \");\n const whereSql = whereCols.map((c) => `${quoteIdent(c)} = ?`).join(\" AND \");\n const sql = `UPDATE ${quoteIdent(table)} SET ${setSql} WHERE ${whereSql}`;\n return {\n sql,\n args: [...setCols.map((c) => set[c]), ...whereCols.map((c) => where[c])],\n };\n}\n\nfunction buildDelete(\n table: string,\n where: Record<string, unknown>,\n): { sql: string; args: unknown[] } {\n const whereCols = Object.keys(where);\n if (whereCols.length === 0) {\n throw new Error(\"Delete requires a non-empty where clause\");\n }\n whereCols.forEach((c) => assertIdent(c, \"column name\"));\n const whereSql = whereCols.map((c) => `${quoteIdent(c)} = ?`).join(\" AND \");\n const sql = `DELETE FROM ${quoteIdent(table)} WHERE ${whereSql}`;\n return { sql, args: whereCols.map((c) => where[c]) };\n}\n\nexport async function applyMutations(\n table: string,\n m: DbAdminMutation,\n): Promise<DbAdminMutationResult> {\n assertIdent(table, \"table name\");\n\n const statements: { sql: string; args: unknown[] }[] = [];\n for (const row of m.inserts ?? []) statements.push(buildInsert(table, row));\n for (const u of m.updates ?? []) {\n statements.push(buildUpdate(table, u.set, u.where));\n }\n for (const where of m.deletes ?? []) {\n statements.push(buildDelete(table, where));\n }\n\n const result: DbAdminMutationResult = {\n sql: statements.map((s) => s.sql),\n inserted: 0,\n updated: 0,\n deleted: 0,\n };\n\n if (m.dryRun) {\n // dryRun returns the SQL strings WITHOUT executing.\n return result;\n }\n\n // getDbExec() does not expose a transaction handle, so statements run\n // sequentially. A failure mid-batch surfaces as a thrown error with the\n // counts accumulated so far; callers should treat a partial batch as a\n // failure and re-run with a corrected payload.\n const db = getDbExec();\n const insertCount = m.inserts?.length ?? 0;\n const updateCount = m.updates?.length ?? 0;\n\n let executed = 0;\n for (const stmt of statements) {\n const res = await db.execute({ sql: stmt.sql, args: stmt.args });\n if (executed < insertCount) {\n result.inserted += 1;\n } else if (executed < insertCount + updateCount) {\n result.updated += res.rowsAffected || 0;\n } else {\n result.deleted += res.rowsAffected || 0;\n }\n executed += 1;\n }\n\n if (statements.length > 0) await notifyDbAdminChange();\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// runSql\n// ---------------------------------------------------------------------------\n\n/** Error thrown when a destructive statement is run without confirmation. */\nexport class DbAdminConfirmRequiredError extends Error {\n readonly needsConfirm = true;\n constructor(message: string) {\n super(message);\n this.name = \"DbAdminConfirmRequiredError\";\n }\n}\n\n/** Strip `--` line comments and `/* *\\/` block comments from SQL. */\nfunction stripComments(sql: string): string {\n return sql.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \" \").replace(/--[^\\n\\r]*/g, \" \");\n}\n\nfunction isMutatingSql(sql: string): boolean {\n const head = stripComments(sql).trim().toLowerCase();\n return /^(insert|update|delete|replace|create|alter|drop|truncate|merge|pragma\\s+\\w+\\s*=)/.test(\n head,\n );\n}\n\n/** Detect destructive ops on comment-stripped SQL. */\nfunction isDestructiveSql(sql: string): boolean {\n const cleaned = stripComments(sql).trim();\n const lower = cleaned.toLowerCase();\n if (/^drop\\b/.test(lower)) return true;\n if (/^truncate\\b/.test(lower)) return true;\n if (/^delete\\b/.test(lower) && !/\\bwhere\\b/.test(lower)) return true;\n if (/^update\\b/.test(lower) && !/\\bwhere\\b/.test(lower)) return true;\n return false;\n}\n\n/** A leading SELECT (or CTE that ends in SELECT) with no LIMIT clause. */\nfunction isBareSelectWithoutLimit(sql: string): boolean {\n const cleaned = stripComments(sql).trim();\n const lower = cleaned.toLowerCase();\n const isSelect = /^(select|with)\\b/.test(lower);\n if (!isSelect) return false;\n if (/\\blimit\\b/.test(lower)) return false;\n return true;\n}\n\nexport async function runSql(\n sql: string,\n params: unknown[] | undefined,\n opts: { confirmDestructive?: boolean } = {},\n): Promise<DbAdminQueryResult> {\n if (typeof sql !== \"string\" || !sql.trim()) {\n throw new Error(\"SQL is required\");\n }\n\n if (isDestructiveSql(sql) && !opts.confirmDestructive) {\n throw new DbAdminConfirmRequiredError(\n \"This statement is destructive (DROP / TRUNCATE / unscoped DELETE or UPDATE). Re-run with confirmDestructive: true to proceed.\",\n );\n }\n\n // Guardrail: auto-append LIMIT 100 to a bare SELECT so an accidental\n // full-table scan can't dump a huge result set.\n let finalSql = sql.trim().replace(/;\\s*$/, \"\");\n if (isBareSelectWithoutLimit(finalSql)) {\n finalSql = `${finalSql} LIMIT 100`;\n }\n\n const db = getDbExec();\n const started = Date.now();\n const res = await db.execute(\n params && params.length > 0 ? { sql: finalSql, args: params } : finalSql,\n );\n const durationMs = Date.now() - started;\n\n const rows = (res.rows ?? []) as Record<string, unknown>[];\n const columns =\n rows.length > 0 && rows[0] && typeof rows[0] === \"object\"\n ? Object.keys(rows[0])\n : [];\n\n if (isMutatingSql(finalSql)) await notifyDbAdminChange();\n\n return {\n columns,\n rows,\n rowsAffected: res.rowsAffected ?? 0,\n durationMs,\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/db-admin/routes.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAmBD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,GAAG,EACb,OAAO,GAAE,yBAA8B,GACtC,IAAI,CAwGN"}
|