@njeybe/nativ 2.2.0 → 2.3.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 +24 -1
- package/README.md +15 -0
- package/dist/runner/agent-supervisor.d.ts +5 -3
- package/dist/runner/agent-supervisor.js +17 -12
- package/dist/server/studio-server.js +1 -130
- package/dist/server/studio-ui/document.d.ts +3 -0
- package/dist/server/studio-ui/document.js +22 -0
- package/dist/server/studio-ui/pages/benchmarks/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/benchmarks/index.js +11 -0
- package/dist/server/studio-ui/pages/benchmarks/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/benchmarks/script.js +104 -0
- package/dist/server/studio-ui/pages/benchmarks/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/benchmarks/styles.js +30 -0
- package/dist/server/studio-ui/pages/database/browser.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/browser.js +326 -0
- package/dist/server/studio-ui/pages/database/connection.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/connection.js +158 -0
- package/dist/server/studio-ui/pages/database/data-shell.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/data-shell.js +165 -0
- package/dist/server/studio-ui/pages/database/drift.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/drift.js +301 -0
- package/dist/server/studio-ui/pages/database/events.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/events.js +231 -0
- package/dist/server/studio-ui/pages/database/explorer.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/explorer.js +183 -0
- package/dist/server/studio-ui/pages/database/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/database/index.js +28 -0
- package/dist/server/studio-ui/pages/database/markup.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/markup.js +32 -0
- package/dist/server/studio-ui/pages/database/state.d.ts +2 -0
- package/dist/server/studio-ui/pages/database/state.js +57 -0
- package/dist/server/studio-ui/pages/database/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/styles.js +118 -0
- package/dist/server/studio-ui/pages/flow/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/flow/index.js +11 -0
- package/dist/server/studio-ui/pages/flow/layout.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/layout.js +105 -0
- package/dist/server/studio-ui/pages/flow/plan-view.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/plan-view.js +67 -0
- package/dist/server/studio-ui/pages/flow/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/script.js +61 -0
- package/dist/server/studio-ui/pages/flow/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/styles.js +59 -0
- package/dist/server/studio-ui/pages/flow/timeline-model.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/timeline-model.js +95 -0
- package/dist/server/studio-ui/pages/flow/timeline-view.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/timeline-view.js +92 -0
- package/dist/server/studio-ui/pages/home/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/home/index.js +11 -0
- package/dist/server/studio-ui/pages/home/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/home/script.js +160 -0
- package/dist/server/studio-ui/pages/home/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/home/styles.js +48 -0
- package/dist/server/studio-ui/pages/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/index.js +17 -0
- package/dist/server/studio-ui/pages/tasks/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/tasks/index.js +11 -0
- package/dist/server/studio-ui/pages/tasks/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/tasks/script.js +96 -0
- package/dist/server/studio-ui/pages/tasks/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/tasks/styles.js +37 -0
- package/dist/server/studio-ui/pages/team/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/team/index.js +11 -0
- package/dist/server/studio-ui/pages/team/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/team/script.js +112 -0
- package/dist/server/studio-ui/pages/team/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/team/styles.js +22 -0
- package/dist/server/studio-ui/pages/types.d.ts +13 -0
- package/dist/server/studio-ui/pages/types.js +1 -0
- package/dist/server/studio-ui/pages/worktrees/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/worktrees/index.js +11 -0
- package/dist/server/studio-ui/pages/worktrees/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/worktrees/script.js +104 -0
- package/dist/server/studio-ui/pages/worktrees/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/worktrees/styles.js +15 -0
- package/dist/server/studio-ui/shell/actions.d.ts +1 -0
- package/dist/server/studio-ui/shell/actions.js +49 -0
- package/dist/server/studio-ui/shell/boot.d.ts +2 -0
- package/dist/server/studio-ui/shell/boot.js +13 -0
- package/dist/server/studio-ui/shell/console.d.ts +1 -0
- package/dist/server/studio-ui/shell/console.js +194 -0
- package/dist/server/studio-ui/shell/core.d.ts +1 -0
- package/dist/server/studio-ui/shell/core.js +50 -0
- package/dist/server/studio-ui/shell/detail-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/detail-view.js +80 -0
- package/dist/server/studio-ui/shell/detail.d.ts +1 -0
- package/dist/server/studio-ui/shell/detail.js +83 -0
- package/dist/server/studio-ui/shell/dispatch.d.ts +1 -0
- package/dist/server/studio-ui/shell/dispatch.js +150 -0
- package/dist/server/studio-ui/shell/events-tools.d.ts +1 -0
- package/dist/server/studio-ui/shell/events-tools.js +94 -0
- package/dist/server/studio-ui/shell/format.d.ts +1 -0
- package/dist/server/studio-ui/shell/format.js +151 -0
- package/dist/server/studio-ui/shell/icons.d.ts +2 -0
- package/dist/server/studio-ui/shell/icons.js +20 -0
- package/dist/server/studio-ui/shell/markup-head.d.ts +1 -0
- package/dist/server/studio-ui/shell/markup-head.js +43 -0
- package/dist/server/studio-ui/shell/markup-panels.d.ts +2 -0
- package/dist/server/studio-ui/shell/markup-panels.js +7 -0
- package/dist/server/studio-ui/shell/markup-tools.d.ts +1 -0
- package/dist/server/studio-ui/shell/markup-tools.js +222 -0
- package/dist/server/studio-ui/shell/model.d.ts +1 -0
- package/dist/server/studio-ui/shell/model.js +192 -0
- package/dist/server/studio-ui/shell/paint.d.ts +1 -0
- package/dist/server/studio-ui/shell/paint.js +154 -0
- package/dist/server/studio-ui/shell/palette-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/palette-view.js +60 -0
- package/dist/server/studio-ui/shell/palette.d.ts +1 -0
- package/dist/server/studio-ui/shell/palette.js +80 -0
- package/dist/server/studio-ui/shell/pipeline-state.d.ts +1 -0
- package/dist/server/studio-ui/shell/pipeline-state.js +152 -0
- package/dist/server/studio-ui/shell/proposals.d.ts +1 -0
- package/dist/server/studio-ui/shell/proposals.js +197 -0
- package/dist/server/studio-ui/shell/pure.d.ts +3 -0
- package/dist/server/studio-ui/shell/pure.js +9 -0
- package/dist/server/studio-ui/shell/router.d.ts +1 -0
- package/dist/server/studio-ui/shell/router.js +95 -0
- package/dist/server/studio-ui/shell/sidebar-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/sidebar-view.js +38 -0
- package/dist/server/studio-ui/shell/sync.d.ts +1 -0
- package/dist/server/studio-ui/shell/sync.js +120 -0
- package/dist/server/studio-ui/shell/toasts-format.d.ts +1 -0
- package/dist/server/studio-ui/shell/toasts-format.js +89 -0
- package/dist/server/studio-ui/shell/triage.d.ts +1 -0
- package/dist/server/studio-ui/shell/triage.js +210 -0
- package/dist/server/studio-ui/styles/console.d.ts +1 -0
- package/dist/server/studio-ui/styles/console.js +83 -0
- package/dist/server/studio-ui/styles/dialogs.d.ts +1 -0
- package/dist/server/studio-ui/styles/dialogs.js +118 -0
- package/dist/server/studio-ui/styles/responsive.d.ts +1 -0
- package/dist/server/studio-ui/styles/responsive.js +28 -0
- package/dist/server/studio-ui/styles/shell.d.ts +1 -0
- package/dist/server/studio-ui/styles/shell.js +117 -0
- package/dist/server/studio-ui/styles/strategist.d.ts +1 -0
- package/dist/server/studio-ui/styles/strategist.js +77 -0
- package/dist/server/studio-ui/styles/tokens-base.d.ts +1 -0
- package/dist/server/studio-ui/styles/tokens-base.js +131 -0
- package/dist/server/studio-ui.d.ts +1 -10
- package/dist/server/studio-ui.js +63 -5314
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const databaseDriftScript: string;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// Database Studio drift and migration script.
|
|
2
|
+
export const databaseDriftScript = String.raw ` // ─── Database Studio: drift ────────────────────────────────────────────────
|
|
3
|
+
function riskLevel(d) {
|
|
4
|
+
var s = d.summary;
|
|
5
|
+
if (s.hasDestructiveChanges || s.droppedTablesCount > 0) return 'HIGH';
|
|
6
|
+
if (s.alteredTablesCount > 0) return 'MEDIUM';
|
|
7
|
+
if (s.addedTablesCount > 0) return 'LOW';
|
|
8
|
+
return 'NONE';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function targetSelect() {
|
|
12
|
+
return '<div class="toolbar"><label for="diff-target" class="muted">Compare Dev against</label>' +
|
|
13
|
+
'<select class="input" id="diff-target"><option value="prod"' + (state.diffTarget === 'prod' ? ' selected' : '') + '>Production database</option>' +
|
|
14
|
+
'<option value="contract"' + (state.diffTarget === 'contract' ? ' selected' : '') + '>.ai/db_schema.json contract</option></select></div>';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function colCell(c) {
|
|
18
|
+
if (!c) return '<span class="muted">—</span>';
|
|
19
|
+
return esc(c.type) + (c.nullable ? '' : ' NOT NULL') + (c.default != null ? ' <span class="muted">DEFAULT</span> ' + esc(c.default) : '') +
|
|
20
|
+
(c.primaryKey ? ' <span class="badge badge-pk">PK</span>' : '') + (c.isMedia ? ' <span class="badge badge-media">IMAGE</span>' : '') +
|
|
21
|
+
(c.isSubcollection ? ' <span class="badge badge-subcol">SUBCOLLECTION</span>' : '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function alteredTableHtml(t) {
|
|
25
|
+
var targetLabel = state.diffTargetUsed === 'contract' ? 'Contract' : 'Prod';
|
|
26
|
+
var rows = [];
|
|
27
|
+
t.addedColumns.forEach(function (c) { rows.push('<tr class="row-added"><td class="mono">' + esc(c.name) + '</td><td><span class="badge badge-added">ADDED</span></td><td class="before mono">' + colCell(null) + '</td><td class="after mono">' + colCell(c) + '</td></tr>'); });
|
|
28
|
+
t.alteredColumns.forEach(function (ch) { rows.push('<tr class="row-altered"><td class="mono">' + esc(ch.name) + '</td><td><span class="badge badge-altered">' + esc(ch.changedFields.join(', ').toUpperCase()) + '</span></td><td class="before mono">' + colCell(ch.before) + '</td><td class="after mono">' + colCell(ch.after) + '</td></tr>'); });
|
|
29
|
+
t.droppedColumns.forEach(function (c) { rows.push('<tr class="row-dropped"><td class="mono">' + esc(c.name) + '</td><td><span class="badge badge-dropped">DROPPED</span></td><td class="before mono">' + colCell(c) + '</td><td class="after mono">' + colCell(null) + '</td></tr>'); });
|
|
30
|
+
var extras = [];
|
|
31
|
+
t.addedIndexes.forEach(function (i) { extras.push('<span class="chip" style="border-color:var(--success)">+ index ' + esc(i.name) + '</span>'); });
|
|
32
|
+
t.droppedIndexes.forEach(function (i) { extras.push('<span class="chip" style="border-color:var(--danger)">− index ' + esc(i.name) + '</span>'); });
|
|
33
|
+
t.addedForeignKeys.forEach(function (f) { extras.push('<span class="chip" style="border-color:var(--success)">+ FK ' + esc(f.column + ' → ' + f.referencedTable + '.' + f.referencedColumn) + '</span>'); });
|
|
34
|
+
t.droppedForeignKeys.forEach(function (f) { extras.push('<span class="chip" style="border-color:var(--danger)">− FK ' + esc(f.column + ' → ' + f.referencedTable + '.' + f.referencedColumn) + '</span>'); });
|
|
35
|
+
return '<details class="table-card" open><summary><span class="tname">' + esc(t.name) + '</span><span class="badge badge-altered">ALTERED</span>' +
|
|
36
|
+
(t.isDestructive ? '<span class="badge badge-dropped">DESTRUCTIVE</span>' : '') + '</summary><div class="table-body">' +
|
|
37
|
+
(rows.length ? '<table class="cols diff-table"><thead><tr><th scope="col">Column</th><th scope="col">Change</th><th scope="col">' + targetLabel + ' (before)</th><th scope="col">Dev (after)</th></tr></thead><tbody>' + rows.join('') + '</tbody></table>' : '') +
|
|
38
|
+
(extras.length ? '<div class="subhead">Indexes & constraints</div><div class="chip-list">' + extras.join('') + '</div>' : '') + '</div></details>';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function renderDrift() {
|
|
42
|
+
if (!state.diff) {
|
|
43
|
+
return targetSelect() + emptyState('Drift unavailable', state.diffError || 'Connect both databases to compute schema drift.');
|
|
44
|
+
}
|
|
45
|
+
var d = state.diff, s = d.summary, risk = riskLevel(d);
|
|
46
|
+
var targetLabel = state.diffTargetUsed === 'contract' ? 'the contract' : 'Prod';
|
|
47
|
+
var noun = driftNoun(true);
|
|
48
|
+
var html = targetSelect() + '<div class="summary-banner card" role="region" aria-label="Drift summary">' +
|
|
49
|
+
'<div class="stat"><div class="n" style="color:var(--success)">' + s.addedTablesCount + '</div><div class="l">Added ' + noun + '</div></div>' +
|
|
50
|
+
'<div class="stat"><div class="n" style="color:var(--active)">' + s.alteredTablesCount + '</div><div class="l">Altered ' + noun + '</div></div>' +
|
|
51
|
+
'<div class="stat"><div class="n" style="color:var(--danger)">' + s.droppedTablesCount + '</div><div class="l">Dropped ' + noun + '</div></div>' +
|
|
52
|
+
'<div class="stat"><div class="n muted">' + s.unchangedTablesCount + '</div><div class="l">Unchanged</div></div>' +
|
|
53
|
+
'<div class="stat"><div class="n risk-' + risk + '">' + risk + '</div><div class="l">Risk level</div></div></div>';
|
|
54
|
+
|
|
55
|
+
if (!s.addedTablesCount && !s.alteredTablesCount && !s.droppedTablesCount) {
|
|
56
|
+
return html + '<div class="empty card"><h3>In sync</h3><p>Dev matches ' + esc(targetLabel) + '. No drift detected.</p></div>';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (d.addedTables.length) {
|
|
60
|
+
html += '<section class="section card"><h3><span class="badge badge-added">NEW TABLE</span> Added in Dev (' + d.addedTables.length + ')</h3>' +
|
|
61
|
+
d.addedTables.map(function (t) { return tableCard(t, 'added'); }).join('') + '</section>';
|
|
62
|
+
}
|
|
63
|
+
if (d.alteredTables.length) {
|
|
64
|
+
html += '<section class="section card"><h3><span class="badge badge-altered">ALTERED</span> Altered ' + noun + ' (' + d.alteredTables.length + ')</h3>' +
|
|
65
|
+
d.alteredTables.map(alteredTableHtml).join('') + '</section>';
|
|
66
|
+
}
|
|
67
|
+
if (d.droppedTables.length) {
|
|
68
|
+
html += '<section class="section card"><h3><span class="badge badge-dropped">DROPPED</span> Dropped in Dev (' + d.droppedTables.length + ')</h3>' +
|
|
69
|
+
'<div class="alert-danger" role="alert"><strong>High severity:</strong> these ' + noun + ' exist in ' + esc(targetLabel) +
|
|
70
|
+
' but not in Dev. Applying this migration would permanently delete them and all their data.</div>' +
|
|
71
|
+
d.droppedTables.map(function (t) { return tableCard(t, 'dropped'); }).join('') + '</section>';
|
|
72
|
+
}
|
|
73
|
+
return html;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─── Migration script (SQL DDL / MongoDB shell / Firestore index JSON) ─────
|
|
77
|
+
function sqlDialect() {
|
|
78
|
+
var st = state.status || {};
|
|
79
|
+
if (state.diffTargetUsed === 'prod' && st.prod && st.prod.connected) return st.prod.engine;
|
|
80
|
+
return (st.dev && st.dev.engine) || 'postgresql';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function generateScript(d) {
|
|
84
|
+
var dialect = sqlDialect();
|
|
85
|
+
if (dialect === 'mongodb') { var js = generateMongo(d); return { text: js, lang: 'js', count: countStatements(js, 'js') }; }
|
|
86
|
+
if (dialect === 'firestore') return generateFirestore(d);
|
|
87
|
+
var sql = generateSql(d);
|
|
88
|
+
return { text: sql, lang: 'sql', count: countStatements(sql, 'sql') };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function generateMongo(d) {
|
|
92
|
+
var target = state.diffTargetUsed === 'contract' ? '.ai/db_schema.json' : 'Prod';
|
|
93
|
+
var coll = function (name) { return 'db.getCollection(' + JSON.stringify(name) + ')'; };
|
|
94
|
+
var keySpec = function (i) {
|
|
95
|
+
return '{ ' + i.columns.map(function (c) { return JSON.stringify(c) + ': 1'; }).join(', ') + ' }';
|
|
96
|
+
};
|
|
97
|
+
var createIdx = function (t, i) {
|
|
98
|
+
return coll(t) + '.createIndex(' + keySpec(i) + ', { name: ' + JSON.stringify(i.name) + (i.unique ? ', unique: true' : '') + ' });';
|
|
99
|
+
};
|
|
100
|
+
var out = [
|
|
101
|
+
'// Nativ migration preview: Dev → ' + target + ' (MongoDB shell)',
|
|
102
|
+
'// Generated from structural metadata only. Review carefully; nothing is executed automatically.',
|
|
103
|
+
''
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
d.addedTables.forEach(function (t) {
|
|
107
|
+
out.push('// [NEW COLLECTION] ' + t.name);
|
|
108
|
+
out.push('db.createCollection(' + JSON.stringify(t.name) + ');');
|
|
109
|
+
(t.indexes || []).forEach(function (i) { out.push(createIdx(t.name, i)); });
|
|
110
|
+
out.push('');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
d.alteredTables.forEach(function (t) {
|
|
114
|
+
out.push('// [ALTERED] ' + t.name + (t.isDestructive ? ' [DESTRUCTIVE]' : ''));
|
|
115
|
+
t.droppedIndexes.forEach(function (i) { out.push(coll(t.name) + '.dropIndex(' + JSON.stringify(i.name) + ');'); });
|
|
116
|
+
t.addedColumns.forEach(function (c) {
|
|
117
|
+
if (c.isSubcollection) { out.push('// + subcollection ' + JSON.stringify(c.name) + ' (Firestore-only concept; model as a separate collection or embedded array in MongoDB)'); return; }
|
|
118
|
+
out.push('// + field ' + JSON.stringify(c.name) + ' (' + c.type + '): schemaless, no DDL needed. Optional backfill:');
|
|
119
|
+
out.push('// ' + coll(t.name) + '.updateMany({ ' + JSON.stringify(c.name) + ': { $exists: false } }, { $set: { ' + JSON.stringify(c.name) + ': null } });');
|
|
120
|
+
});
|
|
121
|
+
t.alteredColumns.forEach(function (ch) {
|
|
122
|
+
out.push('// ~ field ' + JSON.stringify(ch.name) + ': ' + ch.before.type + ' → ' + ch.after.type + ' (' + ch.changedFields.join(', ') + '); convert existing values with a data migration.');
|
|
123
|
+
});
|
|
124
|
+
t.droppedColumns.forEach(function (c) {
|
|
125
|
+
out.push('// [DESTRUCTIVE] removes field data from every document');
|
|
126
|
+
out.push(coll(t.name) + '.updateMany({}, { $unset: { ' + JSON.stringify(c.name) + ': "" } });');
|
|
127
|
+
});
|
|
128
|
+
t.addedIndexes.forEach(function (i) { out.push(createIdx(t.name, i)); });
|
|
129
|
+
out.push('');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
d.droppedTables.forEach(function (t) {
|
|
133
|
+
out.push('// [DESTRUCTIVE: DROPPED] ' + t.name + ': permanently deletes the collection and all documents');
|
|
134
|
+
out.push(coll(t.name) + '.drop();');
|
|
135
|
+
out.push('');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (!d.addedTables.length && !d.alteredTables.length && !d.droppedTables.length) out.push('// No schema drift. Nothing to migrate.');
|
|
139
|
+
return out.join('\n').replace(/\n+$/, '\n');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Firestore: deployable index definitions (firestore.indexes.json shape) plus a list of data-migration steps. */
|
|
143
|
+
function generateFirestore(d) {
|
|
144
|
+
var target = state.diffTargetUsed === 'contract' ? '.ai/db_schema.json' : 'Prod';
|
|
145
|
+
var indexes = [], fieldOverrides = [], steps = [], warnings = [];
|
|
146
|
+
var addIndex = function (collection, i) {
|
|
147
|
+
if (i.unique) warnings.push('Firestore has no unique indexes: enforce uniqueness of ' + collection + '.' + i.columns.join('+') + ' (' + i.name + ') in application code or security rules.');
|
|
148
|
+
if (i.columns.length > 1) {
|
|
149
|
+
indexes.push({ collectionGroup: collection, queryScope: 'COLLECTION', fields: i.columns.map(function (f) { return { fieldPath: f, order: 'ASCENDING' }; }) });
|
|
150
|
+
} else if (i.columns.length === 1) {
|
|
151
|
+
fieldOverrides.push({ collectionGroup: collection, fieldPath: i.columns[0], indexes: [{ order: 'ASCENDING', queryScope: 'COLLECTION' }] });
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
d.addedTables.forEach(function (t) {
|
|
156
|
+
steps.push({ op: 'createCollection', collection: t.name, note: 'Collections are created implicitly by the first document write.' });
|
|
157
|
+
(t.indexes || []).forEach(function (i) { addIndex(t.name, i); });
|
|
158
|
+
});
|
|
159
|
+
d.alteredTables.forEach(function (t) {
|
|
160
|
+
t.addedColumns.forEach(function (c) {
|
|
161
|
+
steps.push(c.isSubcollection
|
|
162
|
+
? { op: 'addSubcollection', collection: t.name, subcollection: c.name }
|
|
163
|
+
: { op: 'addField', collection: t.name, field: c.name, type: c.type });
|
|
164
|
+
});
|
|
165
|
+
t.alteredColumns.forEach(function (ch) { steps.push({ op: 'changeField', collection: t.name, field: ch.name, from: ch.before.type, to: ch.after.type, changed: ch.changedFields }); });
|
|
166
|
+
t.droppedColumns.forEach(function (c) { steps.push({ op: 'deleteField', collection: t.name, field: c.name, destructive: true }); });
|
|
167
|
+
t.addedIndexes.forEach(function (i) { addIndex(t.name, i); });
|
|
168
|
+
t.droppedIndexes.forEach(function (i) { steps.push({ op: 'removeIndex', collection: t.name, index: i.name, fields: i.columns }); });
|
|
169
|
+
});
|
|
170
|
+
d.droppedTables.forEach(function (t) {
|
|
171
|
+
steps.push({ op: 'deleteCollection', collection: t.name, destructive: true, note: 'Permanently deletes every document (e.g. firebase firestore:delete --recursive).' });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
var doc = {
|
|
175
|
+
_nativ: 'Preview Dev → ' + target + ' (Firestore). Nothing runs automatically.',
|
|
176
|
+
_deploy: 'indexes + fieldOverrides: firebase deploy --only firestore:indexes; dataMigrations: apply with a script',
|
|
177
|
+
indexes: indexes,
|
|
178
|
+
fieldOverrides: fieldOverrides,
|
|
179
|
+
dataMigrations: steps
|
|
180
|
+
};
|
|
181
|
+
if (warnings.length) doc.warnings = warnings;
|
|
182
|
+
return { text: JSON.stringify(doc, null, 2) + '\n', lang: 'json', count: indexes.length + fieldOverrides.length + steps.length };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function generateSql(d) {
|
|
186
|
+
var dialect = sqlDialect();
|
|
187
|
+
var BT = String.fromCharCode(96);
|
|
188
|
+
var q = function (name) {
|
|
189
|
+
return dialect === 'mysql' ? BT + String(name).split(BT).join(BT + BT) + BT : '"' + String(name).split('"').join('""') + '"';
|
|
190
|
+
};
|
|
191
|
+
var colDef = function (c) { return q(c.name) + ' ' + c.type + (c.nullable ? '' : ' NOT NULL') + (c.default != null ? ' DEFAULT ' + c.default : ''); };
|
|
192
|
+
var fkDef = function (f) { return 'CONSTRAINT ' + q(f.name) + ' FOREIGN KEY (' + q(f.column) + ') REFERENCES ' + q(f.referencedTable) + ' (' + q(f.referencedColumn) + ')' + (f.onDelete ? ' ON DELETE ' + f.onDelete : ''); };
|
|
193
|
+
var idxDef = function (t, i) { return 'CREATE ' + (i.unique ? 'UNIQUE ' : '') + 'INDEX ' + q(i.name) + ' ON ' + q(t) + ' (' + i.columns.map(q).join(', ') + ');'; };
|
|
194
|
+
var dropIdx = function (t, i) { return dialect === 'mysql' ? 'DROP INDEX ' + q(i.name) + ' ON ' + q(t) + ';' : 'DROP INDEX ' + q(i.name) + ';'; };
|
|
195
|
+
var target = state.diffTargetUsed === 'contract' ? '.ai/db_schema.json' : 'Prod';
|
|
196
|
+
var out = [
|
|
197
|
+
'-- Nativ migration preview: Dev → ' + target + ' (' + (ENGINE_LABEL[dialect] || dialect) + ' dialect)',
|
|
198
|
+
'-- Generated from structural metadata only. Review carefully; nothing is executed automatically.',
|
|
199
|
+
''
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
d.addedTables.forEach(function (t) {
|
|
203
|
+
var lines = t.columns.map(function (c) { return ' ' + colDef(c); });
|
|
204
|
+
var pks = t.columns.filter(function (c) { return c.primaryKey; }).map(function (c) { return q(c.name); });
|
|
205
|
+
if (pks.length) lines.push(' PRIMARY KEY (' + pks.join(', ') + ')');
|
|
206
|
+
(t.foreignKeys || []).forEach(function (f) { lines.push(' ' + fkDef(f)); });
|
|
207
|
+
out.push('-- [NEW TABLE] ' + t.name);
|
|
208
|
+
out.push('CREATE TABLE ' + q(t.name) + ' (\n' + lines.join(',\n') + '\n);');
|
|
209
|
+
(t.indexes || []).forEach(function (i) { out.push(idxDef(t.name, i)); });
|
|
210
|
+
out.push('');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
d.alteredTables.forEach(function (t) {
|
|
214
|
+
var T = 'ALTER TABLE ' + q(t.name) + ' ';
|
|
215
|
+
out.push('-- [ALTERED] ' + t.name + (t.isDestructive ? ' [DESTRUCTIVE]' : ''));
|
|
216
|
+
t.droppedForeignKeys.forEach(function (f) {
|
|
217
|
+
if (dialect === 'sqlite') out.push('-- SQLite cannot drop constraints in place; rebuild ' + t.name + ' to remove FK ' + f.name);
|
|
218
|
+
else out.push(T + (dialect === 'mysql' ? 'DROP FOREIGN KEY ' : 'DROP CONSTRAINT ') + q(f.name) + ';');
|
|
219
|
+
});
|
|
220
|
+
t.droppedIndexes.forEach(function (i) { out.push(dropIdx(t.name, i)); });
|
|
221
|
+
t.addedColumns.forEach(function (c) { out.push(T + 'ADD COLUMN ' + colDef(c) + ';'); });
|
|
222
|
+
t.alteredColumns.forEach(function (ch) {
|
|
223
|
+
var c = ch.after, f = ch.changedFields;
|
|
224
|
+
if (dialect === 'mysql') { out.push(T + 'MODIFY COLUMN ' + colDef(c) + ';'); return; }
|
|
225
|
+
if (dialect === 'sqlite') { out.push('-- SQLite cannot ALTER COLUMN ' + t.name + '.' + c.name + ' (' + f.join(', ') + '); a table rebuild is required.'); return; }
|
|
226
|
+
if (f.indexOf('type') !== -1) out.push(T + 'ALTER COLUMN ' + q(c.name) + ' TYPE ' + c.type + ' USING ' + q(c.name) + '::' + c.type + ';');
|
|
227
|
+
if (f.indexOf('nullable') !== -1) out.push(T + 'ALTER COLUMN ' + q(c.name) + (c.nullable ? ' DROP NOT NULL;' : ' SET NOT NULL;'));
|
|
228
|
+
if (f.indexOf('default') !== -1) out.push(T + 'ALTER COLUMN ' + q(c.name) + (c.default == null ? ' DROP DEFAULT;' : ' SET DEFAULT ' + c.default + ';'));
|
|
229
|
+
if (f.indexOf('primaryKey') !== -1) out.push('-- Primary key membership changed for ' + t.name + '.' + c.name + '; recreate the PRIMARY KEY constraint manually.');
|
|
230
|
+
});
|
|
231
|
+
t.droppedColumns.forEach(function (c) { out.push('-- [DESTRUCTIVE] drops column data'); out.push(T + 'DROP COLUMN ' + q(c.name) + ';'); });
|
|
232
|
+
t.addedIndexes.forEach(function (i) { out.push(idxDef(t.name, i)); });
|
|
233
|
+
t.addedForeignKeys.forEach(function (f) {
|
|
234
|
+
if (dialect === 'sqlite') out.push('-- SQLite cannot add constraints in place; rebuild ' + t.name + ' to add FK ' + f.column + ' → ' + f.referencedTable);
|
|
235
|
+
else out.push(T + 'ADD ' + fkDef(f) + ';');
|
|
236
|
+
});
|
|
237
|
+
out.push('');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
d.droppedTables.forEach(function (t) {
|
|
241
|
+
out.push('-- [DESTRUCTIVE: DROPPED] ' + t.name + ': permanently deletes the table and all rows');
|
|
242
|
+
out.push('DROP TABLE ' + q(t.name) + ';');
|
|
243
|
+
out.push('');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (!d.addedTables.length && !d.alteredTables.length && !d.droppedTables.length) out.push('-- No schema drift. Nothing to migrate.');
|
|
247
|
+
return out.join('\n').replace(/\n+$/, '\n');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function countStatements(text, lang) {
|
|
251
|
+
if (!text) return 0;
|
|
252
|
+
var comment = lang === 'js' ? /^\s*\/\// : /^\s*--/;
|
|
253
|
+
return text.split('\n').filter(function (l) { return /;\s*$/.test(l) && !comment.test(l); }).length;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
var JS_TOKEN = /(\/\/[^\n]*)|("(?:[^"\\\n]|\\.)*")|\b(db|getCollection|createCollection|createIndex|dropIndex|updateMany|drop|true|false|null)\b|(\$[A-Za-z]+)/g;
|
|
257
|
+
var JSON_TOKEN = /("(?:[^"\\\n]|\\.)*")(\s*:)?|\b(true|false|null)\b|(-?\d+(?:\.\d+)?)/g;
|
|
258
|
+
|
|
259
|
+
function highlightScript(text, lang) {
|
|
260
|
+
if (lang === 'sql') return highlightSql(text);
|
|
261
|
+
var html = esc(text).replace(/"/g, '"').replace(/'/g, "'");
|
|
262
|
+
if (lang === 'js') {
|
|
263
|
+
return html.replace(JS_TOKEN, function (m, comment, str, kw, op) {
|
|
264
|
+
if (comment) return '<span class="' + (/DESTRUCTIVE/.test(comment) ? 'd' : 'c') + '">' + comment + '</span>';
|
|
265
|
+
if (str) return '<span class="s">' + str + '</span>';
|
|
266
|
+
if (kw) return '<span class="k">' + kw + '</span>';
|
|
267
|
+
return '<span class="o">' + op + '</span>';
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return html.replace(JSON_TOKEN, function (m, str, colon, lit, num) {
|
|
271
|
+
if (str) return colon ? '<span class="p">' + str + '</span>' + colon : '<span class="' + (/deleteField|deleteCollection/.test(str) ? 'd' : 's') + '">' + str + '</span>';
|
|
272
|
+
if (lit) return '<span class="' + (lit === 'true' ? 'n' : 'k') + '">' + lit + '</span>';
|
|
273
|
+
return '<span class="n">' + num + '</span>';
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
var LANG_LABEL = { sql: 'SQL DDL', js: 'MongoDB Shell', json: 'Firestore Index JSON' };
|
|
278
|
+
|
|
279
|
+
var SQL_TOKEN = /(--[^\n]*)|('(?:[^']|'')*')|\b(CREATE|TABLE|ALTER|ADD|DROP|COLUMN|CONSTRAINT|PRIMARY|FOREIGN|KEY|REFERENCES|ON|DELETE|INDEX|UNIQUE|NOT|NULL|DEFAULT|SET|TYPE|USING|MODIFY|CASCADE|RESTRICT|NO|ACTION)\b/g;
|
|
280
|
+
|
|
281
|
+
function highlightSql(sql) {
|
|
282
|
+
return esc(sql).replace(/'/g, "'").replace(SQL_TOKEN, function (m, comment, str, kw) {
|
|
283
|
+
if (comment) return '<span class="' + (/DESTRUCTIVE/.test(comment) ? 'd' : 'c') + '">' + comment + '</span>';
|
|
284
|
+
if (str) return '<span class="s">' + str + '</span>';
|
|
285
|
+
return '<span class="k">' + kw + '</span>';
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function renderSql() {
|
|
290
|
+
if (!state.diff) return emptyState('No migration to preview', state.diffError || 'Connect Dev and Prod to generate a migration script.');
|
|
291
|
+
var destructive = state.diff.summary.hasDestructiveChanges;
|
|
292
|
+
var lang = state.scriptLang;
|
|
293
|
+
var unit = lang === 'json' ? ' change(s)' : ' statement(s)';
|
|
294
|
+
return '<div class="sql-wrap card"><div class="sql-head"><span class="muted"><span class="lang">' + LANG_LABEL[lang] + '</span>' +
|
|
295
|
+
(destructive ? '<span class="badge badge-dropped">DESTRUCTIVE</span> ' : '') + state.scriptCount + unit + ' · read-only preview</span>' +
|
|
296
|
+
'<span class="copy-wrap"><span class="tooltip">Copied!</span><button class="btn small" type="button" data-action="copy-sql">Copy ' +
|
|
297
|
+
(lang === 'sql' ? 'SQL' : lang === 'js' ? 'Script' : 'JSON') + '</button></span></div>' +
|
|
298
|
+
'<pre class="sql" tabindex="0" aria-label="Migration script (' + LANG_LABEL[lang] + ')"><code>' + highlightScript(state.sql, lang) + '</code></pre></div>';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const databaseEventsScript: string;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Database Studio event wiring.
|
|
2
|
+
export const databaseEventsScript = String.raw ` // ─── Events: Database Studio ───────────────────────────────────────────────
|
|
3
|
+
function setDbTab(tab, focus) {
|
|
4
|
+
state.tab = tab;
|
|
5
|
+
renderDb();
|
|
6
|
+
if (focus) $('tab-' + tab).focus();
|
|
7
|
+
if (tab === 'data' && (!state.dataResult || state.dataResult.entity !== state.dataEntity)) {
|
|
8
|
+
fetchDataRecords();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
document.querySelectorAll('.subtab').forEach(function (t) {
|
|
13
|
+
t.addEventListener('click', function () { setDbTab(t.getAttribute('data-tab')); });
|
|
14
|
+
t.addEventListener('keydown', function (e) {
|
|
15
|
+
var order = ['explorer', 'data', 'drift', 'sql'];
|
|
16
|
+
var i = order.indexOf(state.tab);
|
|
17
|
+
if (e.key === 'ArrowRight') { e.preventDefault(); setDbTab(order[(i + 1) % 4], true); }
|
|
18
|
+
else if (e.key === 'ArrowLeft') { e.preventDefault(); setDbTab(order[(i + 3) % 4], true); }
|
|
19
|
+
else if (e.key === 'Home') { e.preventDefault(); setDbTab('explorer', true); }
|
|
20
|
+
else if (e.key === 'End') { e.preventDefault(); setDbTab('sql', true); }
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
document.querySelectorAll('.env-seg-btn').forEach(function (btn) {
|
|
25
|
+
btn.addEventListener('click', function () {
|
|
26
|
+
var mode = btn.getAttribute('data-env-mode');
|
|
27
|
+
if (mode) {
|
|
28
|
+
state.envMode = mode;
|
|
29
|
+
if (mode === 'prod') state.dataEnv = 'prod';
|
|
30
|
+
else if (mode === 'dev') state.dataEnv = 'dev';
|
|
31
|
+
state.dataResult = null;
|
|
32
|
+
renderDb();
|
|
33
|
+
if (state.tab === 'data') fetchDataRecords();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
$('btn-settings').addEventListener('click', openSettings);
|
|
39
|
+
$('btn-export').addEventListener('click', function () {
|
|
40
|
+
var btn = $('btn-export');
|
|
41
|
+
var st = state.status || {};
|
|
42
|
+
var sourceEnv = st.dev && st.dev.connected ? 'dev' : (st.prod && st.prod.connected ? 'prod' : 'dev');
|
|
43
|
+
if (!window.confirm('Export the ' + sourceEnv.toUpperCase() + ' schema (structure only, no credentials or data) to .ai/db_schema.json?\nThis overwrites the tables in the current contract.')) return;
|
|
44
|
+
btn.disabled = true;
|
|
45
|
+
postJson('/api/export-contract', { sourceEnv: sourceEnv }).then(function (r) {
|
|
46
|
+
var n = r.exportedEntitiesCount != null ? r.exportedEntitiesCount : r.exportedTablesCount;
|
|
47
|
+
toast('Exported ' + n + ' ' + entityNoun(st[sourceEnv], true).toLowerCase() + ' to ' + r.filePath, 'ok');
|
|
48
|
+
if (state.diffTarget === 'contract') load(true);
|
|
49
|
+
}, function (e) { toast('Export failed: ' + e.message); }).then(function () { btn.disabled = false; });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
$('view').addEventListener('input', function (e) {
|
|
53
|
+
if (e.target && e.target.id === 'search') {
|
|
54
|
+
state.search = e.target.value;
|
|
55
|
+
var pos = e.target.selectionStart;
|
|
56
|
+
renderDb();
|
|
57
|
+
var s = $('search');
|
|
58
|
+
if (s) { s.focus(); try { s.setSelectionRange(pos, pos); } catch (err) { /* ignore */ } }
|
|
59
|
+
}
|
|
60
|
+
if (e.target && e.target.id === 'data-search') {
|
|
61
|
+
state.dataSearch = e.target.value;
|
|
62
|
+
clearTimeout(state._dataSearchTimeout);
|
|
63
|
+
state._dataSearchTimeout = setTimeout(function () {
|
|
64
|
+
state.dataPage = 1;
|
|
65
|
+
fetchDataRecords();
|
|
66
|
+
}, 350);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
$('view').addEventListener('change', function (e) {
|
|
71
|
+
if (e.target && e.target.id === 'diff-target') { state.diffTarget = e.target.value; load(false); }
|
|
72
|
+
if (e.target && e.target.id === 'data-entity-select') {
|
|
73
|
+
state.dataEntity = e.target.value;
|
|
74
|
+
state.dataPage = 1;
|
|
75
|
+
state.dataSort = '';
|
|
76
|
+
state.dataSearch = '';
|
|
77
|
+
state.dataResult = null;
|
|
78
|
+
fetchDataRecords();
|
|
79
|
+
}
|
|
80
|
+
if (e.target && e.target.id === 'data-limit-select') {
|
|
81
|
+
state.dataLimit = Number(e.target.value);
|
|
82
|
+
state.dataPage = 1;
|
|
83
|
+
fetchDataRecords();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
$('view').addEventListener('mouseover', function (e) {
|
|
88
|
+
var el = e.target.closest && e.target.closest('[data-media]');
|
|
89
|
+
if (el) showThumb(el);
|
|
90
|
+
});
|
|
91
|
+
$('view').addEventListener('mouseout', function (e) {
|
|
92
|
+
var el = e.target.closest && e.target.closest('[data-media]');
|
|
93
|
+
if (el && !el.contains(e.relatedTarget)) hideThumb();
|
|
94
|
+
});
|
|
95
|
+
$('view').addEventListener('focusin', function (e) {
|
|
96
|
+
if (e.target.hasAttribute && e.target.hasAttribute('data-media')) showThumb(e.target);
|
|
97
|
+
});
|
|
98
|
+
$('view').addEventListener('focusout', hideThumb);
|
|
99
|
+
window.addEventListener('scroll', hideThumb, { passive: true });
|
|
100
|
+
|
|
101
|
+
$('view').addEventListener('click', function (e) {
|
|
102
|
+
var sortTh = e.target.closest('th.sortable');
|
|
103
|
+
if (sortTh) {
|
|
104
|
+
var col = sortTh.getAttribute('data-sort-col');
|
|
105
|
+
if (col) {
|
|
106
|
+
if (state.dataSort === col) {
|
|
107
|
+
state.dataOrder = state.dataOrder === 'asc' ? 'desc' : 'asc';
|
|
108
|
+
} else {
|
|
109
|
+
state.dataSort = col;
|
|
110
|
+
state.dataOrder = 'asc';
|
|
111
|
+
}
|
|
112
|
+
fetchDataRecords();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
var el = e.target.closest('button');
|
|
118
|
+
if (!el) return;
|
|
119
|
+
if (el.hasAttribute('data-media')) {
|
|
120
|
+
// Keep the <details> card from toggling when the badge sits in a summary row.
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
openMedia(Number(el.getAttribute('data-media')));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
var action = el.getAttribute('data-action');
|
|
126
|
+
if (action === 'open-settings') openSettings();
|
|
127
|
+
else if (action === 'copy-sql') copyText(state.sql, el);
|
|
128
|
+
else if (action === 'edit-record') openEditRecordModal(Number(el.getAttribute('data-row')));
|
|
129
|
+
else if (action === 'delete-record') handleDeleteRecord(Number(el.getAttribute('data-row')));
|
|
130
|
+
else if (action === 'preview-url') {
|
|
131
|
+
var url = el.getAttribute('data-url');
|
|
132
|
+
$('media-title').textContent = 'Image Preview';
|
|
133
|
+
$('media-sub').textContent = url;
|
|
134
|
+
$('media-thumb').innerHTML = '<img src="' + esc(url) + '" style="max-width:100%;max-height:300px">';
|
|
135
|
+
$('media-details').innerHTML = '<dt>Source URL</dt><dd><code>' + esc(url) + '</code></dd>';
|
|
136
|
+
openDialog($('media-dialog'));
|
|
137
|
+
}
|
|
138
|
+
else if (action === 'view-json') {
|
|
139
|
+
var raw = el.getAttribute('data-raw');
|
|
140
|
+
toast(raw);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (el.id === 'btn-insert-record') openInsertRecordModal();
|
|
144
|
+
else if (el.id === 'btn-data-refresh') fetchDataRecords();
|
|
145
|
+
else if (el.id === 'btn-page-prev') {
|
|
146
|
+
state.dataPage = Math.max(1, state.dataPage - 1);
|
|
147
|
+
fetchDataRecords();
|
|
148
|
+
}
|
|
149
|
+
else if (el.id === 'btn-page-next') {
|
|
150
|
+
state.dataPage++;
|
|
151
|
+
fetchDataRecords();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
var envToggle = el.getAttribute('data-env-toggle');
|
|
155
|
+
if (envToggle) { state.mobileEnv = envToggle; renderDb(); }
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
$('btn-save-record').addEventListener('click', handleSaveRecordSubmit);
|
|
159
|
+
|
|
160
|
+
$('challenge-input').addEventListener('input', function (e) {
|
|
161
|
+
var val = e.target.value.trim();
|
|
162
|
+
var entity = state.pendingMutation ? state.pendingMutation.entity : '';
|
|
163
|
+
$('btn-confirm-challenge').disabled = (val !== entity);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
$('btn-confirm-challenge').addEventListener('click', function () {
|
|
167
|
+
if (!state.pendingMutation) return;
|
|
168
|
+
var m = state.pendingMutation;
|
|
169
|
+
var inputVal = $('challenge-input').value.trim();
|
|
170
|
+
if (inputVal !== m.entity) return;
|
|
171
|
+
executeMutationDirect(m.action, m.entity, m.pk, m.payload, true, inputVal);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
$('conn-form').addEventListener('click', function (e) {
|
|
175
|
+
var el = e.target.closest('button');
|
|
176
|
+
if (!el) return;
|
|
177
|
+
var toggle = el.getAttribute('data-toggle');
|
|
178
|
+
if (toggle) {
|
|
179
|
+
var input = $('url-' + toggle);
|
|
180
|
+
var show = input.type === 'password';
|
|
181
|
+
input.type = show ? 'text' : 'password';
|
|
182
|
+
el.textContent = show ? 'Hide' : 'Show';
|
|
183
|
+
el.setAttribute('aria-pressed', String(show));
|
|
184
|
+
}
|
|
185
|
+
var pingEnv = el.getAttribute('data-ping');
|
|
186
|
+
if (pingEnv) {
|
|
187
|
+
el.disabled = true;
|
|
188
|
+
ping(pingEnv).then(function (ok) { el.disabled = false; if (ok) load(false).then(renderModalStatus); });
|
|
189
|
+
}
|
|
190
|
+
var copyEnv = el.getAttribute('data-copy-masked');
|
|
191
|
+
if (copyEnv && state.status && state.status[copyEnv]) copyText(state.status[copyEnv].maskedUrl, el);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
['dev', 'prod'].forEach(function (env) {
|
|
195
|
+
$('engine-' + env).addEventListener('change', function () { setEngineMode(env); });
|
|
196
|
+
$('pengine-' + env).addEventListener('change', function () {
|
|
197
|
+
var port = $('pport-' + env);
|
|
198
|
+
// Swap the port only when it still holds the other engine's default.
|
|
199
|
+
if (!port.value.trim() || port.value.trim() === DEFAULT_PORT.mysql || port.value.trim() === DEFAULT_PORT.postgresql) {
|
|
200
|
+
port.value = DEFAULT_PORT[$('pengine-' + env).value];
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
document.querySelector('[data-xampp="' + env + '"]').addEventListener('click', function () { applyXamppDefaults(env); });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
$('btn-save-conn').addEventListener('click', function () {
|
|
207
|
+
var envs = ['dev', 'prod'].filter(hasInput);
|
|
208
|
+
if (!envs.length) { $('conn-dialog').close(); return; }
|
|
209
|
+
var btn = $('btn-save-conn');
|
|
210
|
+
btn.disabled = true;
|
|
211
|
+
Promise.all(envs.map(ping)).then(function (results) {
|
|
212
|
+
btn.disabled = false;
|
|
213
|
+
load(false).then(renderModalStatus);
|
|
214
|
+
if (results.every(Boolean)) { $('conn-dialog').close(); toast('Connections saved to in-memory session', 'ok'); }
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Clear any typed secrets whenever the modal closes.
|
|
219
|
+
$('conn-dialog').addEventListener('close', function () {
|
|
220
|
+
['dev', 'prod'].forEach(function (env) {
|
|
221
|
+
var input = $('url-' + env);
|
|
222
|
+
input.value = ''; input.type = 'password';
|
|
223
|
+
$('fsproject-' + env).value = '';
|
|
224
|
+
$('fsemu-' + env).value = '';
|
|
225
|
+
PARAM_FIELDS.forEach(function (f) { $(f + '-' + env).value = ''; });
|
|
226
|
+
var t = document.querySelector('[data-toggle="' + env + '"]');
|
|
227
|
+
if (t) { t.textContent = 'Show'; t.setAttribute('aria-pressed', 'false'); }
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const databaseExplorerScript: string;
|