@njeybe/nativ 2.1.0 → 2.3.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.
Files changed (144) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README.md +17 -0
  3. package/dist/server/studio-ui/document.d.ts +3 -0
  4. package/dist/server/studio-ui/document.js +22 -0
  5. package/dist/server/studio-ui/pages/benchmarks/index.d.ts +2 -0
  6. package/dist/server/studio-ui/pages/benchmarks/index.js +11 -0
  7. package/dist/server/studio-ui/pages/benchmarks/script.d.ts +1 -0
  8. package/dist/server/studio-ui/pages/benchmarks/script.js +104 -0
  9. package/dist/server/studio-ui/pages/benchmarks/styles.d.ts +1 -0
  10. package/dist/server/studio-ui/pages/benchmarks/styles.js +30 -0
  11. package/dist/server/studio-ui/pages/database/browser.d.ts +1 -0
  12. package/dist/server/studio-ui/pages/database/browser.js +326 -0
  13. package/dist/server/studio-ui/pages/database/connection.d.ts +1 -0
  14. package/dist/server/studio-ui/pages/database/connection.js +158 -0
  15. package/dist/server/studio-ui/pages/database/data-shell.d.ts +1 -0
  16. package/dist/server/studio-ui/pages/database/data-shell.js +165 -0
  17. package/dist/server/studio-ui/pages/database/drift.d.ts +1 -0
  18. package/dist/server/studio-ui/pages/database/drift.js +301 -0
  19. package/dist/server/studio-ui/pages/database/events.d.ts +1 -0
  20. package/dist/server/studio-ui/pages/database/events.js +231 -0
  21. package/dist/server/studio-ui/pages/database/explorer.d.ts +1 -0
  22. package/dist/server/studio-ui/pages/database/explorer.js +183 -0
  23. package/dist/server/studio-ui/pages/database/index.d.ts +2 -0
  24. package/dist/server/studio-ui/pages/database/index.js +28 -0
  25. package/dist/server/studio-ui/pages/database/markup.d.ts +1 -0
  26. package/dist/server/studio-ui/pages/database/markup.js +32 -0
  27. package/dist/server/studio-ui/pages/database/state.d.ts +2 -0
  28. package/dist/server/studio-ui/pages/database/state.js +57 -0
  29. package/dist/server/studio-ui/pages/database/styles.d.ts +1 -0
  30. package/dist/server/studio-ui/pages/database/styles.js +118 -0
  31. package/dist/server/studio-ui/pages/flow/index.d.ts +2 -0
  32. package/dist/server/studio-ui/pages/flow/index.js +11 -0
  33. package/dist/server/studio-ui/pages/flow/layout.d.ts +1 -0
  34. package/dist/server/studio-ui/pages/flow/layout.js +105 -0
  35. package/dist/server/studio-ui/pages/flow/plan-view.d.ts +1 -0
  36. package/dist/server/studio-ui/pages/flow/plan-view.js +67 -0
  37. package/dist/server/studio-ui/pages/flow/script.d.ts +1 -0
  38. package/dist/server/studio-ui/pages/flow/script.js +61 -0
  39. package/dist/server/studio-ui/pages/flow/styles.d.ts +1 -0
  40. package/dist/server/studio-ui/pages/flow/styles.js +59 -0
  41. package/dist/server/studio-ui/pages/flow/timeline-model.d.ts +1 -0
  42. package/dist/server/studio-ui/pages/flow/timeline-model.js +95 -0
  43. package/dist/server/studio-ui/pages/flow/timeline-view.d.ts +1 -0
  44. package/dist/server/studio-ui/pages/flow/timeline-view.js +92 -0
  45. package/dist/server/studio-ui/pages/home/index.d.ts +2 -0
  46. package/dist/server/studio-ui/pages/home/index.js +11 -0
  47. package/dist/server/studio-ui/pages/home/script.d.ts +1 -0
  48. package/dist/server/studio-ui/pages/home/script.js +160 -0
  49. package/dist/server/studio-ui/pages/home/styles.d.ts +1 -0
  50. package/dist/server/studio-ui/pages/home/styles.js +48 -0
  51. package/dist/server/studio-ui/pages/index.d.ts +2 -0
  52. package/dist/server/studio-ui/pages/index.js +17 -0
  53. package/dist/server/studio-ui/pages/tasks/index.d.ts +2 -0
  54. package/dist/server/studio-ui/pages/tasks/index.js +11 -0
  55. package/dist/server/studio-ui/pages/tasks/script.d.ts +1 -0
  56. package/dist/server/studio-ui/pages/tasks/script.js +96 -0
  57. package/dist/server/studio-ui/pages/tasks/styles.d.ts +1 -0
  58. package/dist/server/studio-ui/pages/tasks/styles.js +37 -0
  59. package/dist/server/studio-ui/pages/team/index.d.ts +2 -0
  60. package/dist/server/studio-ui/pages/team/index.js +11 -0
  61. package/dist/server/studio-ui/pages/team/script.d.ts +1 -0
  62. package/dist/server/studio-ui/pages/team/script.js +112 -0
  63. package/dist/server/studio-ui/pages/team/styles.d.ts +1 -0
  64. package/dist/server/studio-ui/pages/team/styles.js +22 -0
  65. package/dist/server/studio-ui/pages/types.d.ts +13 -0
  66. package/dist/server/studio-ui/pages/types.js +1 -0
  67. package/dist/server/studio-ui/pages/worktrees/index.d.ts +2 -0
  68. package/dist/server/studio-ui/pages/worktrees/index.js +11 -0
  69. package/dist/server/studio-ui/pages/worktrees/script.d.ts +1 -0
  70. package/dist/server/studio-ui/pages/worktrees/script.js +104 -0
  71. package/dist/server/studio-ui/pages/worktrees/styles.d.ts +1 -0
  72. package/dist/server/studio-ui/pages/worktrees/styles.js +15 -0
  73. package/dist/server/studio-ui/shell/actions.d.ts +1 -0
  74. package/dist/server/studio-ui/shell/actions.js +49 -0
  75. package/dist/server/studio-ui/shell/boot.d.ts +2 -0
  76. package/dist/server/studio-ui/shell/boot.js +13 -0
  77. package/dist/server/studio-ui/shell/console.d.ts +1 -0
  78. package/dist/server/studio-ui/shell/console.js +194 -0
  79. package/dist/server/studio-ui/shell/core.d.ts +1 -0
  80. package/dist/server/studio-ui/shell/core.js +50 -0
  81. package/dist/server/studio-ui/shell/detail-view.d.ts +1 -0
  82. package/dist/server/studio-ui/shell/detail-view.js +80 -0
  83. package/dist/server/studio-ui/shell/detail.d.ts +1 -0
  84. package/dist/server/studio-ui/shell/detail.js +83 -0
  85. package/dist/server/studio-ui/shell/dispatch.d.ts +1 -0
  86. package/dist/server/studio-ui/shell/dispatch.js +156 -0
  87. package/dist/server/studio-ui/shell/events-tools.d.ts +1 -0
  88. package/dist/server/studio-ui/shell/events-tools.js +94 -0
  89. package/dist/server/studio-ui/shell/format.d.ts +1 -0
  90. package/dist/server/studio-ui/shell/format.js +151 -0
  91. package/dist/server/studio-ui/shell/icons.d.ts +2 -0
  92. package/dist/server/studio-ui/shell/icons.js +20 -0
  93. package/dist/server/studio-ui/shell/markup-head.d.ts +1 -0
  94. package/dist/server/studio-ui/shell/markup-head.js +43 -0
  95. package/dist/server/studio-ui/shell/markup-panels.d.ts +2 -0
  96. package/dist/server/studio-ui/shell/markup-panels.js +7 -0
  97. package/dist/server/studio-ui/shell/markup-tools.d.ts +1 -0
  98. package/dist/server/studio-ui/shell/markup-tools.js +222 -0
  99. package/dist/server/studio-ui/shell/model.d.ts +1 -0
  100. package/dist/server/studio-ui/shell/model.js +192 -0
  101. package/dist/server/studio-ui/shell/paint.d.ts +1 -0
  102. package/dist/server/studio-ui/shell/paint.js +154 -0
  103. package/dist/server/studio-ui/shell/palette-view.d.ts +1 -0
  104. package/dist/server/studio-ui/shell/palette-view.js +60 -0
  105. package/dist/server/studio-ui/shell/palette.d.ts +1 -0
  106. package/dist/server/studio-ui/shell/palette.js +80 -0
  107. package/dist/server/studio-ui/shell/pipeline-state.d.ts +1 -0
  108. package/dist/server/studio-ui/shell/pipeline-state.js +152 -0
  109. package/dist/server/studio-ui/shell/proposals.d.ts +1 -0
  110. package/dist/server/studio-ui/shell/proposals.js +197 -0
  111. package/dist/server/studio-ui/shell/pure.d.ts +3 -0
  112. package/dist/server/studio-ui/shell/pure.js +9 -0
  113. package/dist/server/studio-ui/shell/router.d.ts +1 -0
  114. package/dist/server/studio-ui/shell/router.js +95 -0
  115. package/dist/server/studio-ui/shell/sidebar-view.d.ts +1 -0
  116. package/dist/server/studio-ui/shell/sidebar-view.js +38 -0
  117. package/dist/server/studio-ui/shell/sync.d.ts +1 -0
  118. package/dist/server/studio-ui/shell/sync.js +120 -0
  119. package/dist/server/studio-ui/shell/toasts-format.d.ts +1 -0
  120. package/dist/server/studio-ui/shell/toasts-format.js +89 -0
  121. package/dist/server/studio-ui/shell/triage.d.ts +1 -0
  122. package/dist/server/studio-ui/shell/triage.js +210 -0
  123. package/dist/server/studio-ui/styles/console.d.ts +1 -0
  124. package/dist/server/studio-ui/styles/console.js +83 -0
  125. package/dist/server/studio-ui/styles/dialogs.d.ts +1 -0
  126. package/dist/server/studio-ui/styles/dialogs.js +118 -0
  127. package/dist/server/studio-ui/styles/responsive.d.ts +1 -0
  128. package/dist/server/studio-ui/styles/responsive.js +28 -0
  129. package/dist/server/studio-ui/styles/shell.d.ts +1 -0
  130. package/dist/server/studio-ui/styles/shell.js +117 -0
  131. package/dist/server/studio-ui/styles/strategist.d.ts +1 -0
  132. package/dist/server/studio-ui/styles/strategist.js +77 -0
  133. package/dist/server/studio-ui/styles/tokens-base.d.ts +1 -0
  134. package/dist/server/studio-ui/styles/tokens-base.js +131 -0
  135. package/dist/server/studio-ui.d.ts +1 -10
  136. package/dist/server/studio-ui.js +63 -5314
  137. package/package.json +2 -2
  138. package/plugin/.claude-plugin/plugin.json +1 -1
  139. package/plugin/agents/architect.md +9 -5
  140. package/templates/GEMINI.md +12 -8
  141. package/templates/claude-agents/architect.md +9 -5
  142. package/templates/dot-ai/subagents/flutter-developer.md +10 -0
  143. package/templates/dot-ai/subagents/frontend.md +10 -0
  144. package/templates/dot-ai/ui_specs.md +36 -6
