admindb 2.1.1 → 2.2.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.
Files changed (91) hide show
  1. package/README.md +41 -12
  2. package/dist/app.js +230 -1
  3. package/dist/auth/config.js +121 -1
  4. package/dist/auth/crypto.js +108 -1
  5. package/dist/auth/index.js +21 -1
  6. package/dist/auth/middleware.js +85 -1
  7. package/dist/auth/routes.js +70 -1
  8. package/dist/auth/types.js +7 -1
  9. package/dist/cli/args.js +320 -1
  10. package/dist/cli/config.js +128 -1
  11. package/dist/cli/index.js +20 -1
  12. package/dist/cli/runner.js +211 -1
  13. package/dist/cli.js +4 -1
  14. package/dist/data/chain.d.ts +1 -0
  15. package/dist/data/chain.js +707 -0
  16. package/dist/data/datasets.js +129 -1
  17. package/dist/data/detector.d.ts +1 -1
  18. package/dist/data/detector.js +511 -1
  19. package/dist/data/engine.d.ts +1 -1
  20. package/dist/data/engine.js +245 -1
  21. package/dist/data/index.d.ts +1 -1
  22. package/dist/data/index.js +22 -1
  23. package/dist/data/strategies.d.ts +1 -1
  24. package/dist/data/strategies.js +339 -1
  25. package/dist/data/types.d.ts +1 -1
  26. package/dist/data/types.js +5 -1
  27. package/dist/db/database.d.ts +1 -1
  28. package/dist/db/database.js +477 -1
  29. package/dist/db/export.d.ts +1 -1
  30. package/dist/db/export.js +90 -1
  31. package/dist/db/filters.js +142 -1
  32. package/dist/db/index.js +24 -1
  33. package/dist/db/introspection.js +167 -1
  34. package/dist/db/manager.js +351 -1
  35. package/dist/db/migrations.js +107 -1
  36. package/dist/db/postgres.d.ts +1 -1
  37. package/dist/db/postgres.js +735 -1
  38. package/dist/db/types.d.ts +1 -1
  39. package/dist/db/types.js +6 -1
  40. package/dist/index.js +52 -1
  41. package/dist/public/css/app.css +1 -1
  42. package/dist/public/js/api.js +1 -1
  43. package/dist/public/js/browse.js +1 -1
  44. package/dist/public/js/common.js +1 -0
  45. package/dist/public/js/databases.js +1 -1
  46. package/dist/public/js/designer.js +1 -1
  47. package/dist/public/js/forms.js +1 -1
  48. package/dist/public/js/home.js +1 -0
  49. package/dist/public/js/inspector.js +1 -1
  50. package/dist/public/js/query.js +1 -1
  51. package/dist/public/js/schema.js +1 -1
  52. package/dist/public/js/seed.js +1 -1
  53. package/dist/routes/api/erd.d.ts +1 -0
  54. package/dist/routes/api/erd.js +50 -0
  55. package/dist/routes/api/helpers.d.ts +1 -1
  56. package/dist/routes/api/helpers.js +163 -1
  57. package/dist/routes/api/import-export.js +83 -1
  58. package/dist/routes/api/index.js +48 -1
  59. package/dist/routes/api/query.js +78 -1
  60. package/dist/routes/api/rows.js +362 -1
  61. package/dist/routes/api/seed.js +153 -1
  62. package/dist/routes/api/tables.js +231 -1
  63. package/dist/routes/databases.js +228 -1
  64. package/dist/routes/index.js +19 -1
  65. package/dist/routes/pages.js +397 -1
  66. package/dist/serverless.js +174 -1
  67. package/dist/sql/classifier.js +24 -1
  68. package/dist/sql/error-analyzer.d.ts +1 -0
  69. package/dist/sql/error-analyzer.js +550 -0
  70. package/dist/sql/generator.js +319 -1
  71. package/dist/sql/index.js +18 -1
  72. package/dist/types/api.js +2 -1
  73. package/dist/utils/colors.js +54 -1
  74. package/dist/utils/common.d.ts +1 -1
  75. package/dist/utils/common.js +225 -1
  76. package/dist/utils/csv.js +67 -1
  77. package/dist/utils/datatype.js +181 -1
  78. package/dist/utils/icons.js +81 -1
  79. package/dist/utils/index.js +20 -1
  80. package/dist/utils/logger.js +36 -1
  81. package/dist/views/layouts/main.hbs +14 -1
  82. package/dist/views/pages/erd.hbs +1241 -0
  83. package/dist/views/pages/home.hbs +98 -2
  84. package/dist/views/pages/info.hbs +107 -0
  85. package/dist/views/pages/query.hbs +54 -12
  86. package/dist/views/pages/seed.hbs +435 -171
  87. package/dist/views/partials/sidebar.hbs +12 -0
  88. package/docs/API.md +6 -1
  89. package/docs/EXAMPLES.md +3 -0
  90. package/package.json +5 -4
  91. package/dist/public/js/icons.js +0 -1