@@ -0,0 +1,158 @@
1
+ // Database Studio connection modal.
2
+ export const databaseConnectionScript = String.raw ` // ─── Database Studio: connection modal ─────────────────────────────────────
3
+ function renderModalStatus() {
4
+ ['dev', 'prod'].forEach(function (env) {
5
+ var s = state.status && state.status[env];
6
+ $('mdot-' + env).className = 'dot ' + (s && s.connected ? 'on' : 'off');
7
+ $('current-' + env).innerHTML = s && s.maskedUrl
8
+ ? 'Current: <code>' + esc(s.maskedUrl) + '</code><span class="copy-wrap"><span class="tooltip">Copied!</span><button class="btn small" type="button" data-copy-masked="' + env + '">Copy</button></span>'
9
+ : 'Current: <span class="muted">not configured</span>';
10
+ });
11
+ }
12
+
13
+ function renderTemplateChips() {
14
+ var host = $('conn-template-chips');
15
+ if (!host) return;
16
+ if (!state.envInfo || !state.envInfo.templateFound || !state.envInfo.detectedKeys || state.envInfo.detectedKeys.length === 0) {
17
+ host.innerHTML = '';
18
+ host.hidden = true;
19
+ return;
20
+ }
21
+ var html = '<div class="template-helper-box">' +
22
+ '<div class="template-helper-label">Discovered in <code>' + esc(state.envInfo.templateFile || '.env.example') + '</code>:</div>' +
23
+ '<div class="template-chips">';
24
+ state.envInfo.detectedKeys.forEach(function (dk) {
25
+ var eng = dk.engine ? ' (' + (ENGINE_LABEL[dk.engine] || dk.engine) + ')' : '';
26
+ var statusDot = dk.isConfiguredInEnv ? '<span class="chip-dot on"></span>' : '<span class="chip-dot off"></span>';
27
+ var title = dk.isConfiguredInEnv ? 'Configured in .env' : 'Missing in .env';
28
+ html += '<button type="button" class="btn small template-chip" data-template-key="' + esc(dk.key) + '" data-template-engine="' + esc(dk.engine || '') + '" data-template-env="' + esc(dk.targetEnv) + '" title="' + title + '">' +
29
+ statusDot + esc(dk.key) + eng +
30
+ '</button>';
31
+ });
32
+ html += '</div></div>';
33
+ host.innerHTML = html;
34
+ host.hidden = false;
35
+
36
+ host.querySelectorAll('.template-chip').forEach(function (btn) {
37
+ btn.onclick = function () {
38
+ var key = btn.getAttribute('data-template-key');
39
+ var eng = btn.getAttribute('data-template-engine');
40
+ var targetEnv = btn.getAttribute('data-template-env') || 'dev';
41
+ // Fragmented component keys (DB_HOST, DB_PORT, ...) map to the parameter form, not a URL.
42
+ var isComponent = /(^|_)(HOST|HOSTNAME|SERVER|PORT|USER|USERNAME|PASS|PASSWORD|DATABASE|NAME|CONNECTION|DRIVER)$/i.test(key) && eng !== 'mongodb' && eng !== 'firestore' && eng !== 'sqlite';
43
+ $('engine-' + targetEnv).value = eng === 'firestore' ? 'firestore' : isComponent ? 'form' : 'url';
44
+ setEngineMode(targetEnv);
45
+ if (isComponent) {
46
+ if (eng === 'mysql' || eng === 'postgresql') $('pengine-' + targetEnv).value = eng;
47
+ $('phost-' + targetEnv).focus();
48
+ } else {
49
+ $((eng === 'firestore' ? 'fsproject-' : 'url-') + targetEnv).focus();
50
+ }
51
+ toast('Selected ' + key + ' for ' + targetEnv.toUpperCase());
52
+ };
53
+ });
54
+ }
55
+
56
+ function openSettings() {
57
+ renderModalStatus();
58
+ renderTemplateChips();
59
+ ['dev', 'prod'].forEach(function (env) { $('result-' + env).textContent = ''; $('result-' + env).className = 'result'; });
60
+ openDialog($('conn-dialog'));
61
+ }
62
+
63
+ function isFirestoreMode(env) { return $('engine-' + env).value === 'firestore'; }
64
+ function isFormMode(env) { return $('engine-' + env).value === 'form'; }
65
+
66
+ var PARAM_FIELDS = ['phost', 'pport', 'puser', 'ppass', 'pdb'];
67
+ var DEFAULT_PORT = { mysql: '3306', postgresql: '5432' };
68
+
69
+ function setEngineMode(env) {
70
+ var fs = isFirestoreMode(env);
71
+ var form = isFormMode(env);
72
+ $('url-' + env).hidden = fs || form;
73
+ document.querySelector('[data-toggle="' + env + '"]').hidden = fs || form;
74
+ $('fsproject-' + env).hidden = !fs;
75
+ $('fsemu-' + env).hidden = !fs;
76
+ $('params-' + env).hidden = !form;
77
+ $('result-' + env).textContent = '';
78
+ }
79
+
80
+ /** Quick-fill for a stock XAMPP install: MySQL on 127.0.0.1:3306 as root with an empty password. */
81
+ function applyXamppDefaults(env) {
82
+ $('engine-' + env).value = 'form';
83
+ setEngineMode(env);
84
+ $('pengine-' + env).value = 'mysql';
85
+ $('phost-' + env).value = '127.0.0.1';
86
+ $('pport-' + env).value = '3306';
87
+ $('puser-' + env).value = 'root';
88
+ $('ppass-' + env).value = '';
89
+ $('pdb-' + env).focus();
90
+ }
91
+
92
+ function hasInput(env) {
93
+ if (isFirestoreMode(env)) return !!$('fsproject-' + env).value.trim();
94
+ if (isFormMode(env)) return !!$('phost-' + env).value.trim();
95
+ return !!$('url-' + env).value.trim();
96
+ }
97
+
98
+ /** Parameter form: sent as the contract's components payload; the server assembles the URL in memory. */
99
+ function componentsBody(env) {
100
+ var engine = $('pengine-' + env).value;
101
+ var host = $('phost-' + env).value.trim();
102
+ var port = $('pport-' + env).value.trim() || DEFAULT_PORT[engine];
103
+ if (!host) return { error: 'Enter a host, e.g. 127.0.0.1.' };
104
+ if (!/^[A-Za-z0-9._:\[\]-]{1,253}$/.test(host)) return { error: 'Host must be a hostname or IP address.' };
105
+ if (!/^\d{1,5}$/.test(port) || +port < 1 || +port > 65535) return { error: 'Port must be a number between 1 and 65535.' };
106
+ var components = { engine: engine, host: host, port: +port };
107
+ var user = $('puser-' + env).value.trim();
108
+ var password = $('ppass-' + env).value;
109
+ var database = $('pdb-' + env).value.trim();
110
+ if (user) components.user = user;
111
+ if (password) components.password = password;
112
+ if (database) components.database = database;
113
+ return { body: { env: env, engine: engine, components: components } };
114
+ }
115
+
116
+ /** Builds the /api/connect body. Firestore is sent as a firestore:// URL plus the contract's firestoreConfig. */
117
+ function connectBody(env) {
118
+ if (isFormMode(env)) return componentsBody(env);
119
+ if (!isFirestoreMode(env)) {
120
+ var url = $('url-' + env).value.trim();
121
+ return url ? { body: { env: env, connectionUrl: url } } : { error: 'Enter a connection string first.' };
122
+ }
123
+ var projectId = $('fsproject-' + env).value.trim();
124
+ var emulatorHost = $('fsemu-' + env).value.trim();
125
+ if (!/^[a-z0-9][a-z0-9-]{2,62}$/.test(projectId)) return { error: 'Enter a valid Firestore project ID (lowercase letters, digits, hyphens).' };
126
+ if (emulatorHost && !/^[A-Za-z0-9.\-\[\]:]+:\d{2,5}$/.test(emulatorHost)) return { error: 'Emulator host must look like localhost:8080.' };
127
+ var firestoreUrl = 'firestore://' + encodeURIComponent(projectId) + (emulatorHost ? '?emulator=' + emulatorHost : '');
128
+ var config = { projectId: projectId };
129
+ if (emulatorHost) config.emulatorHost = emulatorHost;
130
+ return { body: { env: env, engine: 'firestore', connectionUrl: firestoreUrl, firestoreConfig: config } };
131
+ }
132
+
133
+ function ping(env) {
134
+ var out = $('result-' + env);
135
+ var req = connectBody(env);
136
+ if (req.error) { out.className = 'result err'; out.textContent = req.error; return Promise.resolve(false); }
137
+ out.className = 'result'; out.textContent = 'Testing connection…';
138
+ return postJson('/api/connect', req.body).then(function (r) {
139
+ if (r.success) {
140
+ var count = r.entityCount != null ? r.entityCount : r.tableCount;
141
+ out.className = 'result ok';
142
+ out.textContent = 'Connected to ' + (ENGINE_LABEL[r.engine] || r.engine) + ' in ' + Math.round(r.pingMs) + 'ms · ' + count + ' ' +
143
+ entityNoun({ engine: r.engine }, true).toLowerCase() + ' · saved to session as ' + r.maskedUrl;
144
+ $('url-' + env).value = '';
145
+ $('ppass-' + env).value = '';
146
+ return true;
147
+ }
148
+ out.className = 'result err';
149
+ out.textContent = 'Error: ' + (r.error || 'Connection failed');
150
+ var hint = hintFor(r.error);
151
+ if (hint) out.textContent += ' — ' + hint;
152
+ return false;
153
+ }, function (e) {
154
+ out.className = 'result err'; out.textContent = 'Error: ' + e.message; return false;
155
+ });
156
+ }
157
+
158
+ `;
@@ -0,0 +1 @@
1
+ export declare const databaseDataScript: string;
@@ -0,0 +1,165 @@
1
+ // Database Studio data loading and shell.
2
+ export const databaseDataScript = String.raw ` // ─── View 5: Database Studio — data loading ────────────────────────────────
3
+ function load(fresh) {
4
+ state.loading = true;
5
+ renderDb();
6
+ var q = fresh ? '?refresh=1' : '';
7
+ var diffPath = '/api/diff?target=' + state.diffTarget + (fresh ? '&refresh=1' : '');
8
+ return Promise.all([
9
+ api('/api/status' + q).catch(function (e) { toast(e.message); return null; }),
10
+ api('/api/schema' + q).catch(function (e) { toast(e.message); return { devTables: [], prodTables: [] }; }),
11
+ api(diffPath).then(function (d) { state.diffError = null; return d; }, function (e) { state.diffError = e.message; return null; }),
12
+ api('/api/env-info').catch(function () { return null; })
13
+ ]).then(function (results) {
14
+ state.status = results[0];
15
+ state.schema = results[1];
16
+ state.diff = results[2];
17
+ state.envInfo = results[3];
18
+ state.diffTargetUsed = state.diffTarget;
19
+ state.loading = false;
20
+ if (state.status) {
21
+ ['dev', 'prod'].forEach(function (env) {
22
+ var s = state.status[env];
23
+ if (s && !s.connected && s.error && !/No (Dev|Prod) database configured/.test(s.error)) {
24
+ toast((env === 'dev' ? 'Dev' : 'Prod') + ' connection failed: ' + s.error);
25
+ }
26
+ });
27
+ }
28
+ renderDb();
29
+ });
30
+ }
31
+
32
+ // ─── Database Studio: shell ────────────────────────────────────────────────
33
+ /** DB_HOST -> DB_*, PROD_DB_HOST -> PROD_DB_*, PGHOST -> PG*. */
34
+ function keyFamily(key) {
35
+ var m = /^(.*_)[^_]*$/.exec(key);
36
+ return m ? m[1] + '*' : key.slice(0, 2) + '*';
37
+ }
38
+
39
+ /** Local-stack hint when fragmented DB_* keys resolve to a server that refuses connections. */
40
+ function fragmentedDiagnostic(s) {
41
+ var cfg = state.envInfo && state.envInfo.fragmentedConfig;
42
+ if (!s || s.connected || !s.synthesized || !cfg) return '';
43
+ if (!/ECONNREFUSED|ETIMEDOUT|EHOSTUNREACH/i.test(s.error || '')) return '';
44
+ var keys = cfg.sourceKeys.filter(function (k) { return /HOST|DATABASE|_DB$|DB_NAME$/.test(k); });
45
+ var label = ENGINE_LABEL[cfg.engine] || cfg.engine;
46
+ var action = cfg.engine === 'mysql'
47
+ ? 'Ensure MySQL is started in XAMPP Control Panel.'
48
+ : 'Ensure the PostgreSQL service is running on ' + cfg.host + ':' + cfg.port + '.';
49
+ return 'Detected fragmented ' + label + ' config (' + (keys.length ? keys : cfg.sourceKeys).join(', ') + '). ' + action;
50
+ }
51
+
52
+ function statusPill(env, s) {
53
+ var label = env.toUpperCase();
54
+ if (!s) return '<span class="pill"><span class="dot"></span><b>' + label + ':</b> loading…</span>';
55
+ if (!s.connected) {
56
+ var why = /No (Dev|Prod) database configured/.test(s.error || '') ? 'not configured' : 'offline';
57
+ return '<span class="pill" title="' + esc(s.error || '') + '"><span class="dot off"></span><b>' + label + ':</b> ' + why +
58
+ ' <span class="badge badge-offline">OFFLINE</span></span>';
59
+ }
60
+ var sourceBadge = s.synthesized && s.sourceKey
61
+ ? ' <span class="badge badge-synth" title="Assembled in memory from fragmented .env keys">synthesized from ' + esc(keyFamily(s.sourceKey)) + '</span>'
62
+ : s.detectedFromExample && s.sourceKey
63
+ ? ' <span class="badge badge-source" title="Detected in ' + esc(s.exampleFile || '.env.example') + '">via ' + esc(s.sourceKey) + '</span>'
64
+ : (s.sourceKey ? ' <span class="badge badge-source">via ' + esc(s.sourceKey) + '</span>' : '');
65
+ return '<span class="pill" title="' + esc(s.maskedUrl) + '"><span class="dot on"></span><b>' + label + ':</b> ' +
66
+ esc(ENGINE_LABEL[s.engine] || s.engine) + (s.database ? ' · ' + esc(s.database) : '') + ' (' + Math.round(s.pingMs) + 'ms) – ' +
67
+ entityCount(s) + ' ' + entityNoun(s, true) + sourceBadge + ' <span class="badge badge-online">ONLINE</span></span>';
68
+ }
69
+
70
+ function renderDbShell() {
71
+ var st = state.status || {};
72
+ $('telemetry').innerHTML = statusPill('dev', st.dev) + statusPill('prod', st.prod);
73
+
74
+ var bannerEl = $('diagnostic-banner');
75
+ if (bannerEl) {
76
+ var devS = st.dev;
77
+ var prodS = st.prod;
78
+ var activeS = state.envMode === 'prod' ? prodS : devS;
79
+ var suggestion = fragmentedDiagnostic(activeS) || fragmentedDiagnostic(devS) ||
80
+ (activeS && !activeS.connected && activeS.suggestion) ||
81
+ (devS && !devS.connected && devS.suggestion) ||
82
+ (prodS && !prodS.connected && prodS.suggestion);
83
+
84
+ if (!suggestion && state.envInfo && state.envInfo.missingKeys && state.envInfo.missingKeys.length > 0) {
85
+ var missingForActive = state.envInfo.missingKeys.find(function (k) {
86
+ return k.targetEnv === (state.envMode === 'prod' ? 'prod' : 'dev');
87
+ }) || state.envInfo.missingKeys[0];
88
+ if (missingForActive && ((missingForActive.targetEnv === 'dev' && (!devS || !devS.connected)) || (missingForActive.targetEnv === 'prod' && (!prodS || !prodS.connected)))) {
89
+ var engLabel = missingForActive.engine ? ' (' + (ENGINE_LABEL[missingForActive.engine] || missingForActive.engine) + ')' : '';
90
+ suggestion = 'Detected "' + missingForActive.key + '"' + engLabel + ' in ' + (state.envInfo.templateFile || '.env.example') + ', but it is not set in your .env file.';
91
+ }
92
+ }
93
+
94
+ if (suggestion && ((!devS || !devS.connected) || (!prodS || !prodS.connected))) {
95
+ bannerEl.innerHTML = '<div class="diagnostic-banner">' +
96
+ '<div class="diagnostic-content">' +
97
+ '<span class="diagnostic-tag">[DIAGNOSTIC]</span> ' +
98
+ '<span class="diagnostic-msg">' + esc(suggestion) + '</span>' +
99
+ '</div>' +
100
+ '<div class="diagnostic-actions">' +
101
+ '<button class="btn small" type="button" id="btn-diag-settings">Open Connection Settings</button>' +
102
+ '</div>' +
103
+ '</div>';
104
+ bannerEl.hidden = false;
105
+ var btnDiag = $('btn-diag-settings');
106
+ if (btnDiag) {
107
+ btnDiag.onclick = function () { openSettings(); };
108
+ }
109
+ } else {
110
+ bannerEl.innerHTML = '';
111
+ bannerEl.hidden = true;
112
+ }
113
+ }
114
+
115
+ $('count-explorer').textContent = String((state.schema.devTables || []).length + (state.schema.prodTables || []).length);
116
+ var activeEnv = state.envMode === 'prod' ? 'prod' : 'dev';
117
+ var dataCount = state.dataResult && state.dataResult.totalCount != null
118
+ ? state.dataResult.totalCount
119
+ : ((state.schema[activeEnv + 'Tables'] || []).length);
120
+ $('count-data').textContent = String(dataCount);
121
+ var d = state.diff && state.diff.summary;
122
+ $('count-drift').textContent = d ? String(d.addedTablesCount + d.alteredTablesCount + d.droppedTablesCount) : '–';
123
+ var script = state.diff ? generateScript(state.diff) : { text: '', lang: 'sql', count: 0 };
124
+ state.sql = script.text;
125
+ state.scriptLang = script.lang;
126
+ state.scriptCount = script.count;
127
+ $('count-sql').textContent = String(script.count);
128
+ document.querySelectorAll('.subtab').forEach(function (t) {
129
+ var active = t.getAttribute('data-tab') === state.tab;
130
+ t.setAttribute('aria-selected', active ? 'true' : 'false');
131
+ t.setAttribute('tabindex', active ? '0' : '-1');
132
+ });
133
+ document.querySelectorAll('.env-seg-btn').forEach(function (btn) {
134
+ var m = btn.getAttribute('data-env-mode');
135
+ btn.setAttribute('aria-pressed', String(m === state.envMode));
136
+ });
137
+ $('view').setAttribute('aria-labelledby', 'tab-' + state.tab);
138
+ }
139
+
140
+ function renderDb() {
141
+ renderDbShell();
142
+ mediaRegistry = [];
143
+ hideThumb();
144
+ var view = $('view');
145
+ if (state.loading) { view.innerHTML = dbSkeleton(); return; }
146
+ if (state.tab === 'explorer') view.innerHTML = renderExplorer();
147
+ else if (state.tab === 'data') view.innerHTML = renderData();
148
+ else if (state.tab === 'drift') view.innerHTML = renderDrift();
149
+ else view.innerHTML = renderSql();
150
+ }
151
+
152
+ function dbSkeleton() {
153
+ var col = '<div class="pane card">' + '<div class="skeleton short"></div><div class="skeleton"></div><div class="skeleton"></div><div class="skeleton"></div></div>';
154
+ return '<div class="sr-only">Loading database schema…</div><div class="panes" aria-busy="true">' + col + col + '</div>';
155
+ }
156
+
157
+ var DB_ICON = '<svg class="icon empty-icon" width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true">' +
158
+ '<ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v6c0 1.66 3.58 3 8 3s8-1.34 8-3V5"/><path d="M4 11v6c0 1.66 3.58 3 8 3s8-1.34 8-3v-6"/></svg>';
159
+
160
+ function emptyState(title, text) {
161
+ return '<div class="empty card">' + DB_ICON + '<h3>' + esc(title) + '</h3><p>' + esc(text) + '</p>' +
162
+ '<button class="btn primary" type="button" data-action="open-settings">Connection Settings</button></div>';
163
+ }
164
+
165
+ `;
@@ -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 &amp; 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(/&quot;/g, '"').replace(/&#39;/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(/&#39;/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;