@@ -1 +1 @@
1
- !function(){"use strict";const e=document.getElementById("query-sql"),t=document.getElementById("query-gutter"),n=document.getElementById("run-query"),s=document.getElementById("query-message"),r=document.getElementById("query-result"),o=document.getElementById("query-result-head"),a=document.getElementById("query-result-body"),i=document.getElementById("result-summary"),c=Array.prototype.slice.call(document.querySelectorAll("#save-query, #save-query-inline")),l=document.getElementById("save-query-name"),d=document.getElementById("saved-query-select"),u=document.getElementById("delete-query"),p=document.getElementById("export-csv"),m=document.getElementById("export-json");let y=[];function g(e){p&&(p.disabled=!e),m&&(m.disabled=!e)}async function f(t){const n=e.value.trim();if(n)try{const e=await fetch(Api.url("/api/query/export"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sql:n,format:t})});if(!e.ok){let t="Export failed ("+e.status+").";try{const n=await e.json();n&&n.error&&(t=n.error)}catch(e){}throw new Error(t)}const s=await e.blob(),r=URL.createObjectURL(s),o=document.createElement("a"),a=(new Date).toISOString().replace(/[:.]/g,"-");o.href=r,o.download="query-"+a+"."+t,document.body.appendChild(o),o.click(),o.remove(),setTimeout(()=>URL.revokeObjectURL(r),1e3),UI.showToast("Exported as "+t.toUpperCase()+".","success")}catch(e){x(e.message,"error")}}function w(e){return window.UI&&UI.escapeHtml?UI.escapeHtml(e):String(null==e?"":e)}function x(e,t){if(!e)return void(s.innerHTML="");const n={error:"border-error/40 bg-error/10 text-error",success:"border-success/40 bg-success/10 text-success",info:"border-info/40 bg-info/10 text-info",warning:"border-warning/40 bg-warning/10 text-warning"};s.innerHTML='<div class="flex items-start gap-2.5 rounded-xl border px-4 py-3 text-sm font-medium '+(n[t]||n.info)+'"><span>'+w(e)+"</span></div>"}function h(e,t){o.innerHTML="<tr>"+e.map(e=>'<th class="whitespace-nowrap font-semibold"><span class="font-mono normal-case">'+w(e)+"</span></th>").join("")+"</tr>",t.length?a.innerHTML=t.map(t=>'<tr class="hover:bg-primary/[0.04]">'+e.map(e=>{const n=t[e];if(null==n)return'<td class="align-middle"><span class="chip chip-null">NULL</span></td>';const s=String(n),r=function(e){if("string"!=typeof e)return!1;const t=e.trim();if(!(t.startsWith("{")&&t.endsWith("}")||t.startsWith("[")&&t.endsWith("]")))return!1;try{return JSON.parse(t),!0}catch{return!1}}(s),o=function(e){return"string"==typeof e&&/^0x[0-9a-f]{4,}$/i.test(e.trim())}(s),a=s.length>140?s.slice(0,140)+"…":s;return r?'<td class="max-w-xs align-middle js-query-cell" data-col="'+w(e)+'" data-value="'+w(s)+'" data-json="true"><div class="flex items-center gap-1.5"><span class="inline-flex items-center gap-1 rounded-md bg-info/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-info">{ } JSON</span><span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">'+w(a)+'</span><button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect JSON"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button></div></td>':o?'<td class="max-w-xs align-middle js-query-cell" data-col="'+w(e)+'" data-value="'+w(s)+'" data-blob="true"><div class="flex items-center gap-1.5"><span class="inline-flex items-center gap-1 rounded-md bg-secondary/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-secondary">🗃️ BLOB</span><span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">'+w(a)+'</span><button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect BLOB"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button></div></td>':'<td class="max-w-xs align-middle js-query-cell" data-col="'+w(e)+'" data-value="'+w(s)+'"><div class="flex items-center justify-between gap-1 group/cell"><span class="truncate font-mono text-[13px]" title="'+w(s)+'">'+w(a)+"</span>"+(s.length>25?'<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover/cell:opacity-100 transition-opacity js-query-inspect shrink-0" title="Inspect text"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>':"")+"</div></td>"}).join("")+"</tr>").join(""):a.innerHTML='<tr><td colspan="'+Math.max(1,e.length)+'" class="py-10 text-center text-base-content/45">No rows returned.</td></tr>'}function v(e,t){o.innerHTML="",a.innerHTML='<tr><td class="px-4 py-8 text-center"><p class="text-2xl font-extrabold text-primary">'+w(e)+'</p><p class="mt-1 text-sm text-base-content/45">'+w(t)+"</p></td></tr>"}function b(e){i&&(i.textContent=e)}async function q(){const t=e.value.trim();if(!t)return void x("Enter a SQL statement first.","warning");if(function(e){var t=e.replace(/--.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,"").trim();return!!/\b(drop\s+(table|view|index|trigger)|truncate\b)/i.test(t)||!(!/\bdelete\s+from\b/i.test(t)||/\bwhere\b/i.test(t))}(t)){if(!await UI.confirm({title:"Execute Destructive SQL",message:"This SQL statement contains potentially destructive operations (e.g. DROP, TRUNCATE, or unrestricted DELETE) that may permanently erase database schemas or data.",detailsHtml:'<pre class="font-mono text-xs text-base-content/85 whitespace-pre-wrap break-all">'+w(t.length>350?t.slice(0,350)+"…":t)+"</pre>",confirmText:"Execute anyway",danger:!0}))return}s.innerHTML="",r.classList.add("hidden"),g(!1);const n=performance.now();try{const e=await Api.post("/api/query",{sql:t}),s=Math.max(1,Math.round(performance.now()-n));r.classList.remove("hidden"),"count"===e.kind?(v(String(e.count),"count"),b("Count query · "+s+"ms")):"select"===e.kind?(h(e.columns,e.rows),b(e.rows.length+" row(s) · "+s+"ms"),g(!0)):(v(e.message||"Statement executed.","write"),b(null!=e.changes?e.changes+" row(s) affected · "+s+"ms":s+"ms"))}catch(e){r.classList.add("hidden"),x(e.message,"error")}}function k(){if(!t)return;const n=e.value.split("\n").length;let s="";for(let e=1;e<=n;e++)s+=e+"\n";t.textContent=s,t.scrollTop=e.scrollTop}async function E(e){try{y=await Api.get("/api/queries")}catch(e){y=[]}d.innerHTML='<option value="">— select a saved query —</option>'+y.map(t=>'<option value="'+t.id+'"'+(e&&t.name===e?" selected":"")+">"+w(t.name)+"</option>").join(""),u.disabled=!0}async function L(){const t=e.value.trim(),n=l.value.trim();if(t)if(n)try{await Api.post("/api/queries",{name:n,sql:t}),UI.showToast("Query saved.","success"),await E(n)}catch(e){x(e.message,"error")}else x("Give the query a name.","warning");else x("Write some SQL before saving.","warning")}p&&p.addEventListener("click",()=>f("csv")),m&&m.addEventListener("click",()=>f("json")),a&&a.addEventListener("click",function(e){const t=e.target.closest(".js-query-inspect"),n=e.target.closest(".js-query-cell");if(!t&&(!n||e.detail<2))return;const s=t?t.closest(".js-query-cell"):n;if(!s)return;const r=s.dataset.col||"result",o=s.dataset.value||"",a="true"===s.dataset.blob,i="true"===s.dataset.json;window.Inspector&&window.Inspector.open&&window.Inspector.open({col:r,value:o,isNull:!1,isBlob:a,isJson:i,readonly:!0})}),e&&t&&(e.addEventListener("scroll",()=>{t.scrollTop=e.scrollTop}),e.addEventListener("input",k),e.addEventListener("keydown",e=>{(e.ctrlKey||e.metaKey)&&"Enter"===e.key&&(e.preventDefault(),q())}),k()),n&&n.addEventListener("click",q),c.forEach(e=>e.addEventListener("click",L)),d&&d.addEventListener("change",()=>{const t=d.value;if(u.disabled=!t,!t)return;const n=y.find(e=>String(e.id)===String(t));n&&(e.value=n.sql,l.value=n.name,k(),x('Loaded "'+n.name+'".',"info"))}),u&&u.addEventListener("click",async function(){const e=d.value;if(!e)return;const t=y.find(t=>String(t.id)===String(e)),n=t?t.name:"Saved query";if(await UI.confirm({title:"Delete Saved Query",message:'Are you sure you want to delete the saved query "'+n+'"?',item:n,itemType:"Saved Query",confirmText:"Delete query",danger:!0}))try{await Api.del("/api/queries/"+e),UI.showToast("Query deleted.","success"),await E()}catch(e){x(e.message,"error")}})}();
1
+ !function(){"use strict";const t=document.getElementById("query-sql"),e=document.getElementById("query-gutter"),n=document.getElementById("editor-cursor-pos"),s=document.getElementById("editor-stats"),r=document.getElementById("run-query"),i=document.getElementById("format-query"),o=document.getElementById("copy-query"),a=document.getElementById("clear-query"),l=document.getElementById("query-message"),c=document.getElementById("query-result"),d=document.getElementById("query-result-head"),u=document.getElementById("query-result-body"),p=document.getElementById("result-summary"),m=Array.prototype.slice.call(document.querySelectorAll("#save-query, #save-query-inline")),f=document.getElementById("save-query-name"),g=document.getElementById("saved-query-select"),h=document.getElementById("delete-query"),x=document.getElementById("export-csv"),w=document.getElementById("export-json");let y=[],b=null,v=!1;const E=window.Utils?window.Utils.escapeHtml:function(t){return String(null==t?"":t)},L=window.Utils?window.Utils.highlightSql:function(t){return E(t)};const T=new class{constructor(t=100){this.stack=[],this.index=-1,this.limit=t,this.lastRecordTime=0}record(t,e,n=250){const s=Date.now(),r=this.index>=0?this.stack[this.index]:null;if(r&&r.text===t)r.selection=e;else{if(r&&s-this.lastRecordTime<n&&1===Math.abs(t.length-r.text.length)&&!/\s|[;(),]/.test(t.slice(-1)))return r.text=t,r.selection=e,void(this.lastRecordTime=s);this.stack=this.stack.slice(0,this.index+1),this.stack.push({text:t,selection:e}),this.stack.length>this.limit?this.stack.shift():this.index++,this.lastRecordTime=s}}undo(){return this.index>0?(this.index--,this.stack[this.index]):null}redo(){return this.index<this.stack.length-1?(this.index++,this.stack[this.index]):null}}(100);function I(){if(!t)return"";return(t.innerText||t.textContent||"").replace(/\r\n/g,"\n").replace(/\r/g,"\n")}function k(t){const e=window.getSelection();if(!e||!e.rangeCount)return{start:0,end:0};const n=e.getRangeAt(0),s=n.cloneRange();s.selectNodeContents(t),s.setEnd(n.startContainer,n.startOffset);const r=s.toString().length;return{start:r,end:r+n.toString().length}}function q(t,e){const n=window.getSelection();if(!n)return;let s=0;const r=document.createRange();r.setStart(t,0),r.collapse(!0);const i=[t];let o,a=!1,l=!1;for(;!l&&(o=i.pop());)if(3===o.nodeType){const t=s+o.length;!a&&e.start>=s&&e.start<=t&&(r.setStart(o,e.start-s),a=!0),a&&e.end>=s&&e.end<=t&&(r.setEnd(o,e.end-s),l=!0),s=t}else{let t=o.childNodes.length;for(;t--;)i.push(o.childNodes[t])}n.removeAllRanges(),n.addRange(r)}function S(e,n=!0){if(t&&(v=!0,t.innerHTML=e?L(e):"",v=!1,M(),O(),n)){const t=(e||"").length;T.record(e||"",{start:t,end:t},0)}}function U(e){if(!t)return;const n=I(),s=k(t),r=n.slice(0,s.start)+e+n.slice(s.end),i=s.start+e.length;v=!0,t.innerHTML=L(r),q(t,{start:i,end:i}),v=!1,M(),O(),T.record(r,{start:i,end:i},0)}function M(){if(!t||!e)return;const n=I().split("\n"),s=Math.max(1,n.length);let r="";for(let t=1;t<=s;t++){const e=null!=b&&t===b;r+='<div class="sql-gutter-line'+(e?" has-error":"")+'" data-line="'+t+'" title="'+(e?"Error on line "+t:"Line "+t)+'">'+t+"</div>"}e.innerHTML=r,e.scrollTop=t.scrollTop}function O(){if(!t||!n)return;const e=I(),r=k(t),i=e.slice(0,r.start).split("\n"),o=i.length,a=i[i.length-1].length+1;if(n.textContent="Ln "+o+", Col "+a,s){const t=e.length,n=e.split("\n").length,i=Math.abs(r.end-r.start);s.textContent=i>0?t+" chars ("+i+" selected) · "+n+" lines":t+" chars · "+n+" lines"}}function j(e,n){if(!t)return;const s=I().split("\n"),r=Math.max(1,Math.min(s.length,e||1));let i=0;for(let t=0;t<r-1;t++)i+=s[t].length+1;n&&n>1&&(i+=Math.min(s[r-1].length,n-1)),t.focus(),q(t,{start:i,end:i});t.scrollTop=Math.max(0,21*(r-3)),M(),O()}function B(t,e){if(!l)return;if(!t)return void(l.innerHTML="");const n={error:"border-error/40 bg-error/10 text-error",success:"border-success/40 bg-success/10 text-success",info:"border-info/40 bg-info/10 text-info",warning:"border-warning/40 bg-warning/10 text-warning"};l.innerHTML='<div class="flex items-start gap-2.5 rounded-xl border px-4 py-3 text-sm font-medium '+(n[e]||n.info)+'"><span>'+E(t)+"</span></div>"}function R(t){x&&(x.disabled=!t),w&&(w.disabled=!t)}async function N(t){const e=I().trim();if(e)try{const n=await fetch(Api.url("/api/query/export"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sql:e,format:t})});if(!n.ok){let t="Export failed ("+n.status+").";try{const e=await n.json();e&&e.error&&(t=e.error)}catch(t){}throw new Error(t)}const s=await n.blob(),r=(new Date).toISOString().replace(/[:.]/g,"-");window.Utils&&window.Utils.downloadBlob&&window.Utils.downloadBlob(s,"query-"+r+"."+t),window.UI&&UI.showToast&&UI.showToast("Exported as "+t.toUpperCase()+".","success")}catch(t){B(t.message,"error")}}i&&i.addEventListener("click",function(){const t=I();t.trim()&&(S(function(t){if(!t)return"";let e=t.trim().replace(/\r\n/g,"\n").replace(/\t/g," ");return["SELECT","FROM","WHERE","AND","OR","GROUP BY","HAVING","ORDER BY","LIMIT","OFFSET","LEFT JOIN","RIGHT JOIN","INNER JOIN","CROSS JOIN","FULL JOIN","JOIN","UNION ALL","UNION","INSERT INTO","VALUES","UPDATE","SET","DELETE FROM","CREATE TABLE","ALTER TABLE","DROP TABLE"].forEach(function(t){const n=new RegExp("\\b"+t.replace(" ","\\s+")+"\\b","gi");e=e.replace(n,"\n"+t.toUpperCase())}),e.split("\n").map(function(t){return t.trim()}).filter(function(t){return t.length>0}).map(function(t){const e=t.toUpperCase();return e.startsWith("WHERE")||e.startsWith("AND")||e.startsWith("OR")||e.startsWith("JOIN")||e.startsWith("LEFT")||e.startsWith("RIGHT")||e.startsWith("INNER")||e.startsWith("ON")||e.startsWith("SET")||e.startsWith("VALUES")||e.startsWith("HAVING")||e.startsWith("ORDER")||e.startsWith("GROUP")||e.startsWith("LIMIT")||e.startsWith("OFFSET")?" "+t:t}).join("\n")}(t)),window.UI&&UI.showToast&&UI.showToast("SQL formatted.","info"))}),o&&o.addEventListener("click",function(){const t=I();t.trim()&&navigator.clipboard.writeText(t).then(function(){window.UI&&UI.showToast&&UI.showToast("SQL copied to clipboard.","success")})}),a&&a.addEventListener("click",function(){S(""),b=null,B("","info"),t.focus()}),document.querySelectorAll(".js-snippet").forEach(function(e){e.addEventListener("click",function(n){n.preventDefault();const s=e.getAttribute("data-snippet");s&&(S(s.replace(/\\n/g,"\n")),b=null,B("","info"),t.focus())})}),e&&e.addEventListener("click",function(t){const e=t.target.closest(".sql-gutter-line");if(!e)return;const n=parseInt(e.dataset.line,10);n&&j(n,1)}),t&&(t.addEventListener("input",function(){b=null;const e=I(),n=k(t);T.record(e,n,0),function(){if(!t||v)return;v=!0;const e=k(t),n=I();n?(t.innerHTML=L(n),q(t,e)):t.innerHTML="",v=!1,M(),O()}()}),t.addEventListener("paste",function(t){t.preventDefault();const e=(t.clipboardData||window.clipboardData).getData("text/plain");e&&U(e)}),t.addEventListener("scroll",function(){e&&(e.scrollTop=t.scrollTop)}),t.addEventListener("click",function(){O()}),t.addEventListener("keyup",function(){O()}),t.addEventListener("keydown",function(e){if((e.ctrlKey||e.metaKey)&&"z"===e.key.toLowerCase()&&!e.shiftKey)return e.preventDefault(),void function(){const e=T.undo();e&&(v=!0,t.innerHTML=L(e.text),q(t,e.selection||{start:0,end:0}),v=!1,b=null,M(),O())}();if((e.ctrlKey||e.metaKey)&&"y"===e.key.toLowerCase()||(e.ctrlKey||e.metaKey)&&e.shiftKey&&"z"===e.key.toLowerCase())return e.preventDefault(),void function(){const e=T.redo();e&&(v=!0,t.innerHTML=L(e.text),q(t,e.selection||{start:0,end:0}),v=!1,b=null,M(),O())}();if((e.ctrlKey||e.metaKey)&&"Enter"===e.key)return e.preventDefault(),void W();if("Enter"===e.key){e.preventDefault();const n=I(),s=k(t),r=(n.slice(0,s.start).split("\n").pop()||"").match(/^(\s+)/);return void U("\n"+(r?r[1]:""))}if((e.ctrlKey||e.metaKey)&&"/"===e.key){e.preventDefault();const n=k(t),s=I(),r=s.lastIndexOf("\n",n.start-1)+1;let i=s.indexOf("\n",n.end);-1===i&&(i=s.length);const o=s.slice(r,i).split("\n"),a=o.every(function(t){return t.trim().startsWith("--")}),l=o.map(function(t){return a?t.replace(/^(\s*)--\s?/,"$1"):t?"-- "+t:t}).join("\n");return S(s.slice(0,r)+l+s.slice(i)),void q(t,{start:r,end:r+l.length})}if("Tab"===e.key){e.preventDefault();const n=k(t),s=I();if(n.start===n.end)e.shiftKey||U(" ");else{const r=s.lastIndexOf("\n",n.start-1)+1;let i=s.indexOf("\n",n.end);-1===i&&(i=s.length);const o=s.slice(r,i).split("\n").map(function(t){return e.shiftKey?t.replace(/^( |\t| )/,""):" "+t}).join("\n");S(s.slice(0,r)+o+s.slice(i)),q(t,{start:r,end:r+o.length})}return}})),x&&x.addEventListener("click",()=>N("csv")),w&&w.addEventListener("click",()=>N("json"));const C=window.Utils?window.Utils.isJson:function(t){return!1},A=window.Utils?window.Utils.isHexBlob:function(t){return!1};function H(t,e){d.innerHTML="",u.innerHTML='<tr><td class="px-4 py-8 text-center"><p class="text-2xl font-extrabold text-primary">'+E(t)+'</p><p class="mt-1 text-sm text-base-content/45">'+E(e)+"</p></td></tr>"}function D(t){p&&(p.textContent=t)}async function W(){const t=I().trim();if(!t)return void B("Enter a SQL statement first.","warning");if(function(t){var e=t.replace(/--.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,"").trim();return!!/\b(drop\s+(table|view|index|trigger)|truncate\b)/i.test(e)||!(!/\bdelete\s+from\b/i.test(e)||/\bwhere\b/i.test(e))}(t)){if(!await UI.confirm({title:"Execute Destructive SQL",message:"This SQL statement contains potentially destructive operations (e.g. DROP, TRUNCATE, or unrestricted DELETE) that may permanently erase database schemas or data.",detailsHtml:'<pre class="font-mono text-xs text-base-content/85 whitespace-pre-wrap break-all">'+E(t.length>350?t.slice(0,350)+"…":t)+"</pre>",confirmText:"Execute anyway",danger:!0}))return}b=null,M(),l.innerHTML="",c.classList.add("hidden"),R(!1);const e=performance.now();try{const r=await Api.post("/api/query",{sql:t}),i=Math.max(1,Math.round(performance.now()-e));c.classList.remove("hidden"),"count"===r.kind?(H(String(r.count),"count"),D("Count query · "+i+"ms")):"select"===r.kind?(n=r.columns,s=r.rows,d.innerHTML="<tr>"+n.map(t=>'<th class="whitespace-nowrap font-semibold"><span class="font-mono normal-case">'+E(t)+"</span></th>").join("")+"</tr>",s.length?u.innerHTML=s.map(t=>'<tr class="hover:bg-primary/[0.04]">'+n.map(e=>{const n=t[e];if(null==n)return'<td class="align-middle"><span class="chip chip-null">NULL</span></td>';const s=String(n),r=C(s),i=A(s),o=s.length>140?s.slice(0,140)+"…":s;return r?'<td class="max-w-xs align-middle js-query-cell" data-col="'+E(e)+'" data-value="'+E(s)+'" data-json="true"><div class="flex items-center gap-1.5"><span class="inline-flex items-center gap-1 rounded-md bg-info/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-info">{ } JSON</span><span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">'+E(o)+'</span><button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect JSON"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button></div></td>':i?'<td class="max-w-xs align-middle js-query-cell" data-col="'+E(e)+'" data-value="'+E(s)+'" data-blob="true"><div class="flex items-center gap-1.5"><span class="inline-flex items-center gap-1 rounded-md bg-secondary/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-secondary">🗃️ BLOB</span><span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">'+E(o)+'</span><button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect BLOB"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button></div></td>':'<td class="max-w-xs align-middle js-query-cell" data-col="'+E(e)+'" data-value="'+E(s)+'"><div class="flex items-center justify-between gap-1 group/cell"><span class="truncate font-mono text-[13px]" title="'+E(s)+'">'+E(o)+"</span>"+(s.length>25?'<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover/cell:opacity-100 transition-opacity js-query-inspect shrink-0" title="Inspect text"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>':"")+"</div></td>"}).join("")+"</tr>").join(""):u.innerHTML='<tr><td colspan="'+Math.max(1,n.length)+'" class="py-10 text-center text-base-content/45">No rows returned.</td></tr>',D(r.rows.length+" row(s) · "+i+"ms"),R(!0)):(H(r.message||"Statement executed.","write"),D(null!=r.changes?r.changes+" row(s) affected · "+i+"ms":i+"ms"))}catch(t){c.classList.add("hidden"),function(t){if(!l)return;const e=t&&t.details,n=t&&t.message||e&&e.message||"Query failed.";if(!e||!e.snippet&&!e.cause&&!e.suggestion)return void B(n,"error");b=e.line||null,M();let s='<div class="sql-error-card space-y-3">';s+='<div class="flex flex-wrap items-center justify-between gap-2 border-b border-error/20 pb-2.5">',s+='<div class="flex items-center gap-2">',s+='<span class="grid h-6 w-6 place-items-center rounded-lg bg-error/15 text-error font-bold">✕</span>',s+='<span class="font-bold text-sm text-error">'+E(n)+"</span>",s+="</div>",e.line&&(s+='<div class="flex items-center gap-1.5">',s+='<span class="badge badge-sm badge-error font-mono font-bold">Line '+e.line+(e.column?", Col "+e.column:"")+"</span>",s+='<button type="button" class="btn btn-xs btn-outline btn-error js-jump-err-line" data-line="'+e.line+'" data-col="'+(e.column||1)+'">Jump to line</button>',s+="</div>"),s+="</div>",e.snippet&&(s+="<div>",s+='<p class="text-[11px] font-bold uppercase tracking-wider text-base-content/45 mb-1.5">Code Snippet</p>',s+='<pre class="sql-error-snippet">'+E(e.snippet)+"</pre>",s+="</div>"),(e.cause||e.suggestion)&&(s+='<div class="grid grid-cols-1 md:grid-cols-2 gap-2.5 pt-1">',e.cause&&(s+='<div class="rounded-lg border border-base-300 bg-base-100/90 p-3 shadow-xs">',s+='<div class="flex items-center gap-1.5 text-xs font-bold text-base-content/80 mb-1">',s+="<span>💡</span><span>Possible Cause</span>",s+="</div>",s+='<p class="text-xs text-base-content/70 leading-relaxed">'+E(e.cause)+"</p>",s+="</div>"),e.suggestion&&(s+='<div class="rounded-lg border border-primary/30 bg-primary/5 p-3 shadow-xs">',s+='<div class="flex items-center gap-1.5 text-xs font-bold text-primary mb-1">',s+="<span>🔧</span><span>Suggested Fix</span>",s+="</div>",s+='<p class="text-xs text-base-content/80 font-medium leading-relaxed">'+E(e.suggestion)+"</p>",s+="</div>"),s+="</div>"),s+="</div>",l.innerHTML=s;const r=l.querySelector(".js-jump-err-line");r&&r.addEventListener("click",function(){j(parseInt(r.dataset.line,10),parseInt(r.dataset.col,10))})}(t)}var n,s}async function K(t){try{y=await Api.get("/api/queries")}catch(t){y=[]}g.innerHTML='<option value="">— select a saved query —</option>'+y.map(e=>'<option value="'+e.id+'"'+(t&&e.name===t?" selected":"")+">"+E(e.name)+"</option>").join(""),h.disabled=!0}async function J(){const t=I().trim(),e=f.value.trim();if(t)if(e)try{await Api.post("/api/queries",{name:e,sql:t}),window.UI&&UI.showToast&&UI.showToast("Query saved.","success"),await K(e)}catch(t){B(t.message,"error")}else B("Give the query a name.","warning");else B("Write some SQL before saving.","warning")}u&&u.addEventListener("click",function(t){const e=t.target.closest(".js-query-inspect"),n=t.target.closest(".js-query-cell");if(!e&&(!n||t.detail<2))return;const s=e?e.closest(".js-query-cell"):n;if(!s)return;const r=s.dataset.col||"result",i=s.dataset.value||"",o="true"===s.dataset.blob,a="true"===s.dataset.json;window.Inspector&&window.Inspector.open&&window.Inspector.open({col:r,value:i,isNull:!1,isBlob:o,isJson:a,readonly:!0})}),r&&r.addEventListener("click",W),m.forEach(t=>t.addEventListener("click",J)),g&&g.addEventListener("change",()=>{const t=g.value;if(h.disabled=!t,!t)return;const e=y.find(e=>String(e.id)===String(t));e&&(S(e.sql),f.value=e.name,b=null,B('Loaded "'+e.name+'".',"info"))}),h&&h.addEventListener("click",async function(){const t=g.value;if(!t)return;const e=y.find(e=>String(e.id)===String(t)),n=e?e.name:"Saved query";if(await UI.confirm({title:"Delete Saved Query",message:'Are you sure you want to delete the saved query "'+n+'"?',item:n,itemType:"Saved Query",confirmText:"Delete query",danger:!0}))try{await Api.del("/api/queries/"+t),window.UI&&UI.showToast&&UI.showToast("Query deleted.","success"),await K()}catch(t){B(t.message,"error")}});const Q=I();Q?S(Q,!0):T.record("",{start:0,end:0},0),M(),O()}();
@@ -1 +1 @@
1
- !function(){"use strict";const e=document.getElementById("schema-config");if(!e)return;const t=JSON.parse(e.textContent),l=window.APP&&"postgres"===window.APP.dialect||t&&"postgres"===t.dialect?["TEXT","VARCHAR(255)","INTEGER","BIGINT","SMALLINT","SERIAL","BIGSERIAL","REAL","DECIMAL(10,2)","NUMERIC","BOOLEAN","DATE","DATETIME","TIMESTAMP","TIMESTAMPTZ","TIME","INTERVAL","JSON","JSONB","UUID","BYTEA","INET"]:["TEXT","INTEGER","REAL","BLOB","BOOLEAN","DATE","DATETIME","NUMERIC","VARCHAR(255)","JSON"],n=/^[A-Za-z_][A-Za-z0-9_$]*$/,a=encodeURIComponent(t.table);function c(e){return window.UI&&UI.escapeHtml?UI.escapeHtml(e):String(null==e?"":e)}function s(e,t){e.classList.remove("hidden"),e.innerHTML='<div class="flex items-start gap-2.5 rounded-xl border border-error/40 bg-error/10 px-4 py-3 text-sm font-medium text-error"><span>'+c(t)+"</span></div>"}const o=document.getElementById("table-rename-input"),i=document.getElementById("rename-table");async function d(){const e=(o.value||"").trim();if(!e)return UI.showError("Table name is required.");if(e===t.table)return UI.showToast("Name unchanged.","info");try{const t=await Api.post("/api/tables/"+a+"/rename",{name:e});UI.showToast(t.message||"Table renamed.","success"),setTimeout(()=>{window.location.href=Api.basePath+"/tables/"+encodeURIComponent(e)+"/schema"},400)}catch(e){UI.showError(e.message)}}i&&i.addEventListener("click",d),o&&o.addEventListener("keydown",e=>{"Enter"===e.key&&d()});const r=document.getElementById("add-column"),u=document.getElementById("add-column-panel"),p=document.getElementById("add-column-form"),m=document.getElementById("add-column-error"),f=document.getElementById("add-column-submit"),b=document.getElementById("add-column-cancel");let y=[];async function h(){try{const e=await Api.get("/api/tables");y=e.map(e=>e.name).filter(e=>"_"!==e.charAt(0))}catch(e){y=[]}}function x(e){const t=(e||"TEXT").toUpperCase(),n=[...l];return e&&!n.map(e=>e.toUpperCase()).includes(t)&&n.unshift(e),n.map(e=>'<option value="'+c(e)+'"'+(e.toUpperCase()===t?" selected":"")+">"+c(e)+"</option>").join("")}r&&r.addEventListener("click",async()=>{E&&E.classList.add("hidden"),u.classList.toggle("hidden"),u.classList.contains("hidden")||(m.classList.add("hidden"),await async function(){y.length||await h(),p.innerHTML="",p.innerHTML=['<div class="col-span-2 sm:col-span-3"><label class="field-label">Name</label><input class="field-input ac-name" placeholder="column_name" autocomplete="off" spellcheck="false"></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Type</label><select class="field-select ac-type">'+x("TEXT")+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK table</label><select class="field-select ac-fk-table"><option value="">— none —</option>'+y.map(e=>"<option>"+c(e)+"</option>").join("")+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK column</label><select class="field-select ac-fk-col"><option value="">—</option></select></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Default</label><input class="field-input ac-default" placeholder="0, CURRENT_TIMESTAMP" autocomplete="off" spellcheck="false"></div>','<div class="col-span-2 flex items-end gap-1.5 pb-0.5 sm:col-span-1">','<label class="flex cursor-pointer flex-col items-center gap-1" title="Primary key"><input type="checkbox" class="checkbox checkbox-xs ac-pk"><span class="text-[10px] font-semibold uppercase text-base-content/50">PK</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Not null"><input type="checkbox" class="checkbox checkbox-xs ac-notnull"><span class="text-[10px] font-semibold uppercase text-base-content/50">NN</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Unique"><input type="checkbox" class="checkbox checkbox-xs ac-unique"><span class="text-[10px] font-semibold uppercase text-base-content/50">UQ</span></label>',"</div>"].join("");const e=p.querySelector(".ac-fk-table"),t=p.querySelector(".ac-fk-col");e.addEventListener("change",async()=>{t.innerHTML='<option value="">—</option>';const l=e.value;if(l)try{const e=await Api.get("/api/tables/"+encodeURIComponent(l)+"/info");t.innerHTML='<option value="">—</option>'+e.columns.map(e=>"<option>"+c(e.name)+"</option>").join("")}catch(e){}})}())}),b&&b.addEventListener("click",()=>{u.classList.add("hidden"),p.innerHTML=""}),f&&f.addEventListener("click",async function(){m.classList.add("hidden");const e=(p.querySelector(".ac-name").value||"").trim();if(!e)return s(m,"Column name is required.");if(!n.test(e))return s(m,'Invalid column name: "'+e+'". Use letters, digits and underscores.');const t=p.querySelector(".ac-fk-table").value,l=p.querySelector(".ac-fk-col").value,c={name:e,type:p.querySelector(".ac-type").value,primaryKey:p.querySelector(".ac-pk").checked,notNull:p.querySelector(".ac-notnull").checked,unique:p.querySelector(".ac-unique").checked,defaultValue:p.querySelector(".ac-default").value.trim()||null,foreignKey:t&&l?{table:t,column:l}:null};try{const e=await Api.post("/api/tables/"+a+"/columns",{column:c});UI.showToast(e.message||"Column added.","success"),window.location.reload()}catch(e){s(m,e.message)}});const E=document.getElementById("edit-column-panel"),v=document.getElementById("edit-column-form"),g=document.getElementById("edit-column-error"),I=document.getElementById("edit-column-submit"),T=document.getElementById("edit-column-cancel"),k=document.getElementById("edit-column-target");let L=null;T&&T.addEventListener("click",()=>{E.classList.add("hidden"),v.innerHTML="",L=null}),I&&I.addEventListener("click",async function(){if(!L)return;g.classList.add("hidden");const e=(v.querySelector(".ec-name").value||"").trim();if(!e)return s(g,"Column name is required.");if(!n.test(e))return s(g,'Invalid column name: "'+e+'". Use letters, digits and underscores.');const t=v.querySelector(".ec-fk-table").value,l=v.querySelector(".ec-fk-col").value,c={name:e,type:v.querySelector(".ec-type").value,primaryKey:v.querySelector(".ec-pk").checked,notNull:v.querySelector(".ec-notnull").checked,unique:v.querySelector(".ec-unique").checked,defaultValue:v.querySelector(".ec-default").value.trim()||null,foreignKey:t&&l?{table:t,column:l}:null};I&&(I.disabled=!0);try{const e=await Api.put("/api/tables/"+a+"/columns/"+encodeURIComponent(L),{column:c});UI.showToast(e.message||"Column updated.","success"),window.location.reload()}catch(e){s(g,e.message)}finally{I&&(I.disabled=!1)}}),document.querySelectorAll(".col-edit").forEach(e=>{e.addEventListener("click",()=>{!async function(e){if(!E||!v)return;u&&u.classList.add("hidden"),y.length||await h(),L=e.name,k&&(k.textContent='"'+e.name+'"'),g.classList.add("hidden"),v.innerHTML=['<div class="col-span-2 sm:col-span-3"><label class="field-label">Name</label><input class="field-input ec-name" value="'+c(e.name)+'" autocomplete="off" spellcheck="false"></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Type</label><select class="field-select ec-type">'+x(e.type)+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK table</label><select class="field-select ec-fk-table"><option value="">— none —</option>'+y.map(t=>'<option value="'+c(t)+'"'+(e.fkTable===t?" selected":"")+">"+c(t)+"</option>").join("")+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK column</label><select class="field-select ec-fk-col"><option value="">—</option></select></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Default</label><input class="field-input ec-default" value="'+c(e.defaultValue||"")+'" placeholder="0, CURRENT_TIMESTAMP" autocomplete="off" spellcheck="false"></div>','<div class="col-span-2 flex items-end gap-1.5 pb-0.5 sm:col-span-1">','<label class="flex cursor-pointer flex-col items-center gap-1" title="Primary key"><input type="checkbox" class="checkbox checkbox-xs ec-pk"'+(e.primaryKey?" checked":"")+'><span class="text-[10px] font-semibold uppercase text-base-content/50">PK</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Not null"><input type="checkbox" class="checkbox checkbox-xs ec-notnull"'+(e.notNull?" checked":"")+'><span class="text-[10px] font-semibold uppercase text-base-content/50">NN</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Unique"><input type="checkbox" class="checkbox checkbox-xs ec-unique"'+(e.unique?" checked":"")+'><span class="text-[10px] font-semibold uppercase text-base-content/50">UQ</span></label>',"</div>"].join("");const t=v.querySelector(".ec-fk-table"),l=v.querySelector(".ec-fk-col");async function n(e,t){if(l.innerHTML='<option value="">—</option>',e)try{const n=await Api.get("/api/tables/"+encodeURIComponent(e)+"/info");l.innerHTML='<option value="">—</option>'+n.columns.map(e=>'<option value="'+c(e.name)+'"'+(e.name===t?" selected":"")+">"+c(e.name)+"</option>").join("")}catch(e){}}e.fkTable&&await n(e.fkTable,e.fkTo),t.addEventListener("change",()=>n(t.value,"")),E.classList.remove("hidden"),E.scrollIntoView({behavior:"smooth",block:"nearest"})}({name:e.dataset.column||"",type:e.dataset.type||"TEXT",primaryKey:"true"===e.dataset.pk,notNull:"true"===e.dataset.notnull,unique:"true"===e.dataset.unique,defaultValue:e.dataset.default||"",fkTable:e.dataset.fkTable||"",fkTo:e.dataset.fkTo||""})})}),document.querySelectorAll(".col-drop").forEach(e=>{e.addEventListener("click",async()=>{const l=e.closest("tr");if(!l)return;const n=l.querySelector(".col-name-cell"),c=n?n.dataset.column:"";if(!c)return;await UI.confirm({title:"Drop Column",message:'Dropping column "'+c+'" will permanently delete all data stored in this column for all rows in table "'+t.table+'". This action cannot be undone.',item:c,itemType:"Column",confirmText:"Drop column",danger:!0})&&Api.del("/api/tables/"+a+"/columns/"+encodeURIComponent(c)).then(e=>{UI.showToast(e.message||"Column dropped.","success"),window.location.reload()}).catch(e=>UI.showError(e.message))})});const w=document.getElementById("drop-table");w&&w.addEventListener("click",async()=>{await UI.confirm({title:"Drop Table",message:'Dropping table "'+t.table+'" permanently erases the table schema, all indexes, and all stored records. This action cannot be undone.',item:t.table,itemType:"Table",requireInputMatch:t.table,confirmText:"Drop table",danger:!0})&&Api.del("/api/tables/"+a).then(()=>{UI.showToast("Table dropped.","success"),setTimeout(()=>{window.location.href=Api.basePath+"/"},400)}).catch(e=>UI.showError(e.message))});const A=document.getElementById("add-index"),q=document.getElementById("add-index-panel"),U=document.getElementById("index-name"),S=document.getElementById("index-unique"),B=document.getElementById("add-index-error"),N=document.getElementById("add-index-submit"),C=document.getElementById("add-index-cancel"),M=document.getElementById("index-preview"),R=document.getElementById("index-preview-code"),D=Array.prototype.slice.call(document.querySelectorAll(".index-col-btn"));let H=[];function P(){if(!M||!R)return;const e=H.slice(),l=(U.value||"").trim(),n=S&&S.checked?"UNIQUE ":"";if(!e.length)return M.classList.add("hidden"),void(R.textContent="");const a=l||"idx_"+t.table+"_"+e.join("_");R.textContent="CREATE "+n+'INDEX "'+a+'" ON "'+t.table+'" ('+e.map(e=>'"'+e+'"').join(", ")+");",M.classList.remove("hidden")}D.forEach(e=>e.addEventListener("click",()=>function(e){const t=e.dataset.column,l=H.indexOf(t),n=e.querySelector(".index-col-num");-1!==l?(H.splice(l,1),e.classList.remove("border-primary","bg-primary/10"),n&&n.classList.add("hidden")):(H.push(t),e.classList.add("border-primary","bg-primary/10"),n&&n.classList.remove("hidden")),H.forEach((e,t)=>{const l=D.find(t=>t.dataset.column===e),n=l&&l.querySelector(".index-col-num");n&&(n.textContent=String(t+1))}),P()}(e))),U&&U.addEventListener("input",P),S&&S.addEventListener("change",P),A&&q&&A.addEventListener("click",()=>{q.classList.toggle("hidden"),q.classList.contains("hidden")||(B.classList.add("hidden"),U.focus(),P())}),C&&C.addEventListener("click",()=>{q.classList.add("hidden"),B.classList.add("hidden")}),N&&N.addEventListener("click",async()=>{B.classList.add("hidden");const e=H.slice();if(!e.length)return s(B,"Select at least one column for the index.");const t={columns:e,unique:!(!S||!S.checked)},l=(U.value||"").trim();l&&(t.name=l),N.disabled=!0;try{const e=await Api.post("/api/tables/"+a+"/indexes",t);UI.showToast(e.message||"Index created.","success"),window.location.reload()}catch(e){s(B,e.message)}finally{N.disabled=!1}}),document.querySelectorAll(".index-drop").forEach(e=>{e.addEventListener("click",async()=>{const l=e.dataset.index;if(!l)return;await UI.confirm({title:"Drop Index",message:'Are you sure you want to permanently drop index "'+l+'" on table "'+t.table+'"?',item:l,itemType:"Index",confirmText:"Drop index",danger:!0})&&Api.del("/api/tables/"+a+"/indexes/"+encodeURIComponent(l)).then(e=>{UI.showToast(e.message||"Index dropped.","success"),window.location.reload()}).catch(e=>UI.showError(e.message))})}),h()}();
1
+ !function(){"use strict";const e=document.getElementById("schema-config");if(!e)return;const t=JSON.parse(e.textContent),l=(window.Utils?window.Utils.isPostgres(t&&t.dialect):window.APP&&"postgres"===window.APP.dialect||t&&t.dialect,window.Utils?window.Utils.getDesignerTypes(t&&t.dialect):["TEXT","INTEGER","REAL","BLOB","BOOLEAN"]),n=window.Utils?window.Utils.NAME_REGEX:/^[A-Za-z_][A-Za-z0-9_$]*$/,a=encodeURIComponent(t.table),c=window.Utils?window.Utils.escapeHtml:function(e){return String(null==e?"":e)};function s(e,t){e.classList.remove("hidden"),e.innerHTML='<div class="flex items-start gap-2.5 rounded-xl border border-error/40 bg-error/10 px-4 py-3 text-sm font-medium text-error"><span>'+c(t)+"</span></div>"}const o=document.getElementById("table-rename-input"),i=document.getElementById("rename-table");async function d(){const e=(o.value||"").trim();if(!e)return UI.showError("Table name is required.");if(e===t.table)return UI.showToast("Name unchanged.","info");try{const t=await Api.post("/api/tables/"+a+"/rename",{name:e});UI.showToast(t.message||"Table renamed.","success"),setTimeout(()=>{window.location.href=Api.basePath+"/tables/"+encodeURIComponent(e)+"/schema"},400)}catch(e){UI.showError(e.message)}}i&&i.addEventListener("click",d),o&&o.addEventListener("keydown",e=>{"Enter"===e.key&&d()});const r=document.getElementById("add-column"),u=document.getElementById("add-column-panel"),p=document.getElementById("add-column-form"),m=document.getElementById("add-column-error"),f=document.getElementById("add-column-submit"),b=document.getElementById("add-column-cancel");let y=[];async function h(){try{const e=await Api.get("/api/tables");y=e.map(e=>e.name).filter(e=>"_"!==e.charAt(0))}catch(e){y=[]}}function x(e){const t=(e||"TEXT").toUpperCase(),n=[...l];return e&&!n.map(e=>e.toUpperCase()).includes(t)&&n.unshift(e),n.map(e=>'<option value="'+c(e)+'"'+(e.toUpperCase()===t?" selected":"")+">"+c(e)+"</option>").join("")}r&&r.addEventListener("click",async()=>{g&&g.classList.add("hidden"),u.classList.toggle("hidden"),u.classList.contains("hidden")||(m.classList.add("hidden"),await async function(){y.length||await h(),p.innerHTML="",p.innerHTML=['<div class="col-span-2 sm:col-span-3"><label class="field-label">Name</label><input class="field-input ac-name" placeholder="column_name" autocomplete="off" spellcheck="false"></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Type</label><select class="field-select ac-type">'+x("TEXT")+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK table</label><select class="field-select ac-fk-table"><option value="">— none —</option>'+y.map(e=>"<option>"+c(e)+"</option>").join("")+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK column</label><select class="field-select ac-fk-col"><option value="">—</option></select></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Default</label><input class="field-input ac-default" placeholder="0, CURRENT_TIMESTAMP" autocomplete="off" spellcheck="false"></div>','<div class="col-span-2 flex items-end gap-1.5 pb-0.5 sm:col-span-1">','<label class="flex cursor-pointer flex-col items-center gap-1" title="Primary key"><input type="checkbox" class="checkbox checkbox-xs ac-pk"><span class="text-[10px] font-semibold uppercase text-base-content/50">PK</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Not null"><input type="checkbox" class="checkbox checkbox-xs ac-notnull"><span class="text-[10px] font-semibold uppercase text-base-content/50">NN</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Unique"><input type="checkbox" class="checkbox checkbox-xs ac-unique"><span class="text-[10px] font-semibold uppercase text-base-content/50">UQ</span></label>',"</div>"].join("");const e=p.querySelector(".ac-fk-table"),t=p.querySelector(".ac-fk-col");e.addEventListener("change",async()=>{t.innerHTML='<option value="">—</option>';const l=e.value;if(l)try{const e=await Api.get("/api/tables/"+encodeURIComponent(l)+"/info");t.innerHTML='<option value="">—</option>'+e.columns.map(e=>"<option>"+c(e.name)+"</option>").join("")}catch(e){}})}())}),b&&b.addEventListener("click",()=>{u.classList.add("hidden"),p.innerHTML=""}),f&&f.addEventListener("click",async function(){m.classList.add("hidden");const e=(p.querySelector(".ac-name").value||"").trim();if(!e)return s(m,"Column name is required.");if(!n.test(e))return s(m,'Invalid column name: "'+e+'". Use letters, digits and underscores.');const t=p.querySelector(".ac-fk-table").value,l=p.querySelector(".ac-fk-col").value,c={name:e,type:p.querySelector(".ac-type").value,primaryKey:p.querySelector(".ac-pk").checked,notNull:p.querySelector(".ac-notnull").checked,unique:p.querySelector(".ac-unique").checked,defaultValue:p.querySelector(".ac-default").value.trim()||null,foreignKey:t&&l?{table:t,column:l}:null};try{const e=await Api.post("/api/tables/"+a+"/columns",{column:c});UI.showToast(e.message||"Column added.","success"),window.location.reload()}catch(e){s(m,e.message)}});const g=document.getElementById("edit-column-panel"),v=document.getElementById("edit-column-form"),k=document.getElementById("edit-column-error"),E=document.getElementById("edit-column-submit"),w=document.getElementById("edit-column-cancel"),I=document.getElementById("edit-column-target");let T=null;w&&w.addEventListener("click",()=>{g.classList.add("hidden"),v.innerHTML="",T=null}),E&&E.addEventListener("click",async function(){if(!T)return;k.classList.add("hidden");const e=(v.querySelector(".ec-name").value||"").trim();if(!e)return s(k,"Column name is required.");if(!n.test(e))return s(k,'Invalid column name: "'+e+'". Use letters, digits and underscores.');const t=v.querySelector(".ec-fk-table").value,l=v.querySelector(".ec-fk-col").value,c={name:e,type:v.querySelector(".ec-type").value,primaryKey:v.querySelector(".ec-pk").checked,notNull:v.querySelector(".ec-notnull").checked,unique:v.querySelector(".ec-unique").checked,defaultValue:v.querySelector(".ec-default").value.trim()||null,foreignKey:t&&l?{table:t,column:l}:null};E&&(E.disabled=!0);try{const e=await Api.put("/api/tables/"+a+"/columns/"+encodeURIComponent(T),{column:c});UI.showToast(e.message||"Column updated.","success"),window.location.reload()}catch(e){s(k,e.message)}finally{E&&(E.disabled=!1)}}),document.querySelectorAll(".col-edit").forEach(e=>{e.addEventListener("click",()=>{!async function(e){if(!g||!v)return;u&&u.classList.add("hidden"),y.length||await h(),T=e.name,I&&(I.textContent='"'+e.name+'"'),k.classList.add("hidden"),v.innerHTML=['<div class="col-span-2 sm:col-span-3"><label class="field-label">Name</label><input class="field-input ec-name" value="'+c(e.name)+'" autocomplete="off" spellcheck="false"></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Type</label><select class="field-select ec-type">'+x(e.type)+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK table</label><select class="field-select ec-fk-table"><option value="">— none —</option>'+y.map(t=>'<option value="'+c(t)+'"'+(e.fkTable===t?" selected":"")+">"+c(t)+"</option>").join("")+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK column</label><select class="field-select ec-fk-col"><option value="">—</option></select></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Default</label><input class="field-input ec-default" value="'+c(e.defaultValue||"")+'" placeholder="0, CURRENT_TIMESTAMP" autocomplete="off" spellcheck="false"></div>','<div class="col-span-2 flex items-end gap-1.5 pb-0.5 sm:col-span-1">','<label class="flex cursor-pointer flex-col items-center gap-1" title="Primary key"><input type="checkbox" class="checkbox checkbox-xs ec-pk"'+(e.primaryKey?" checked":"")+'><span class="text-[10px] font-semibold uppercase text-base-content/50">PK</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Not null"><input type="checkbox" class="checkbox checkbox-xs ec-notnull"'+(e.notNull?" checked":"")+'><span class="text-[10px] font-semibold uppercase text-base-content/50">NN</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Unique"><input type="checkbox" class="checkbox checkbox-xs ec-unique"'+(e.unique?" checked":"")+'><span class="text-[10px] font-semibold uppercase text-base-content/50">UQ</span></label>',"</div>"].join("");const t=v.querySelector(".ec-fk-table"),l=v.querySelector(".ec-fk-col");async function n(e,t){if(l.innerHTML='<option value="">—</option>',e)try{const n=await Api.get("/api/tables/"+encodeURIComponent(e)+"/info");l.innerHTML='<option value="">—</option>'+n.columns.map(e=>'<option value="'+c(e.name)+'"'+(e.name===t?" selected":"")+">"+c(e.name)+"</option>").join("")}catch(e){}}e.fkTable&&await n(e.fkTable,e.fkTo),t.addEventListener("change",()=>n(t.value,"")),g.classList.remove("hidden"),g.scrollIntoView({behavior:"smooth",block:"nearest"})}({name:e.dataset.column||"",type:e.dataset.type||"TEXT",primaryKey:"true"===e.dataset.pk,notNull:"true"===e.dataset.notnull,unique:"true"===e.dataset.unique,defaultValue:e.dataset.default||"",fkTable:e.dataset.fkTable||"",fkTo:e.dataset.fkTo||""})})}),document.querySelectorAll(".col-drop").forEach(e=>{e.addEventListener("click",async()=>{const l=e.closest("tr");if(!l)return;const n=l.querySelector(".col-name-cell"),c=n?n.dataset.column:"";if(!c)return;await UI.confirm({title:"Drop Column",message:'Dropping column "'+c+'" will permanently delete all data stored in this column for all rows in table "'+t.table+'". This action cannot be undone.',item:c,itemType:"Column",confirmText:"Drop column",danger:!0})&&Api.del("/api/tables/"+a+"/columns/"+encodeURIComponent(c)).then(e=>{UI.showToast(e.message||"Column dropped.","success"),window.location.reload()}).catch(e=>UI.showError(e.message))})});const L=document.getElementById("drop-table");L&&L.addEventListener("click",async()=>{await UI.confirm({title:"Drop Table",message:'Dropping table "'+t.table+'" permanently erases the table schema, all indexes, and all stored records. This action cannot be undone.',item:t.table,itemType:"Table",requireInputMatch:t.table,confirmText:"Drop table",danger:!0})&&Api.del("/api/tables/"+a).then(()=>{UI.showToast("Table dropped.","success"),setTimeout(()=>{window.location.href=Api.basePath+"/"},400)}).catch(e=>UI.showError(e.message))});const q=document.getElementById("add-index"),U=document.getElementById("add-index-panel"),S=document.getElementById("index-name"),A=document.getElementById("index-unique"),B=document.getElementById("add-index-error"),C=document.getElementById("add-index-submit"),N=document.getElementById("add-index-cancel"),M=document.getElementById("index-preview"),R=document.getElementById("index-preview-code"),P=Array.prototype.slice.call(document.querySelectorAll(".index-col-btn"));let D=[];function K(){if(!M||!R)return;const e=D.slice(),l=(S.value||"").trim(),n=A&&A.checked?"UNIQUE ":"";if(!e.length)return M.classList.add("hidden"),void(R.textContent="");const a=l||"idx_"+t.table+"_"+e.join("_");R.textContent="CREATE "+n+'INDEX "'+a+'" ON "'+t.table+'" ('+e.map(e=>'"'+e+'"').join(", ")+");",M.classList.remove("hidden")}P.forEach(e=>e.addEventListener("click",()=>function(e){const t=e.dataset.column,l=D.indexOf(t),n=e.querySelector(".index-col-num");-1!==l?(D.splice(l,1),e.classList.remove("border-primary","bg-primary/10"),n&&n.classList.add("hidden")):(D.push(t),e.classList.add("border-primary","bg-primary/10"),n&&n.classList.remove("hidden")),D.forEach((e,t)=>{const l=P.find(t=>t.dataset.column===e),n=l&&l.querySelector(".index-col-num");n&&(n.textContent=String(t+1))}),K()}(e))),S&&S.addEventListener("input",K),A&&A.addEventListener("change",K),q&&U&&q.addEventListener("click",()=>{U.classList.toggle("hidden"),U.classList.contains("hidden")||(B.classList.add("hidden"),S.focus(),K())}),N&&N.addEventListener("click",()=>{U.classList.add("hidden"),B.classList.add("hidden")}),C&&C.addEventListener("click",async()=>{B.classList.add("hidden");const e=D.slice();if(!e.length)return s(B,"Select at least one column for the index.");const t={columns:e,unique:!(!A||!A.checked)},l=(S.value||"").trim();l&&(t.name=l),C.disabled=!0;try{const e=await Api.post("/api/tables/"+a+"/indexes",t);UI.showToast(e.message||"Index created.","success"),window.location.reload()}catch(e){s(B,e.message)}finally{C.disabled=!1}}),document.querySelectorAll(".index-drop").forEach(e=>{e.addEventListener("click",async()=>{const l=e.dataset.index;if(!l)return;await UI.confirm({title:"Drop Index",message:'Are you sure you want to permanently drop index "'+l+'" on table "'+t.table+'"?',item:l,itemType:"Index",confirmText:"Drop index",danger:!0})&&Api.del("/api/tables/"+a+"/indexes/"+encodeURIComponent(l)).then(e=>{UI.showToast(e.message||"Index dropped.","success"),window.location.reload()}).catch(e=>UI.showError(e.message))})}),h()}();
@@ -1 +1 @@
1
- !function(){"use strict";const e=document.getElementById("seed-config");if(!e)return;const t=JSON.parse(e.textContent),n=encodeURIComponent(t.table),a=t.columns||[],s=document.getElementById("seed-count"),o=document.getElementById("seed-search"),l=document.getElementById("seed-truncate"),c=document.getElementById("seed-columns"),r=document.getElementById("seed-empty-search"),d=document.getElementById("seed-col-count-badge"),i=document.getElementById("seed-feedback"),m=document.getElementById("seed-preview-card"),p=document.getElementById("preview-summary-badge"),u=document.getElementById("seed-preview-thead"),g=document.getElementById("seed-preview-tbody"),b=document.getElementById("seed-table-view-container"),f=document.getElementById("seed-sql-view-container"),h=document.getElementById("seed-sql-code"),y=document.getElementById("tab-table-view"),x=document.getElementById("tab-sql-view"),E=document.getElementById("seed-insert"),w=document.getElementById("seed-preview"),v=document.getElementById("seed-reset"),k=document.getElementById("seed-refresh-samples"),L=document.getElementById("seed-apply-all"),C=document.getElementById("seed-copy-sql"),S=document.getElementById("seed-download-sql"),I=document.getElementById("seed-export-csv"),N=document.getElementById("seed-export-json"),U=document.getElementById("seed-export-config"),M=document.getElementById("seed-import-config"),B=document.getElementById("seed-config-dialog"),q=document.getElementById("seed-config-dialog-title"),T=document.getElementById("seed-config-textarea"),j=document.getElementById("seed-config-dialog-cancel"),O=document.getElementById("seed-config-dialog-apply"),A=document.getElementById("seed-truncate-dialog"),D=document.getElementById("seed-truncate-cancel"),Y=document.getElementById("seed-truncate-confirm"),P={};let R=null;function F(e){return window.UI&&UI.escapeHtml?UI.escapeHtml(e):String(null==e?"":e)}const H=["Sombat","Minh","Hiroshi","William","Ravi","Zara","Elena","Liam","Mei","Noah"],G=["Chen","Smith","Thompson","Shah","Yamamoto","Patel","Garcia","Kim","Nguyen","Davis"],J=["example.com","acme.io","tech.asia","gmail.com","startup.io"],K=["New York","Tokyo","London","Singapore","Paris","Berlin","Sydney","Toronto"],$=["United States","Japan","United Kingdom","Singapore","Germany","France","Canada","Australia"],_=["Apex Solutions","Vortex Digital","Nexus Systems","Horizon Labs","Quantum Dynamics","Zenith Global"],V=["Software Engineer","Product Manager","UX Designer","DevOps Specialist","Data Scientist","Marketing Director"],Q=["active","pending","completed","archived","draft","verified"],W=["USD","EUR","GBP","JPY","CAD","AUD"],Z=["California","New York","Texas","Washington","Florida","Illinois"],z=["alpha","beta","stream","cloud","service","cluster","record","node","signal"];function X(e){return e[Math.floor(Math.random()*e.length)]}function ee(e,t){return Math.floor(Math.random()*(t-e+1))+e}function te(e,t){const n=t.strategy;if("skip"===n)return"(omitted / default)";if("null"===n)return"NULL";if(t.nullPct&&ee(1,100)<=t.nullPct&&!e.pk&&!e.notnull)return"NULL (simulated)";let a="";switch(n){case"fixed":a=t.value||"";break;case"list":{const e=(t.values||[]).filter(e=>""!==String(e).trim());a=e.length?X(e):"(empty list)";break}case"pattern":a=function(e){if(!e)return"INV-2026-"+ee(1e3,9999);const t=new Date;let n=e.replace(/\{YYYY\}/g,String(t.getFullYear())).replace(/\{YY\}/g,String(t.getFullYear()).slice(-2)).replace(/\{MM\}/g,String(ee(1,12)).padStart(2,"0")).replace(/\{DD\}/g,String(ee(1,28)).padStart(2,"0")),a="";const s="ABCDEFGHIJKLMNOPQRSTUVWXYZ",o="abcdefghijklmnopqrstuvwxyz";for(let e=0;e<n.length;e++){const t=n[e];a+="#"===t?String(ee(0,9)):"A"===t||"?"===t?s[ee(0,25)]:"a"===t?o[ee(0,25)]:t}return a}(t.pattern);break;case"first":a=X(H);break;case"last":a=X(G);break;case"fullname":a=X(H)+" "+X(G);break;case"username":a=X(H).toLowerCase()+ee(10,999);break;case"job":a=X(V);break;case"company":a=X(_);break;case"currency":a=X(W);break;case"status":a=X(Q);break;case"creditCard":a="4532-****-****-"+ee(1e3,9999);break;case"avatar":a="https://api.dicebear.com/7.x/avataaars/svg?seed="+X(H).toLowerCase();break;case"color":a="name"===t.format?"Emerald":"#3b82f6";break;case"email":a=X(H).toLowerCase()+"."+X(G).toLowerCase()+"@"+X(J);break;case"phone":a="+1 ("+ee(200,999)+") "+ee(200,999)+"-"+ee(1e3,9999);break;case"city":a=X(K);break;case"state":a=X(Z);break;case"country":a=X($);break;case"countryCode":a=X(["US","GB","CA","DE","JP","FR","AU","SG"]);break;case"postal":a=String(ee(1e4,99999));break;case"address":a=ee(100,9999)+" "+X(z)+" Ave, "+X(K);break;case"url":a="https://www."+X(J)+"/"+X(z);break;case"domain":a=X(J);break;case"ip":a=ee(1,223)+"."+ee(0,255)+"."+ee(0,255)+"."+ee(1,254);break;case"mac":a="00:1A:2B:"+ee(10,99)+":"+ee(10,99)+":"+ee(10,99);break;case"uuid":a="550e8400-e29b-41d4-a716-"+ee(1e11,999999999999);break;case"slug":a=X(z)+"-"+X(z)+"-"+ee(100,999);break;case"words":a=X(z)+" "+X(z);break;case"sentence":a="Lorem ipsum dolor sit "+X(z)+" amet.";break;case"paragraph":a="Lorem ipsum dolor sit amet. Consectetur adipiscing elit "+X(z)+" dynamicus.";break;case"latitude":a=(180*Math.random()-90).toFixed(6);break;case"longitude":a=(360*Math.random()-180).toFixed(6);break;case"sequence":a=String(t.start||1);break;case"int":a=String(ee(t.min??1,t.max??1e3));break;case"decimal":{const e=t.min??1,n=t.max??1e3,s=t.precision??2;a=(Math.random()*(n-e)+e).toFixed(s);break}case"date":a="2026-03-"+String(ee(1,28)).padStart(2,"0");break;case"datetime":a="2026-03-"+String(ee(1,28)).padStart(2,"0")+"T14:30:00.000Z";break;case"time":a="14:"+String(ee(10,59))+":00";break;case"timestampUnix":a="ms"===t.format?String(Date.now()):String(Math.floor(Date.now()/1e3));break;case"bool":a=X(["1","0"]);break;case"bytes":a="0x"+ee(1e5,999999).toString(16);break;case"json":a='{"scope":"read-only","enabled":true}';break;case"hash":a="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".slice(0,t.maxLen||32);break;case"token":a="tk_"+ee(1e5,999999);break;case"fk":a=e.fk?"[Sample from "+e.fk.table+"]":"NULL";break;default:a="(auto)"}return(t.prefix||t.suffix)&&(a=(t.prefix||"")+a+(t.suffix||"")),a}function ne(e,t,n,a){return Object.assign({key:e,label:t,type:n},a||{})}const ae={fixed:[ne("value","Value","text")],list:[ne("values","One value per line","textarea")],pattern:[ne("pattern","Pattern (tokens: #=digit, ?=letter, {YYYY})","text",{placeholder:"INV-{YYYY}-####"})],int:[ne("min","Min","number"),ne("max","Max","number")],decimal:[ne("min","Min","number"),ne("max","Max","number"),ne("precision","Decimals","number",{step:1,min:0,max:10})],sequence:[ne("start","Start from","number",{placeholder:"1"}),ne("step","Step increment","number",{placeholder:"1"})],date:[ne("from","From","date"),ne("to","To","date")],datetime:[ne("from","From","datetime-local"),ne("to","To","datetime-local")],time:[],timestampUnix:[ne("format","Format (sec / ms)","select",{options:[{value:"sec",label:"Seconds (10-digit)"},{value:"ms",label:"Milliseconds (13-digit)"}]})],color:[ne("format","Color format","select",{options:[{value:"hex",label:"Hex (#3b82f6)"},{value:"name",label:"Color Name (Emerald)"}]})],latitude:[ne("min","Min Lat","number",{step:1e-4,placeholder:"-90"}),ne("max","Max Lat","number",{step:1e-4,placeholder:"90"})],longitude:[ne("min","Min Lng","number",{step:1e-4,placeholder:"-180"}),ne("max","Max Lng","number",{step:1e-4,placeholder:"180"})],words:[ne("minLen","Min words","number"),ne("maxLen","Max words","number")],sentence:[ne("minLen","Min sentences","number"),ne("maxLen","Max sentences","number")],paragraph:[],bytes:[ne("minLen","Min bytes","number"),ne("maxLen","Max bytes","number")],json:[ne("jsonKeys","JSON keys (comma-separated)","text")],hash:[ne("minLen","Min length","number"),ne("maxLen","Max length","number")],token:[ne("minLen","Min length","number"),ne("maxLen","Max length","number")]},se={identity:"👤 Identity & People",contact_web:"🌐 Contact & Web",commerce:"🏢 Commerce & Business",location:"📍 Location",numeric:"🔢 Numbers & Math",datetime:"📅 Date & Time",system_crypto:"🔒 System & Crypto",custom_control:"⚙️ Custom & Control",relational:"🔗 Relational (Foreign Key)"};function oe(e,t){return"fk"===t&&e.fk?"Sampled from foreign table "+F(e.fk.table)+(e.fk.to?"."+F(e.fk.to):"")+".":"skip"===t?"Omitted from INSERT — DB default or NULL applies.":"null"===t?"Explicitly set to NULL for all rows.":"pattern"===t?"Replaces # with digits, ? with letters, and {YYYY} with year.":"sequence"===t?"Generates sequentially incrementing integers across rows.":"json"===t?"Generates valid JSON objects compliant with indexes and defaults.":"hash"===t?"Generates random cryptographic hex hashes.":"token"===t?"Generates short alphanumeric identifier tokens.":""}function le(e,t){const n=t.querySelector(".seed-sample-value");if(n){const t=te(e,P[e.name]||{});n.textContent=t}}function ce(e,t,n,a){e.innerHTML="";const s=ae[n.strategy]||[],o=s.length>0,l=!t.pk&&!t.notnull;["int","decimal","bool","bytes","skip","null"].includes(n.strategy);if(!o&&!l)return;const c=document.createElement("div");if(c.className="space-y-2 pt-2 border-t border-base-200/60",s.length){const e=document.createElement("div");e.className="grid grid-cols-2 gap-2",s.forEach(s=>{const o=document.createElement("label");o.className="block"+("textarea"===s.type||"pattern"===s.key||"jsonKeys"===s.key?" col-span-2":"");const l=document.createElement("span");let c;if(l.className="field-label !mb-1 !text-[11px] font-medium text-base-content/70",l.textContent=s.label,o.appendChild(l),"textarea"===s.type)c=document.createElement("textarea"),c.rows=3,c.className="field-input w-full font-mono !text-[11px] leading-tight",c.placeholder="one value per line",c.spellcheck=!1,Array.isArray(n.values)&&(c.value=n.values.join("\n"));else if("select"===s.type)c=document.createElement("select"),c.className="field-select !py-1 !text-[12px]",(s.options||[]).forEach(e=>{const t=document.createElement("option");t.value=e.value,t.textContent=e.label,n[s.key]===e.value&&(t.selected=!0),c.appendChild(t)});else{c=document.createElement("input"),c.type=s.type,c.className="field-input !py-1 !text-[12px]",null!=s.step&&(c.step=String(s.step)),null!=s.min&&(c.min=String(s.min)),null!=s.max&&(c.max=String(s.max)),s.placeholder&&(c.placeholder=s.placeholder);const e=n[s.key];"jsonKeys"===s.key&&Array.isArray(e)?c.value=e.join(", "):null!=e&&(c.value=String(e))}c.dataset.optionKey=s.key,c.addEventListener("input",()=>{const e=c.dataset.optionKey;if("textarea"===s.type)P[t.name][e]=c.value.split("\n").map(e=>e.trim()).filter(Boolean);else if("jsonKeys"===e)P[t.name][e]=c.value.split(/[,;]/).map(e=>e.trim()).filter(Boolean);else if("value"===e||"from"===e||"to"===e||"pattern"===e||"format"===e)P[t.name][e]=c.value;else{const n=Number(c.value);P[t.name][e]=""!==c.value&&Number.isFinite(n)?n:void 0}le(t,a)}),o.appendChild(c),e.appendChild(o)}),c.appendChild(e)}if(l&&"skip"!==n.strategy&&"null"!==n.strategy){const e=document.createElement("div");e.className="flex items-center justify-between gap-2 pt-1.5 text-[11px]",e.innerHTML='<span class="text-base-content/60">Simulate Nulls:</span><div class="flex items-center gap-1.5"><input type="range" min="0" max="100" step="5" value="'+(n.nullPct||0)+'" class="range range-xs range-primary w-24" /><span class="font-mono text-[11px] w-8 text-right text-base-content/75">'+(n.nullPct||0)+"%</span></div>";const s=e.querySelector('input[type="range"]'),o=e.querySelector("span:last-child");s.addEventListener("input",()=>{const e=Number(s.value);o.textContent=e+"%",P[t.name].nullPct=e>0?e:void 0,le(t,a)}),c.appendChild(e)}e.appendChild(c)}function re(e,t){const n=P[e.name]?P[e.name].strategy:null,a=("skip"===n||"null"===n)&&!e.pk&&e.notnull&&!e.hasDefault;t.textContent=a?'⚠️ Column is NOT NULL with no default — "'+n+'" rows may fail.':"",t.classList.toggle("hidden",!t.textContent)}function de(e){const t=Object.assign({},e.defaultPlan||{strategy:"skip"});P[e.name]=t;const n=c.querySelector('.app-card[data-column="'+CSS.escape(e.name)+'"]');if(n){const a=n.querySelector(".seed-strategy"),s=n.querySelector(".seed-options"),o=n.querySelector(".seed-note"),l=n.querySelector(".seed-warn");a&&(a.value=t.strategy),ce(s,e,t,n),o.textContent=oe(e,t.strategy),o.classList.toggle("hidden",!o.textContent),re(e,l),le(e,n)}}function ie(){const e=(o?o.value:"").toLowerCase().trim();let t=0;a.forEach(n=>{const a=c.querySelector('.app-card[data-column="'+CSS.escape(n.name)+'"]');if(!a)return;const s=!e||n.name.toLowerCase().includes(e)||(n.type||"").toLowerCase().includes(e);a.classList.toggle("hidden",!s),s&&(t+=1)}),r&&r.classList.toggle("hidden",t>0),d&&(d.textContent=t===a.length?`${a.length} columns`:`${t} of ${a.length} columns`)}function me(e){a.forEach(t=>{if("realistic"===e)de(t);else if("minimal"===e)t.pk||t.notnull||t.hasDefault?P[t.name]=Object.assign({},t.defaultPlan||{strategy:"skip"}):P[t.name]={strategy:"null"};else if("sparse"===e)P[t.name]=Object.assign({},t.defaultPlan||{strategy:"skip"}),t.pk||t.notnull||(P[t.name].nullPct=30);else if("numbered"===e){const e=(t.type||"").toUpperCase();e.startsWith("INTEGER")?P[t.name]={strategy:"sequence",start:1,step:1}:P[t.name]="TEXT"===e||""===e?{strategy:"pattern",pattern:"REC-####"}:Object.assign({},t.defaultPlan||{strategy:"skip"})}const n=c.querySelector('.app-card[data-column="'+CSS.escape(t.name)+'"]');if(n){const e=n.querySelector(".seed-strategy"),a=n.querySelector(".seed-options"),s=n.querySelector(".seed-note"),o=n.querySelector(".seed-warn");e&&(e.value=P[t.name].strategy),ce(a,t,P[t.name],n),s.textContent=oe(t,P[t.name].strategy),s.classList.toggle("hidden",!s.textContent),re(t,o),le(t,n)}}),window.UI&&UI.showToast&&UI.showToast("Applied preset: "+e,"info")}function pe(){const e=document.createElement("dialog");e.className="modal",e.id="seed-apply-all-dialog";const t=document.createElement("div");t.className="modal-box app-card max-w-md",t.innerHTML='<h3 class="text-base font-bold">Apply strategy to all columns</h3><p class="mt-1 text-xs text-base-content/60">Columns that do not support the chosen strategy are left untouched.</p><label class="field-label mt-4 text-xs font-medium">Strategy</label><select id="seed-apply-all-select" class="field-select w-full text-xs"></select><div class="mt-5 flex justify-end gap-2"><button id="seed-apply-all-cancel" type="button" class="btn btn-ghost btn-sm">Cancel</button><button id="seed-apply-all-go" type="button" class="btn btn-primary btn-sm">Apply to All</button></div>',e.appendChild(t),document.body.appendChild(e);const n=t.querySelector("#seed-apply-all-select");(function(){const e=new Map;return a.forEach(t=>(t.strategies||[]).forEach(t=>e.set(t.id,t.label))),[...e.entries()].map(([e,t])=>({id:e,label:t}))})().forEach(e=>{const t=document.createElement("option");t.value=e.id,t.textContent=e.label,n.appendChild(t)});const s=()=>e.close();return t.querySelector("#seed-apply-all-cancel").addEventListener("click",s),t.querySelector("#seed-apply-all-go").addEventListener("click",()=>{const e=n.value;a.forEach(t=>{if(!(t.strategies||[]).some(t=>t.id===e))return;P[t.name]=Object.assign({},P[t.name]||{},{strategy:e});const n=c.querySelector('.app-card[data-column="'+CSS.escape(t.name)+'"]');if(n){const a=n.querySelector(".seed-strategy"),s=n.querySelector(".seed-options"),o=n.querySelector(".seed-note"),l=n.querySelector(".seed-warn");a&&(a.value=e),ce(s,t,P[t.name],n),o.textContent=oe(t,e),o.classList.toggle("hidden",!o.textContent),re(t,l),le(t,n)}}),s(),window.UI&&UI.showToast&&UI.showToast("Strategy applied across columns.","success")}),e}function ue(e){if(i){if(!e||!e.length)return i.innerHTML="",void i.classList.add("hidden");i.innerHTML='<div class="alert alert-warning shadow-soft"><svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg><div class="text-xs space-y-1">'+e.map(e=>"<p>"+F(e)+"</p>").join("")+"</div></div>",i.classList.remove("hidden")}}function ge(e){"table"===e?(y.classList.add("tab-active"),x.classList.remove("tab-active"),b.classList.remove("hidden"),f.classList.add("hidden")):(x.classList.add("tab-active"),y.classList.remove("tab-active"),f.classList.remove("hidden"),b.classList.add("hidden"))}function be(e,t,n){const a=new Blob([e],{type:n}),s=URL.createObjectURL(a),o=document.createElement("a");o.href=s,o.download=t,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(s)}function fe(){if(!R||!R.previewRows)return void(window.UI&&UI.showError&&UI.showError("Generate preview data first."));const e=R.previewRows,n=a.map(e=>e.name);let s=n.map(e=>`"${e.replace(/"/g,'""')}"`).join(",")+"\n";e.forEach(e=>{s+=n.map(t=>{const n=e[t];return null==n?"":`"${String(n).replace(/"/g,'""')}"`}).join(",")+"\n"}),be(s,`${t.table}_seed_data.csv`,"text/csv;charset=utf-8;")}function he(){if(!R||!R.previewRows)return void(window.UI&&UI.showError&&UI.showError("Generate preview data first."));be(JSON.stringify(R.previewRows,null,2),`${t.table}_seed_data.json`,"application/json;charset=utf-8;")}function ye(){R&&R.sql?be(R.sql,`${t.table}_seed.sql`,"text/sql;charset=utf-8;"):window.UI&&UI.showError&&UI.showError("Generate preview data first.")}function xe(){const e={};return a.forEach(t=>{const n=P[t.name];if(!n||!n.strategy)return;const a={strategy:n.strategy};Object.keys(n).forEach(e=>{"strategy"!==e&&void 0!==n[e]&&(a[e]=n[e])}),e[t.name]=a}),e}function Ee(){const e=Number.parseInt(String(s?s.value:"10"),10);return Math.max(1,Math.min(t.maxRows||5e3,Number.isFinite(e)?e:10))}async function we(e=!1){const t=Ee(),a=xe(),s="/api/tables/"+n+"/seed";try{E&&(E.disabled=!0,E.classList.add("loading"));const n=await Api.post(s,{count:t,plan:a,truncate:e});window.UI&&UI.showToast&&UI.showToast(n.message||`Inserted ${n.inserted} row(s) successfully.`,"success"),ue(n.warnings||[])}catch(e){window.UI&&UI.showError&&UI.showError(e.message)}finally{E&&(E.disabled=!1,E.classList.remove("loading"))}}async function ve(){const e=Ee(),t=xe(),s="/api/tables/"+n+"/seed/generate";try{w&&(w.disabled=!0);const n=await Api.post(s,{count:e,plan:t});!function(e){if(R=e,m){if(m.classList.remove("hidden"),p&&(p.textContent=`${e.count} row(s) generated`),h&&(h.textContent=e.sql||""),u&&g){u.innerHTML="",g.innerHTML="";const t=document.createElement("tr"),n=document.createElement("th");n.className="w-12 text-center text-base-content/40",n.textContent="#",t.appendChild(n),a.forEach(e=>{const n=document.createElement("th");n.className="whitespace-nowrap font-mono",n.innerHTML=F(e.name)+' <span class="text-[10px] text-base-content/40 font-normal">('+F(e.type||"text")+")</span>",t.appendChild(n)}),u.appendChild(t),(e.previewRows||[]).forEach((e,t)=>{const n=document.createElement("tr"),s=document.createElement("td");s.className="text-center font-mono text-[10px] text-base-content/40",s.textContent=String(t+1),n.appendChild(s),a.forEach(t=>{const a=document.createElement("td");a.className="font-mono whitespace-nowrap max-w-xs truncate";const s=e[t.name];null===s?a.innerHTML='<span class="badge badge-xs badge-ghost text-base-content/40">NULL</span>':"(default)"===s?a.innerHTML='<span class="badge badge-xs badge-outline text-base-content/40">DEFAULT</span>':"boolean"==typeof s?a.innerHTML=s?'<span class="badge badge-xs badge-success">TRUE</span>':'<span class="badge badge-xs badge-ghost">FALSE</span>':a.textContent=String(s),n.appendChild(a)}),g.appendChild(n)})}m.scrollIntoView({behavior:"smooth",block:"nearest"})}}(n),ue(n.warnings||[])}catch(e){window.UI&&UI.showError&&UI.showError(e.message)}finally{w&&(w.disabled=!1)}}!function(){a.forEach(e=>{!function(e){const t=document.createElement("div");t.className="app-card p-4 flex flex-col justify-between transition-shadow hover:shadow-md",t.dataset.column=e.name,t.dataset.type=(e.type||"").toLowerCase();const n=document.createElement("div");n.className="space-y-3";const a=document.createElement("div");a.className="flex items-start justify-between gap-2";const s=document.createElement("div");s.className="min-w-0";const o=document.createElement("div");o.className="flex flex-wrap items-center gap-1.5";const l=document.createElement("span");if(l.className="font-mono text-sm font-bold tracking-tight text-base-content",l.textContent=e.name,o.appendChild(l),e.pk){const e=document.createElement("span");e.className="badge badge-xs badge-warning font-mono font-bold",e.textContent="PK",o.appendChild(e)}if(e.notnull){const e=document.createElement("span");e.className="badge badge-xs badge-info font-mono",e.textContent="NOT NULL",o.appendChild(e)}else{const e=document.createElement("span");e.className="badge badge-xs badge-ghost text-base-content/40 font-mono",e.textContent="NULLABLE",o.appendChild(e)}if(e.unique){const e=document.createElement("span");e.className="badge badge-xs badge-secondary font-mono",e.textContent="UNIQUE",o.appendChild(e)}if(e.hasDefault){const e=document.createElement("span");e.className="badge badge-xs badge-ghost font-mono",e.textContent="DEFAULT",o.appendChild(e)}if(e.fk){const t=document.createElement("span");t.className="badge badge-xs badge-accent font-mono",t.textContent="FK → "+e.fk.table,o.appendChild(t)}const r=document.createElement("p");r.className="mt-0.5 text-[11px] font-mono text-base-content/45",r.textContent=e.type||"untyped",s.appendChild(o),s.appendChild(r),a.appendChild(s),n.appendChild(a);const d=document.createElement("select");d.className="field-select seed-strategy w-full !py-1.5 !text-xs font-medium",d.dataset.column=e.name;const i={};(e.strategies||[]).forEach(e=>{const t=e.category||"custom_control";i[t]||(i[t]=[]),i[t].push(e)}),Object.keys(i).forEach(e=>{const t=document.createElement("optgroup");t.label=se[e]||e,i[e].forEach(e=>{const n=document.createElement("option");n.value=e.id,n.textContent=e.label,t.appendChild(n)}),d.appendChild(t)}),n.appendChild(d);const m=document.createElement("div");m.className="flex items-center justify-between gap-2 px-2.5 py-1.5 rounded-lg bg-base-200/50 text-[11px]",m.innerHTML='<div class="flex items-center gap-1.5 min-w-0 overflow-hidden text-ellipsis"><span class="text-base-content/50 shrink-0 font-medium">Sample:</span><span class="seed-sample-value font-mono text-primary font-medium truncate">...</span></div><button type="button" class="seed-sample-refresh btn btn-ghost btn-xs btn-square h-5 w-5 shrink-0" title="Reroll sample">🎲</button>',m.querySelector(".seed-sample-refresh").addEventListener("click",()=>le(e,t)),n.appendChild(m);const p=document.createElement("div");p.className="seed-options",n.appendChild(p),t.appendChild(n);const u=document.createElement("div");u.className="mt-3 pt-2";const g=document.createElement("p");g.className="seed-note hidden text-[11px] leading-relaxed text-base-content/45",u.appendChild(g);const b=document.createElement("p");b.className="seed-warn hidden text-[11px] font-medium text-warning flex items-center gap-1",u.appendChild(b),t.appendChild(u),d.addEventListener("change",()=>{const n=d.value;P[e.name]=Object.assign({},P[e.name]||{},{strategy:n}),ce(p,e,P[e.name],t),g.textContent=oe(e,n),g.classList.toggle("hidden",!g.textContent),re(e,b),le(e,t)}),c.appendChild(t)}(e),de(e)}),ie(),o&&o.addEventListener("input",ie),w&&w.addEventListener("click",ve),E&&E.addEventListener("click",()=>{l&&l.checked?A&&A.showModal():we(!1)}),Y&&Y.addEventListener("click",()=>{A&&A.close(),we(!0)}),D&&D.addEventListener("click",()=>{A&&A.close()}),v&&v.addEventListener("click",()=>a.forEach(de)),k&&k.addEventListener("click",()=>{a.forEach(e=>{const t=c.querySelector('.app-card[data-column="'+CSS.escape(e.name)+'"]');t&&le(e,t)})}),L&&L.addEventListener("click",()=>{(document.getElementById("seed-apply-all-dialog")||pe()).showModal()});const e=document.getElementById("preset-realistic"),t=document.getElementById("preset-minimal"),n=document.getElementById("preset-sparse"),r=document.getElementById("preset-numbered");e&&e.addEventListener("click",()=>me("realistic")),t&&t.addEventListener("click",()=>me("minimal")),n&&n.addEventListener("click",()=>me("sparse")),r&&r.addEventListener("click",()=>me("numbered")),U&&U.addEventListener("click",()=>{B&&T&&(q.textContent="Export Seed Plan",T.value=JSON.stringify(xe(),null,2),O.classList.add("hidden"),B.showModal())}),M&&M.addEventListener("click",()=>{B&&T&&(q.textContent="Import Seed Plan",T.value="",T.placeholder="Paste plan JSON here...",O.classList.remove("hidden"),B.showModal())}),j&&j.addEventListener("click",()=>{B&&B.close()}),O&&O.addEventListener("click",()=>{try{const e=JSON.parse(T.value);if("object"!=typeof e||!e)throw new Error("Invalid JSON format");a.forEach(t=>{if(e[t.name]){P[t.name]=e[t.name];const n=c.querySelector('.app-card[data-column="'+CSS.escape(t.name)+'"]');if(n){const e=n.querySelector(".seed-strategy"),a=n.querySelector(".seed-options"),s=n.querySelector(".seed-note"),o=n.querySelector(".seed-warn");e&&(e.value=P[t.name].strategy),ce(a,t,P[t.name],n),s.textContent=oe(t,P[t.name].strategy),s.classList.toggle("hidden",!s.textContent),re(t,o),le(t,n)}}}),B&&B.close(),window.UI&&UI.showToast&&UI.showToast("Seed plan configuration applied!","success")}catch(e){window.UI&&UI.showError&&UI.showError("Failed to parse JSON: "+e.message)}}),y&&y.addEventListener("click",()=>ge("table")),x&&x.addEventListener("click",()=>ge("sql")),C&&C.addEventListener("click",()=>{h&&h.textContent&&navigator.clipboard.writeText(h.textContent).then(()=>UI.showToast("Copied SQL to clipboard.","success")).catch(()=>UI.showError("Could not copy SQL."))}),S&&S.addEventListener("click",ye),I&&I.addEventListener("click",fe),N&&N.addEventListener("click",he),document.querySelectorAll(".seed-quick").forEach(e=>{e.addEventListener("click",()=>{s&&(s.value=e.dataset.n)})})}()}();
1
+ !function(){"use strict";const e=document.getElementById("seed-config");if(!e)return;const t=JSON.parse(e.textContent),n=encodeURIComponent(t.table),a=t.columns||[];let s=1,l=null;const c={},i={};let d=null,r=null,o=null;c[t.table]={},a.forEach(e=>{c[t.table][e.name]=Object.assign({},e.defaultPlan||{strategy:"skip"})});const m=document.getElementById("step-nav-1"),p=document.getElementById("step-nav-2"),u=document.getElementById("step-nav-3"),g=document.getElementById("step-content-1"),b=document.getElementById("step-content-2"),h=document.getElementById("step-content-3"),y=document.getElementById("btn-select-single"),v=document.getElementById("btn-select-chain"),x=document.getElementById("btn-next-1"),f=document.getElementById("btn-next-2"),w=document.getElementById("btn-prev-2"),E=document.getElementById("btn-prev-3"),L=document.getElementById("config-single"),k=document.getElementById("config-chain"),I=document.getElementById("seed-columns"),$=document.getElementById("seed-count"),B=document.getElementById("chain-scope"),T=document.getElementById("chain-pipeline-container"),M=document.getElementById("chain-summary-badge"),S=document.getElementById("seed-preview-thead"),C=document.getElementById("seed-preview-tbody"),H=document.getElementById("seed-chain-table-tabs"),U=document.getElementById("seed-refresh-preview"),N=document.getElementById("seed-insert"),A=document.getElementById("seed-truncate"),j=document.getElementById("seed-truncate-dialog"),R=document.getElementById("seed-truncate-confirm"),q=document.getElementById("seed-truncate-cancel"),P=document.getElementById("seed-feedback");function O(e){return window.UI&&UI.escapeHtml?UI.escapeHtml(e):String(null==e?"":e)}function z(e){P&&(e&&0!==e.length?(P.innerHTML=`\n <div class="alert alert-warning text-xs">\n <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>\n <div class="space-y-1">${e.map(e=>`<p>${O(e)}</p>`).join("")}</div>\n </div>\n `,P.classList.remove("hidden")):P.classList.add("hidden"))}const D=["Sombat","Minh","Hiroshi","William","Ravi","Zara","Elena","Liam","Mei","Noah"],F=["Chen","Smith","Thompson","Shah","Yamamoto","Patel","Garcia","Kim","Nguyen","Davis"],K=["example.com","acme.io","tech.asia","gmail.com","startup.io"],_=["active","pending","completed","archived","draft"];function G(e){return e[Math.floor(Math.random()*e.length)]}function J(e,t){return Math.floor(Math.random()*(t-e+1))+e}function Q(e){s=e,m.classList.toggle("step-primary",e>=1),p.classList.toggle("step-primary",e>=2),u.classList.toggle("step-primary",e>=3),g.classList.toggle("hidden",1!==e),b.classList.toggle("hidden",2!==e),h.classList.toggle("hidden",3!==e),2===e&&(L.classList.toggle("hidden","single"!==l),k.classList.toggle("hidden","chain"!==l),"single"===l&&0===I.children.length?(I.innerHTML="",a.forEach(e=>{const n=c[t.table][e.name],a=document.createElement("div");a.className="seed-col-card",a.dataset.strategyType="skip"===n.strategy?"skip":"null"===n.strategy?"null":"fixed"===n.strategy||"list"===n.strategy||"pattern"===n.strategy?"fixed":"generate";let s="";e.pk&&(s+='<span class="badge badge-xs badge-warning">PK</span>'),e.notnull&&(s+='<span class="badge badge-xs badge-info">REQ</span>'),a.innerHTML=`\n <div class="flex items-start justify-between">\n <div class="min-w-0">\n <div class="flex items-center gap-2">\n <span class="font-bold font-mono text-sm truncate">${O(e.name)}</span>\n ${s}\n </div>\n <div class="text-[10px] text-base-content/50 uppercase font-mono mt-0.5">${O(e.type)}</div>\n </div>\n </div>\n <div>\n <select class="seed-strategy field-select !py-1 text-xs w-full">\n ${(e.strategies||[]).map(e=>`<option value="${e.id}" ${e.id===n.strategy?"selected":""}>${O(e.label)}</option>`).join("")}\n </select>\n </div>\n <div class="seed-options space-y-2"></div>\n <div class="pt-2 border-t border-base-200 flex justify-between items-center text-[11px]">\n <span class="text-base-content/50 uppercase font-bold tracking-wider">Sample</span>\n <code class="seed-sample-value font-mono text-xs text-primary bg-primary/8 rounded px-1.5 py-0.5 truncate max-w-[150px] inline-block"></code>\n </div>\n `;const l=a.querySelector(".seed-strategy"),i=a.querySelector(".seed-options"),d=a.querySelector(".seed-sample-value"),r=()=>{d.textContent=function(e,t){const n=t.strategy;if("skip"===n)return"(auto/default)";if("null"===n)return"NULL";if(t.nullPct&&J(1,100)<=t.nullPct&&!e.pk&&!e.notnull)return"NULL (simulated)";let a="";switch(n){case"fixed":a=t.value||"";break;case"list":a=t.values&&t.values.length?G(t.values):"(empty)";break;case"pattern":a="PAT-"+J(1e3,9999);break;case"first":a=G(D);break;case"last":a=G(F);break;case"fullname":a=G(D)+" "+G(F);break;case"email":a=G(D).toLowerCase()+"@"+G(K);break;case"status":a=G(_);break;case"int":a=String(J(t.min??1,t.max??1e3));break;case"decimal":a=(Math.random()*((t.max??1e3)-(t.min??1))+(t.min??1)).toFixed(t.precision??2);break;case"bool":a=G(["1","0"]);break;case"sequence":a=String(t.start||1);break;case"uuid":a="550e8400-e29b-41d4-a716-"+J(1e11,999999999999);break;case"fk":a="[FK -> "+(e.fk?e.fk.table:"?")+"]";break;default:a="(sample)"}return a}(e,c[t.table][e.name])};l.addEventListener("change",()=>{const n=l.value;c[t.table][e.name].strategy=n,a.dataset.strategyType="skip"===n?"skip":"null"===n?"null":"fixed"===n||"list"===n||"pattern"===n?"fixed":"generate",W(i,e,c[t.table][e.name],r),r()}),W(i,e,n,r),r(),I.appendChild(a)})):"chain"!==l||d||Y()),3===e&&Z()}function V(e){l=e,y.classList.toggle("border-primary","single"===l),y.classList.toggle("bg-primary/5","single"===l),v.classList.toggle("border-secondary","chain"===l),v.classList.toggle("bg-secondary/5","chain"===l),x.disabled=!1}function W(e,t,n,a){e.innerHTML="";const s=n.strategy,l=(t,s,l="text",c={})=>{const i=document.createElement("label");i.className="block",i.innerHTML=`<span class="block text-[10px] uppercase font-bold text-base-content/50 mb-1">${O(t)}</span>`;const d=document.createElement("textarea"===l?"textarea":"input");"textarea"!==l&&(d.type=l),d.className="textarea"===l?"field-input w-full text-xs font-mono !py-1 h-16":"field-input w-full text-xs font-mono !py-1",c.min&&(d.min=c.min),c.max&&(d.max=c.max);const r=n[s];"textarea"===l&&Array.isArray(r)?d.value=r.join("\\n"):void 0!==r&&(d.value=r),d.addEventListener("input",()=>{n[s]="textarea"===l?d.value.split("\\n").map(e=>e.trim()).filter(Boolean):"number"===l?Number(d.value):d.value,a()}),i.appendChild(d),e.appendChild(i)};if("fixed"===s&&l("Value","value"),"list"===s&&l("Choices (one per line)","values","textarea"),"int"===s||"decimal"===s){const t=document.createElement("div");t.className="grid grid-cols-2 gap-2",e.appendChild(t);const n=e;e=t,l("Min","min","number"),l("Max","max","number"),e=n}}async function Y(){const e=B.value;T.innerHTML='<div class="py-8 text-center text-sm text-base-content/50">Analyzing foreign keys...</div>';try{d=await Api.get(`/api/tables/${n}/seed/chain?scope=${e}`),M.textContent=`${d.tables.length} Tables`,T.innerHTML='<div class="relative pl-6 ml-2 space-y-6 border-l-2 border-base-200/50"></div>';const a=T.firstChild;d.tables.forEach((e,n)=>{i[e.name]||(i[e.name]=e.suggestedCount||10),c[e.name]||(c[e.name]={},(e.columns||[]).forEach(t=>{c[e.name][t.name]=Object.assign({},t.defaultPlan||{strategy:"skip"})}));const s=document.createElement("div"),l=e.name===t.table;s.className="relative group",s.innerHTML=`\n \x3c!-- Timeline Node Dot --\x3e\n <div class="absolute -left-[35px] top-1/2 -translate-y-1/2 w-4 h-4 rounded-full border-4 border-base-100 ${l?"bg-secondary ring-2 ring-secondary/30 shadow-[0_0_8px_rgba(var(--secondary),0.5)]":"bg-primary ring-2 ring-base-200"} z-10 transition-all duration-300 group-hover:scale-125"></div>\n \n <div class="app-card p-4 flex items-center justify-between shadow-sm hover:shadow-md transition-shadow ${l?"bg-secondary/5 border-secondary/20":"bg-base-100 hover:bg-base-200/30"} border">\n <div class="flex items-center gap-3">\n <span class="w-7 h-7 rounded-lg ${l?"bg-secondary/20 text-secondary":"bg-base-200 text-base-content/60"} text-xs flex items-center justify-center font-bold font-mono">${n+1}</span>\n <div>\n <div class="font-bold text-[15px] font-mono flex items-center gap-2">\n ${O(e.name)}\n ${l?'<span class="badge badge-xs badge-secondary font-bold uppercase tracking-wider text-[9px] px-1.5 h-4">Target</span>':""}\n </div>\n <div class="text-[10px] text-base-content/50 uppercase mt-1 flex gap-3 font-semibold tracking-wide">\n <span title="Tables this table depends on"><span class="text-base-content/30 mr-0.5">←</span> ${e.parents.length} Parents</span>\n <span title="Tables that depend on this table"><span class="text-base-content/30 mr-0.5">→</span> ${e.children.length} Children</span>\n </div>\n </div>\n </div>\n <div>\n <label class="text-[9px] uppercase tracking-widest font-bold text-base-content/40 block mb-1">Rows to generate</label>\n <input type="number" min="1" max="5000" value="${i[e.name]}" class="chain-row-input field-input !py-1 w-24 text-right font-mono font-bold text-sm bg-base-100/80 shadow-inner" data-table="${O(e.name)}" />\n </div>\n </div>\n `,s.querySelector(".chain-row-input").addEventListener("input",t=>{i[e.name]=Number(t.target.value)||10}),a.appendChild(s)})}catch(e){T.innerHTML=`<div class="alert alert-error">${O(e.message)}</div>`}}async function Z(){C.innerHTML='<tr><td colspan="100" class="text-center py-10 opacity-50">Generating preview...</td></tr>',P.classList.add("hidden"),H.classList.add("hidden"),N.disabled=!0;try{if("single"===l){const e=Number($.value)||10,a=c[t.table]||{};r=await Api.post(`/api/tables/${n}/seed/generate`,{count:e,plan:a}),X(t.table,r.previewRows||[])}else{const e=B.value;r=await Api.post(`/api/tables/${n}/seed/chain/generate`,{scope:e,counts:i,plans:c});const t=r.executionOrder||Object.keys(r.tableResults||{});o&&t.includes(o)||(o=t[0]),H.classList.remove("hidden"),H.innerHTML="",t.forEach(e=>{const t=r.tableResults[e],n=document.createElement("button");n.dataset.table=e,n.className="tab tab-bordered h-10 text-xs font-bold font-mono whitespace-nowrap shrink-0 "+(e===o?"tab-active text-primary":""),n.innerHTML=`${O(e)} <span class="badge badge-xs ml-2 ${e===o?"badge-primary":"badge-ghost"}">${t?.rows?.length||0}</span>`,H.appendChild(n)}),Array.from(H.children).forEach(e=>{e.onclick=t=>{t.preventDefault(),o=e.dataset.table,Array.from(H.children).forEach(e=>{e.classList.remove("tab-active","text-primary"),e.querySelector(".badge").classList.replace("badge-primary","badge-ghost")}),e.classList.add("tab-active","text-primary"),e.querySelector(".badge").classList.replace("badge-ghost","badge-primary");const n=r.tableResults[o];X(o,n?.previewRows||[])}});const a=r.tableResults[o];X(o,a?.previewRows||[])}N.disabled=!1,z(r.warnings||[])}catch(e){C.innerHTML=`<tr><td colspan="100" class="text-center py-10 text-error font-bold">${O(e.message)}</td></tr>`,window.UI&&UI.showError&&UI.showError(e.message)}}function X(e,t){if(S.innerHTML="",C.innerHTML="",!t||0===t.length)return void(C.innerHTML='<tr><td colspan="100" class="text-center py-10 text-base-content/40">No rows generated.</td></tr>');const n=Object.keys(t[0]),a=document.createElement("tr");a.innerHTML='<th class="w-10 text-center">#</th>'+n.map(e=>`<th>${O(e)}</th>`).join(""),S.appendChild(a),t.forEach((e,t)=>{const a=document.createElement("tr");let s=`<td class="text-center text-base-content/40">${t+1}</td>`;n.forEach(t=>{const n=e[t];s+=null===n?'<td><span class="badge badge-xs badge-ghost opacity-50">NULL</span></td>':"(default)"===n||"(auto)"===n?`<td><span class="badge badge-xs badge-outline opacity-50">${O(n)}</span></td>`:"boolean"==typeof n?`<td><span class="badge badge-xs ${n?"badge-success":"badge-ghost"}">${n?"TRUE":"FALSE"}</span></td>`:`<td class="truncate max-w-[200px]">${O(String(n))}</td>`}),a.innerHTML=s,C.appendChild(a)})}async function ee(){const e=A.checked;N.disabled=!0,N.classList.add("loading");try{if("single"===l){const a=Number($.value)||10,s=c[t.table]||{},l=await Api.post(`/api/tables/${n}/seed`,{count:a,plan:s,truncate:e});window.UI&&UI.showToast&&UI.showToast(l.message||`Inserted ${l.inserted} rows.`,"success"),z(l.warnings||[])}else{const t=B.value,a=await Api.post(`/api/tables/${n}/seed/chain`,{scope:t,counts:i,plans:c,truncate:e});window.UI&&UI.showToast&&UI.showToast(a.message||`Seeded ${a.totalInserted} rows across tables.`,"success"),z(a.warnings||[])}}catch(e){window.UI&&UI.showError&&UI.showError(e.message)}finally{N.disabled=!1,N.classList.remove("loading"),j&&j.close()}}y.addEventListener("click",()=>V("single")),v.addEventListener("click",()=>V("chain")),x.addEventListener("click",()=>Q(2)),w.addEventListener("click",()=>Q(1)),f.addEventListener("click",()=>Q(3)),E.addEventListener("click",()=>Q(2)),U.addEventListener("click",()=>Z()),N.addEventListener("click",()=>{A.checked?j.showModal():ee()}),R.addEventListener("click",()=>ee()),q.addEventListener("click",()=>j.close()),B&&B.addEventListener("change",()=>Y()),document.querySelectorAll(".chain-scale-btn").forEach(e=>{e.addEventListener("click",()=>{document.querySelectorAll(".chain-scale-btn").forEach(e=>e.classList.remove("btn-neutral")),e.classList.add("btn-neutral");const t=Number(e.dataset.scale);d&&d.tables.forEach(e=>{i[e.name]=Math.max(1,Math.round((e.suggestedCount||10)*t));const n=T.querySelector(`.chain-row-input[data-table="${CSS.escape(e.name)}"]`);n&&(n.value=i[e.name])})})}),Q(1)}();
@@ -0,0 +1 @@
1
+ import{type ApiContext} from './helpers';import type {Router} from 'express';export declare function registerErdRoutes(router:Router,ctx:ApiContext):void;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerErdRoutes = registerErdRoutes;
4
+ const helpers_1 = require("./helpers");
5
+ function registerErdRoutes(router, ctx) {
6
+ const { db } = ctx;
7
+ router.get('/api/erd', async (_req, res) => {
8
+ try {
9
+ const tablesResult = await db.listTables();
10
+ if (!tablesResult.success || !tablesResult.data) {
11
+ return (0, helpers_1.fail)(res, tablesResult.error ?? 'Failed to list tables.', 500);
12
+ }
13
+ const tableNames = tablesResult.data.map((t) => t.name);
14
+ const tableDetails = await Promise.all(tableNames.map(async (name) => {
15
+ const infoResult = await db.getTableInfo(name);
16
+ if (!infoResult.success || !infoResult.data) {
17
+ return {
18
+ name,
19
+ columns: [],
20
+ foreignKeys: [],
21
+ primaryKey: [],
22
+ };
23
+ }
24
+ const { columns, foreignKeys, primaryKey } = infoResult.data;
25
+ return {
26
+ name,
27
+ columns: columns.map((c) => ({
28
+ name: c.name,
29
+ type: c.type,
30
+ pk: c.pk,
31
+ notnull: c.notnull,
32
+ dflt_value: c.dflt_value,
33
+ })),
34
+ foreignKeys: foreignKeys.map((fk) => ({
35
+ from: fk.from,
36
+ table: fk.table,
37
+ to: fk.to ?? '',
38
+ on_delete: fk.on_delete,
39
+ on_update: fk.on_update,
40
+ })),
41
+ primaryKey,
42
+ };
43
+ }));
44
+ (0, helpers_1.ok)(res, { tables: tableDetails });
45
+ }
46
+ catch (err) {
47
+ (0, helpers_1.fail)(res, err.message ?? 'Unknown error.', 500);
48
+ }
49
+ });
50
+ }
@@ -1 +1 @@
1
- import type {Request,Response} from 'express';import type {IDatabase,TableInfoData,WhereClause} from '../../db/index';import type {Logger} from '../../utils/logger';import{type ColumnPlan} from '../../data/index';export interface ApiContext{db:IDatabase;logger:Logger;}export declare function ok(res:Response,data:unknown,status?:number):Response;export declare function fail(res:Response,error:string,status?:number):Response;export declare function wrap(fn:(req:Request,res:Response)=>unknown):(req:Request,res:Response)=>Promise<void>;export declare function requireTable(db:IDatabase,table:string):Promise<TableInfoData|null>;export declare function buildFields(info:TableInfoData,values:Record<string,unknown>,opts?:{excludePk?:boolean;}):{column:string;value:unknown;}[];export declare function buildUpdateFields(info:TableInfoData,values:Record<string,unknown>,nulls:string[]):{column:string;value:unknown;}[];export declare function pkWhere(info:TableInfoData,encodedId:string):{column:string;value:unknown;}[]|null;export declare const MAX_BULK_ROWS=1000;export declare function parseSeedRequest(body:unknown):{count:number;plan:Record<string,ColumnPlan>;truncate:boolean;};export declare function resolvePkRows(info:TableInfoData,ids:unknown):WhereClause[][]|null;export declare function computeBulkImpact(db:IDatabase,info:TableInfoData,wheres:WhereClause[][]):Promise<{references:{table:string; from :string;to:string;count:number;}[];total:number;}>;
1
+ import type {Request,Response} from 'express';import type {IDatabase,TableInfoData,WhereClause} from '../../db/index';import type {Logger} from '../../utils/logger';import{type ColumnPlan,type ErChainScope} from '../../data/index';export interface ApiContext{db:IDatabase;logger:Logger;}export declare function ok(res:Response,data:unknown,status?:number):Response;export declare function fail(res:Response,error:string,status?:number,details?:Record<string,unknown>|null):Response;export declare function wrap(fn:(req:Request,res:Response)=>unknown):(req:Request,res:Response)=>Promise<void>;export declare function requireTable(db:IDatabase,table:string):Promise<TableInfoData|null>;export declare function buildFields(info:TableInfoData,values:Record<string,unknown>,opts?:{excludePk?:boolean;}):{column:string;value:unknown;}[];export declare function buildUpdateFields(info:TableInfoData,values:Record<string,unknown>,nulls:string[]):{column:string;value:unknown;}[];export declare function pkWhere(info:TableInfoData,encodedId:string):{column:string;value:unknown;}[]|null;export declare const MAX_BULK_ROWS=1000;export declare function parseSeedRequest(body:unknown):{count:number;plan:Record<string,ColumnPlan>;truncate:boolean;};export declare function parseChainSeedRequest(body:unknown):{scope:ErChainScope;counts:Record<string,number>;plans:Record<string,Record<string,ColumnPlan>>;truncate:boolean;};export declare function resolvePkRows(info:TableInfoData,ids:unknown):WhereClause[][]|null;export declare function computeBulkImpact(db:IDatabase,info:TableInfoData,wheres:WhereClause[][]):Promise<{references:{table:string; from :string;to:string;count:number;}[];total:number;}>;
@@ -1 +1,163 @@
1
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.MAX_BULK_ROWS=void 0,exports.ok=function(res,data,status=200){return res.status(status).json({success:!0,data:data})},exports.fail=fail,exports.wrap=function(fn){return async(req,res)=>{try{await fn(req,res)}catch(err){fail(res,(0,common_1.errorMessage)(err),500)}}},exports.requireTable=async function(db,table){const info=await db.getTableInfo(table);return info.success&&info.data&&0!==info.data.columns.length?info.data:null},exports.buildFields=buildFields,exports.buildUpdateFields=function(info,values,nulls){const fields=buildFields(info,values,{excludePk:!0}),typeMap=new Map(info.columns.map(c=>[c.name,c.type])),pkSet=new Set(info.primaryKey);for(const col of nulls)pkSet.has(col)||!typeMap.has(col)||fields.some(f=>f.column===col)||fields.push({column:col,value:null});return fields},exports.pkWhere=pkWhere,exports.parseSeedRequest=function(body){const b=body??{},count=Math.max(1,Math.min(index_1.MAX_SEED_ROWS,Number.parseInt(String(b.count??"10"),10)||10)),truncate=Boolean(b.truncate),plan={};if(b.plan&&"object"==typeof b.plan)for(const[k,v]of Object.entries(b.plan)){const p=(0,index_1.sanitizeColumnPlan)(v);p&&(plan[k]=p)}return{count:count,plan:plan,truncate:truncate}},exports.resolvePkRows=function(info,ids){const list=Array.isArray(ids)?ids:null==ids?[]:[ids],out=[];for(const id of list){const where=pkWhere(info,String(id));where&&out.push(where)}return out.length?out:null},exports.computeBulkImpact=async function(db,info,wheres){const[rowsR,refInfoR]=await Promise.all([db.getRowsByPks(info.table,wheres),db.getReferencingTables(info.table)]),rows=rowsR.data??[],referencing=refInfoR.data??[];if(!rows.length||!referencing.length)return{references:[],total:0};const queries=referencing.flatMap(item=>item.refs.map(async ref=>{const targetCol=ref.to||info.primaryKey[0];if(!targetCol)return null;const values=Array.from(new Set(rows.map(r=>r[targetCol]).filter(v=>null!=v)));if(!values.length)return null;try{const placeholders=values.map(()=>"?").join(", "),sql=`SELECT COUNT(*) AS c FROM ${(0,generator_1.quoteIdentifier)(item.table)} WHERE ${(0,generator_1.quoteIdentifier)(ref.from)} IN (${placeholders})`,countR=await db.all(sql,values),count=countR.success&&countR.data?.[0]?Number(countR.data[0].c??0):0;return count>0?{table:item.table,from:ref.from,to:targetCol,count:count}:null}catch{return null}})),results=(await Promise.all(queries)).filter(r=>null!==r),total=results.reduce((acc,r)=>acc+r.count,0);return{references:results,total:total}};const common_1=require("../../utils/common"),index_1=require("../../data/index"),generator_1=require("../../sql/generator");function fail(res,error,status=400){return res.status(status).json({success:!1,error:error})}function buildFields(info,values,opts={}){const typeMap=new Map(info.columns.map(c=>[c.name,c.type])),pkSet=new Set(info.primaryKey),fields=[];for(const[col,raw]of Object.entries(values??{})){if(!typeMap.has(col)||opts.excludePk&&pkSet.has(col))continue;const val=(0,common_1.coerceFormValue)(raw,typeMap.get(col));null!==val&&fields.push({column:col,value:val})}return fields}function pkWhere(info,encodedId){const vals=(0,common_1.decodePk)(encodedId);if(!info.primaryKey.length){if(1===vals.length&&""!==vals[0]){const num=Number(vals[0]);return[{column:"_rowid_",value:Number.isFinite(num)?num:vals[0]}]}return null}if(vals.length!==info.primaryKey.length)return null;const typeMap=new Map(info.columns.map(c=>[c.name,c.type]));return info.primaryKey.map((col,i)=>({column:col,value:(0,common_1.coerceFormValue)(vals[i],typeMap.get(col))}))}exports.MAX_BULK_ROWS=1e3;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_BULK_ROWS = void 0;
4
+ exports.ok = ok;
5
+ exports.fail = fail;
6
+ exports.wrap = wrap;
7
+ exports.requireTable = requireTable;
8
+ exports.buildFields = buildFields;
9
+ exports.buildUpdateFields = buildUpdateFields;
10
+ exports.pkWhere = pkWhere;
11
+ exports.parseSeedRequest = parseSeedRequest;
12
+ exports.parseChainSeedRequest = parseChainSeedRequest;
13
+ exports.resolvePkRows = resolvePkRows;
14
+ exports.computeBulkImpact = computeBulkImpact;
15
+ const common_1 = require("../../utils/common");
16
+ const index_1 = require("../../data/index");
17
+ const generator_1 = require("../../sql/generator");
18
+ function ok(res, data, status = 200) {
19
+ return res.status(status).json({ success: true, data });
20
+ }
21
+ function fail(res, error, status = 400, details) {
22
+ return res.status(status).json({ success: false, error, ...(details ? { details } : {}) });
23
+ }
24
+ function wrap(fn) {
25
+ return async (req, res) => {
26
+ try {
27
+ await fn(req, res);
28
+ }
29
+ catch (err) {
30
+ fail(res, (0, common_1.errorMessage)(err), 500);
31
+ }
32
+ };
33
+ }
34
+ async function requireTable(db, table) {
35
+ const info = await db.getTableInfo(table);
36
+ if (!info.success || !info.data || info.data.columns.length === 0)
37
+ return null;
38
+ return info.data;
39
+ }
40
+ function buildFields(info, values, opts = {}) {
41
+ const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
42
+ const pkSet = new Set(info.primaryKey);
43
+ const fields = [];
44
+ for (const [col, raw] of Object.entries(values ?? {})) {
45
+ if (!typeMap.has(col) || (opts.excludePk && pkSet.has(col)))
46
+ continue;
47
+ const val = (0, common_1.coerceFormValue)(raw, typeMap.get(col));
48
+ if (val !== null)
49
+ fields.push({ column: col, value: val });
50
+ }
51
+ return fields;
52
+ }
53
+ function buildUpdateFields(info, values, nulls) {
54
+ const fields = buildFields(info, values, { excludePk: true });
55
+ const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
56
+ const pkSet = new Set(info.primaryKey);
57
+ for (const col of nulls) {
58
+ if (!pkSet.has(col) && typeMap.has(col) && !fields.some((f) => f.column === col)) {
59
+ fields.push({ column: col, value: null });
60
+ }
61
+ }
62
+ return fields;
63
+ }
64
+ function pkWhere(info, encodedId) {
65
+ const vals = (0, common_1.decodePk)(encodedId);
66
+ if (!info.primaryKey.length) {
67
+ if (vals.length === 1 && vals[0] !== '') {
68
+ const num = Number(vals[0]);
69
+ return [{ column: '_rowid_', value: Number.isFinite(num) ? num : vals[0] }];
70
+ }
71
+ return null;
72
+ }
73
+ if (vals.length !== info.primaryKey.length)
74
+ return null;
75
+ const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
76
+ return info.primaryKey.map((col, i) => ({ column: col, value: (0, common_1.coerceFormValue)(vals[i], typeMap.get(col)) }));
77
+ }
78
+ exports.MAX_BULK_ROWS = 1000;
79
+ function parseSeedRequest(body) {
80
+ const b = (body ?? {});
81
+ const count = Math.max(1, Math.min(index_1.MAX_SEED_ROWS, Number.parseInt(String(b.count ?? '10'), 10) || 10));
82
+ const truncate = Boolean(b.truncate);
83
+ const plan = {};
84
+ if (b.plan && typeof b.plan === 'object') {
85
+ for (const [k, v] of Object.entries(b.plan)) {
86
+ const p = (0, index_1.sanitizeColumnPlan)(v);
87
+ if (p)
88
+ plan[k] = p;
89
+ }
90
+ }
91
+ return { count, plan, truncate };
92
+ }
93
+ function parseChainSeedRequest(body) {
94
+ const b = (body ?? {});
95
+ const scope = b.scope === 'ancestors' || b.scope === 'descendants' || b.scope === 'all' || b.scope === 'single'
96
+ ? b.scope
97
+ : 'chain';
98
+ const truncate = Boolean(b.truncate);
99
+ const counts = {};
100
+ if (b.counts && typeof b.counts === 'object') {
101
+ for (const [table, rawN] of Object.entries(b.counts)) {
102
+ const n = Number.parseInt(String(rawN), 10);
103
+ if (Number.isFinite(n) && n > 0)
104
+ counts[table] = Math.min(2000, n);
105
+ }
106
+ }
107
+ const plans = {};
108
+ if (b.plans && typeof b.plans === 'object') {
109
+ for (const [table, tablePlans] of Object.entries(b.plans)) {
110
+ if (tablePlans && typeof tablePlans === 'object') {
111
+ const pMap = {};
112
+ for (const [col, v] of Object.entries(tablePlans)) {
113
+ const p = (0, index_1.sanitizeColumnPlan)(v);
114
+ if (p)
115
+ pMap[col] = p;
116
+ }
117
+ plans[table] = pMap;
118
+ }
119
+ }
120
+ }
121
+ return { scope, counts, plans, truncate };
122
+ }
123
+ function resolvePkRows(info, ids) {
124
+ const list = Array.isArray(ids) ? ids : ids == null ? [] : [ids];
125
+ const out = [];
126
+ for (const id of list) {
127
+ const where = pkWhere(info, String(id));
128
+ if (where)
129
+ out.push(where);
130
+ }
131
+ return out.length ? out : null;
132
+ }
133
+ async function computeBulkImpact(db, info, wheres) {
134
+ const [rowsR, refInfoR] = await Promise.all([
135
+ db.getRowsByPks(info.table, wheres),
136
+ db.getReferencingTables(info.table),
137
+ ]);
138
+ const rows = (rowsR.data ?? []);
139
+ const referencing = refInfoR.data ?? [];
140
+ if (!rows.length || !referencing.length)
141
+ return { references: [], total: 0 };
142
+ const queries = referencing.flatMap((item) => item.refs.map(async (ref) => {
143
+ const targetCol = ref.to || info.primaryKey[0];
144
+ if (!targetCol)
145
+ return null;
146
+ const values = Array.from(new Set(rows.map((r) => r[targetCol]).filter((v) => v != null)));
147
+ if (!values.length)
148
+ return null;
149
+ try {
150
+ const placeholders = values.map(() => '?').join(', ');
151
+ const sql = `SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(item.table)} WHERE ${(0, generator_1.quoteIdentifier)(ref.from)} IN (${placeholders})`;
152
+ const countR = await db.all(sql, values);
153
+ const count = countR.success && countR.data?.[0] ? Number(countR.data[0].c ?? 0) : 0;
154
+ return count > 0 ? { table: item.table, from: ref.from, to: targetCol, count } : null;
155
+ }
156
+ catch {
157
+ return null;
158
+ }
159
+ }));
160
+ const results = (await Promise.all(queries)).filter((r) => r !== null);
161
+ const total = results.reduce((acc, r) => acc + r.count, 0);
162
+ return { references: results, total };
163
+ }