admindb 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +368 -364
- package/dist/app.js +6 -9
- package/dist/auth/config.d.ts +1 -1
- package/dist/auth/config.js +18 -30
- package/dist/auth/crypto.js +11 -21
- package/dist/auth/middleware.js +9 -16
- package/dist/auth/routes.js +9 -15
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.js +15 -26
- package/dist/cli/config.js +6 -16
- package/dist/cli/runner.js +4 -7
- package/dist/data/chain.d.ts +1 -1
- package/dist/data/chain.js +40 -617
- package/dist/data/datasets.d.ts +1 -1
- package/dist/data/datasets.js +43 -1
- package/dist/data/detector.js +45 -21
- package/dist/data/engine.d.ts +1 -1
- package/dist/data/engine.js +483 -142
- package/dist/data/index.d.ts +1 -1
- package/dist/data/index.js +8 -0
- package/dist/data/prng.d.ts +1 -0
- package/dist/data/prng.js +73 -0
- package/dist/data/registry.d.ts +1 -0
- package/dist/data/registry.js +805 -0
- package/dist/data/schema-graph.d.ts +1 -0
- package/dist/data/schema-graph.js +210 -0
- package/dist/data/strategies.d.ts +1 -1
- package/dist/data/strategies.js +44 -283
- package/dist/data/templates.d.ts +1 -0
- package/dist/data/templates.js +134 -0
- package/dist/data/types.d.ts +1 -1
- package/dist/data/validator.d.ts +1 -0
- package/dist/data/validator.js +160 -0
- package/dist/db/database.js +9 -14
- package/dist/db/export.js +32 -55
- package/dist/db/filters.d.ts +1 -1
- package/dist/db/filters.js +15 -38
- package/dist/db/introspection.js +4 -7
- package/dist/db/manager.js +27 -51
- package/dist/db/migrations.js +7 -14
- package/dist/db/postgres.js +30 -78
- package/dist/index.d.ts +1 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/inspector.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/public/js/seed.js +1 -1
- package/dist/routes/api/erd.js +3 -8
- package/dist/routes/api/helpers.d.ts +1 -1
- package/dist/routes/api/helpers.js +75 -25
- package/dist/routes/api/import-export.js +1 -2
- package/dist/routes/api/query.js +2 -6
- package/dist/routes/api/rows.js +4 -8
- package/dist/routes/api/seed.js +35 -21
- package/dist/routes/api/tables.js +15 -30
- package/dist/routes/databases.js +8 -26
- package/dist/routes/pages.js +67 -18
- package/dist/serverless.js +15 -40
- package/dist/sql/generator.d.ts +1 -1
- package/dist/sql/generator.js +35 -109
- package/dist/types/api.d.ts +1 -1
- package/dist/utils/common.d.ts +1 -1
- package/dist/utils/common.js +45 -85
- package/dist/utils/csv.d.ts +1 -1
- package/dist/utils/csv.js +11 -17
- package/dist/utils/datatype.js +46 -60
- package/dist/utils/icons.js +8 -8
- package/dist/views/layouts/main.hbs +1 -87
- package/dist/views/pages/databases.hbs +1 -233
- package/dist/views/pages/designer.hbs +1 -89
- package/dist/views/pages/erd.hbs +1 -1241
- package/dist/views/pages/error.hbs +1 -10
- package/dist/views/pages/form.hbs +1 -59
- package/dist/views/pages/home.hbs +1 -220
- package/dist/views/pages/info.hbs +1 -107
- package/dist/views/pages/login.hbs +1 -98
- package/dist/views/pages/query.hbs +1 -129
- package/dist/views/pages/schema.hbs +1 -298
- package/dist/views/pages/seed-select.hbs +251 -0
- package/dist/views/pages/seed.hbs +256 -414
- package/dist/views/pages/table.hbs +1 -327
- package/dist/views/partials/navbar.hbs +1 -83
- package/dist/views/partials/sidebar.hbs +1 -129
- package/docs/API.md +149 -149
- package/docs/EXAMPLES.md +579 -579
- package/docs/SECURITY.md +265 -265
- package/package.json +79 -79
package/dist/public/js/seed.js
CHANGED
|
@@ -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||[];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)}();
|
|
1
|
+
!function(){"use strict";const e=document.getElementById("seed-config");if(!e)return;const t=JSON.parse(e.textContent),n=t.table,a=t.columns||[],s=new URLSearchParams(window.location.search),o=t.mode||s.get("mode")||"single";let l=2,c="chain"===o?"chain":"single",i=null;const d={},r={},m={};let p=null,u=null,g=n,b=null;d[n]={},r[n]="generate",m[n]=10,a.forEach(e=>{d[n][e.name]=Object.assign({},e.defaultPlan||{strategy:"skip"})});document.getElementById("wizard-progress-bar"),document.getElementById("step-nav-1"),document.getElementById("step-nav-2"),document.getElementById("step-nav-3"),document.getElementById("step-content-1"),document.getElementById("step-content-2"),document.getElementById("step-content-3");const f=document.getElementById("btn-select-single"),x=document.getElementById("btn-select-chain"),h=document.getElementById("btn-next-1"),v=document.getElementById("btn-next-2"),y=document.getElementById("btn-prev-2"),E=document.getElementById("btn-prev-3"),w=document.getElementById("config-single"),L=document.getElementById("config-chain"),I=document.getElementById("seed-columns"),k=document.getElementById("seed-count"),$=document.getElementById("seed-count-dec"),S=document.getElementById("seed-count-inc"),B=document.getElementById("seed-col-search"),C=document.getElementById("col-count-badge"),M=document.getElementById("plan-seed-input"),T=document.getElementById("btn-random-seed"),N=document.getElementById("plan-transaction-opt"),U=document.getElementById("plan-rollback-opt"),q=document.getElementById("chain-scope"),A=document.getElementById("chain-pipeline-container"),H=document.getElementById("chain-summary-badge"),R=document.getElementById("validation-status-dot"),j=document.getElementById("validation-status-text"),O=document.getElementById("validation-issues-list"),_=document.getElementById("validation-summary-chip"),D=document.getElementById("preview-tab-grid"),z=document.getElementById("preview-tab-sql"),P=document.getElementById("preview-view-grid"),F=document.getElementById("preview-view-sql"),J=document.getElementById("seed-preview-thead"),V=document.getElementById("seed-preview-tbody"),Q=document.getElementById("preview-sql-code"),W=document.getElementById("btn-copy-sql"),G=document.getElementById("seed-chain-table-tabs"),K=document.getElementById("seed-refresh-preview"),Y=document.getElementById("seed-truncate"),X=document.getElementById("seed-truncate-banner"),Z=document.getElementById("seed-truncate-dialog"),ee=document.getElementById("seed-truncate-confirm"),te=document.getElementById("seed-truncate-cancel"),ne=document.getElementById("seed-insert"),ae=document.getElementById("seed-feedback"),se=document.getElementById("btn-profiles-modal"),oe=document.getElementById("seed-profiles-dialog"),le=document.getElementById("btn-close-profiles"),ce=document.getElementById("profile-name-input"),ie=document.getElementById("btn-save-profile"),de=document.getElementById("profiles-list-container"),re=document.getElementById("btn-export-plan-json"),me=document.getElementById("input-import-plan-json");function pe(e){return window.UI&&UI.escapeHtml?UI.escapeHtml(e):String(null==e?"":e)}function ue(e){return e[Math.floor(Math.random()*e.length)]}function ge(e,t){return Math.floor(Math.random()*(t-e+1))+e}function be(e=400){b&&clearTimeout(b),b=setTimeout(()=>{!async function(){"chain"!==c||p||await we();const e=fe();try{xe(await Api.post("/api/seed/validate",{plan:e}))}catch{}}(),Le()},e)}function fe(){const e="single"===c,t={name:e?`Single Table ${n}`:`ER Relational Seed ${n}`,rootTable:n,scope:e?"single":q?.value||"chain",tables:{},options:{seed:null!=i&&Number.isFinite(i)?i:null,transaction:N?.checked??!0,rollbackOnError:U?.checked??!0,truncateAll:Y?.checked??!1}};return e?t.tables[n]={mode:"generate",rows:Math.max(1,parseInt(k?.value,10)||10),columns:d[n]||{},truncate:Y?.checked??!1}:p&&p.tables&&(p.tables.forEach(e=>{const n=r[e.name]||"generate";t.tables[e.name]={mode:n,rows:Math.max(1,m[e.name]||e.suggestedCount||10),columns:d[e.name]||{},truncate:Y?.checked??!1}}),t.relationships=p.relationships),t}function xe(e){if(!e)return;const t=(e.issues||[]).filter(e=>"error"===e.type),n=(e.issues||[]).filter(e=>"warning"===e.type);R&&j&&_&&(0===t.length?(R.className="w-2 h-2 rounded-full bg-success",j.textContent=n.length?`${n.length} Warning(s)`:"Plan Ready",_.className="badge badge-sm badge-success font-mono text-[10px] font-bold",_.textContent=n.length?"Warnings":"All Checks Passed"):(R.className="w-2 h-2 rounded-full bg-error animate-pulse",j.textContent=`${t.length} Issue(s)`,_.className="badge badge-sm badge-error font-mono text-[10px] font-bold",_.textContent="Action Required")),O&&(O.innerHTML="",0===t.length&&0===n.length?O.innerHTML='\n <div class="flex items-center gap-2 text-success font-medium text-[11px]">\n <svg class="h-3.5 w-3.5 shrink-0" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>\n <span>Primary key & constraint strategies valid</span>\n </div>\n ':(t.forEach(e=>{O.innerHTML+=`\n <div class="flex items-start gap-1.5 text-error font-medium text-[11px]">\n <svg class="h-3.5 w-3.5 shrink-0 mt-0.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>\n <span><strong>${pe(e.table||"")}:</strong> ${pe(e.message)}</span>\n </div>\n `}),n.forEach(e=>{O.innerHTML+=`\n <div class="flex items-start gap-1.5 text-warning font-medium text-[11px]">\n <svg class="h-3.5 w-3.5 shrink-0 mt-0.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>\n <span><strong>${pe(e.table||"")}:</strong> ${pe(e.message)}</span>\n </div>\n `})))}function he(e){l=e}function ve(e,t=!1){if(c="chain"===e?"chain":"single",f&&f.classList.toggle("active","single"===c),x&&x.classList.toggle("active","chain"===c),w&&w.classList.toggle("hidden","single"!==c),L&&L.classList.toggle("hidden","chain"!==c),"single"===c?I&&0===I.children.length&&ye(n,a,I):"chain"===c&&(p||we()),t&&window.history&&window.history.replaceState){const e=new URL(window.location.href);e.searchParams.set("mode",c),window.history.replaceState({},"",e.toString())}be(100)}function ye(e,t,n){n.innerHTML="",d[e]||(d[e]={}),t.forEach(a=>{d[e][a.name]||(d[e][a.name]=Object.assign({},a.defaultPlan||{strategy:"skip"}));const s=d[e][a.name],o=document.createElement("div");o.className="seed-col-row",o.dataset.colName=a.name.toLowerCase(),o.dataset.strategyType="template"===s.strategy?"template":"skip"===s.strategy?"skip":"null"===s.strategy?"null":"fixed"===s.strategy?"fixed":"generate";let l="";a.pk&&(l+='<span class="badge badge-xs badge-warning font-mono font-bold text-[9px] px-1">PK</span>'),a.notnull&&(l+='<span class="badge badge-xs badge-error font-mono font-bold text-[9px] px-1">REQ</span>'),a.unique&&(l+='<span class="badge badge-xs badge-secondary font-mono font-bold text-[9px] px-1">UQ</span>'),a.fk&&(l+=`<span class="badge badge-xs badge-outline font-mono text-[9px] text-base-content/60">↳ ${pe(a.fk.table)}.${pe(a.fk.column)}</span>`),o.innerHTML=`\n <div class="flex flex-wrap items-center justify-between gap-2.5">\n <div class="flex items-center gap-2 min-w-0">\n <span class="font-mono font-bold text-xs text-base-content truncate">${pe(a.name)}</span>\n <span class="font-mono text-[10px] text-base-content/40 uppercase">${pe(a.type)}</span>\n ${l}\n </div>\n\n <div class="flex items-center gap-2 flex-wrap">\n <select class="col-strategy-select field-select !py-0.5 text-xs font-mono font-semibold w-40">\n ${(a.strategies||[]).map(e=>`<option value="${e.id}" ${e.id===s.strategy?"selected":""}>${pe(e.label)}</option>`).join("")}\n </select>\n <span class="sample-pill col-sample-badge" title="Click to copy sample value"></span>\n </div>\n </div>\n\n <div class="col-options-box hidden mt-2.5 pt-2 border-t border-base-200/60"></div>\n `;const c=o.querySelector(".col-strategy-select"),i=o.querySelector(".col-options-box"),r=o.querySelector(".col-sample-badge"),m=()=>{const n=function(e,t,n=[]){const a=t.strategy;if("skip"===a)return"(auto / default)";if("null"===a)return"NULL";if("template"===a){let e=t.template||"{{first_name}}";return n.forEach(t=>{const n=t.name.toLowerCase().includes("first")?"Alex":t.name.toLowerCase().includes("last")?"Morgan":t.name.toLowerCase().includes("name")?"Alex Morgan":`[${t.name}]`,a=e.includes("@")&&!n.startsWith("[")?n.toLowerCase().replace(/\s+/g,"."):n;e=e.replace(new RegExp(`\\{\\{\\s*${t.name}\\s*\\}\\}`,"gi"),a)}),e=e.replace(/\{\{\s*year\s*\}\}/gi,String((new Date).getFullYear())),e=e.replace(/\{\{\s*sequence(?::\d+(?::\d+)?)?\s*\}\}/gi,"1"),e=e.replace(/\{\{\s*random(?::\d+,\d+)?\s*\}\}/gi,"42"),e=e.replace(/\{\{\s*uuid\s*\}\}/gi,"550e8400-e29b-41d4-a716-446655440000"),e}return"fixed"===a?t.value||"":"list"===a?t.values&&t.values.length?ue(t.values):"(empty list)":"pattern"===a?(t.pattern||"INV-####").replace(/#/g,()=>ge(0,9)):"first"===a?"Alex":"last"===a?"Morgan":"fullname"===a?"Alex Morgan":"email"===a?"alex.morgan@example.com":"status"===a?"active":"company"===a?"Acme Corp":"city"===a?"San Francisco":"country"===a?"United States":"int"===a?String(ge(t.min??1,t.max??1e3)):"decimal"===a?(Math.random()*((t.max??1e3)-(t.min??1))+(t.min??1)).toFixed(t.precision??2):"bool"===a?ue(["true","false"]):"sequence"===a?String(t.start||1):"uuid"===a?"550e8400-e29b-41d4-a716-446655440000":"product"===a?"Wireless Noise-Canceling Earbuds":"productCategory"===a?"Electronics":"department"===a?"Engineering":"paymentMethod"===a?"Credit Card":"transactionType"===a?"purchase":"techSkill"===a?"TypeScript":"orderStatus"===a?"delivered":"fk"===a?`[FK -> ${e.fk?e.fk.table:"parent"}]`:"sampleExisting"===a?"[Sampled DB Value]":"(sample)"}(a,d[e][a.name],t);r.textContent=n,o.dataset.strategyType="template"===s.strategy?"template":"skip"===s.strategy?"skip":"null"===s.strategy?"null":"fixed"===s.strategy?"fixed":"generate",be(400)};r.addEventListener("click",()=>{navigator.clipboard.writeText(r.textContent||""),window.UI&&UI.showToast&&UI.showToast(`Copied "${r.textContent}" to clipboard`,"info")}),c.addEventListener("change",()=>{d[e][a.name].strategy=c.value,Ee(i,a,d[e][a.name],t,m),m()}),Ee(i,a,s,t,m),m(),n.appendChild(o)}),C&&(C.textContent=`${t.length} cols`)}function Ee(e,t,n,a,s){e.innerHTML="";const o=n.strategy,l=["fixed","list","template","int","decimal","date","datetime","pattern","sequence"].includes(o);if(e.classList.toggle("hidden",!l),!l)return;const c=(t,a,o="text",l="")=>{const c=document.createElement("div");c.innerHTML=`<label class="block text-[9px] uppercase font-bold text-base-content/50 mb-0.5">${pe(t)}</label>`;const i=document.createElement("textarea"===o?"textarea":"input");"textarea"!==o&&(i.type=o),i.placeholder=l,i.className="textarea"===o?"field-input w-full text-xs font-mono !py-1 h-14 leading-relaxed":"field-input w-full text-xs font-mono !py-0.5";const d=n[a];return"textarea"===o&&Array.isArray(d)?i.value=d.join("\n"):void 0!==d&&(i.value=d),i.addEventListener("input",()=>{n[a]="textarea"===o?i.value.split("\n").map(e=>e.trim()).filter(Boolean):"number"===o?""===i.value?void 0:Number(i.value):i.value,s()}),c.appendChild(i),e.appendChild(c),i};if("fixed"===o&&c("Constant Value","value","text","Default string"),"list"===o&&c("Choices (One per line)","values","textarea","admin\nmember\nguest"),"template"===o){const o=c("Template Expression","template","text","{{first_name}}.{{last_name}}@example.com"),l=document.createElement("div");l.className="space-y-1 pt-1",l.innerHTML='<span class="text-[9px] uppercase font-bold text-base-content/40 tracking-wider">Quick Tokens:</span>';const i=document.createElement("div");i.className="flex flex-wrap gap-1",a.slice(0,6).forEach(e=>{if(e.name!==t.name){const t=document.createElement("button");t.type="button",t.className="token-chip",t.textContent=`+ {{${e.name}}}`,t.onclick=()=>{n.template=(n.template||"")+`{{${e.name}}}`,o&&(o.value=n.template),s()},i.appendChild(t)}});[{label:"+ {{year}}",val:"{{year}}"},{label:"+ {{sequence}}",val:"{{sequence:1:1}}"},{label:"+ {{random}}",val:"{{random:1,100}}"}].forEach(e=>{const t=document.createElement("button");t.type="button",t.className="token-chip",t.textContent=e.label,t.onclick=()=>{n.template=(n.template||"")+e.val,o&&(o.value=n.template),s()},i.appendChild(t)}),l.appendChild(i),e.appendChild(l)}if("int"===o||"decimal"===o){const t=document.createElement("div");t.className="grid grid-cols-2 sm:grid-cols-3 gap-2",e.appendChild(t);const n=e;e=t,c("Min Value","min","number","1"),c("Max Value","max","number","1000"),"decimal"===o&&c("Decimals","precision","number","2"),e=n}if("date"===o||"datetime"===o){const t=document.createElement("div");t.className="grid grid-cols-2 gap-2",e.appendChild(t);const n=e;e=t,c("From Date","from","text","2020-01-01"),c("To Date","to","text","2026-12-31"),e=n}if("pattern"===o&&c("Pattern Expression","pattern","text","INV-2026-####"),"sequence"===o){const t=document.createElement("div");t.className="grid grid-cols-2 gap-2",e.appendChild(t);const n=e;e=t,c("Start Sequence","start","number","1"),c("Step","step","number","1"),e=n}}async function we(){const e=q?.value||"chain";A.innerHTML='<div class="py-8 text-center text-xs text-base-content/50"><span class="loading loading-spinner loading-sm text-primary mb-2"></span><br><strong class="font-bold text-base-content">Analyzing foreign keys & dependency DAG…</strong></div>';try{p=await Api.get(`/api/tables/${encodeURIComponent(n)}/seed/chain?scope=${e}`),H&&(H.textContent=`${p.tables.length} Tables`),A.innerHTML="",p.tables.forEach((e,t)=>{m[e.name]||(m[e.name]=e.suggestedCount||10),r[e.name]||(r[e.name]="generate"),d[e.name]||(d[e.name]={},(e.columns||[]).forEach(t=>{d[e.name][t.name]=Object.assign({},t.defaultPlan||{strategy:"skip"})}));const a=e.name===n,s=document.createElement("div");s.className="pipeline-node",s.dataset.nodeMode=r[e.name],s.innerHTML=`\n <div class="flex flex-wrap items-center justify-between gap-3">\n <div class="flex items-center gap-3">\n <span class="w-6 h-6 rounded ${a?"bg-primary text-primary-content font-bold":"bg-base-200 text-base-content/70"} text-[11px] flex items-center justify-center font-mono">${t+1}</span>\n <div>\n <div class="font-mono font-bold text-xs flex items-center gap-2">\n <span class="text-base-content">${pe(e.name)}</span>\n ${a?'<span class="badge badge-xs badge-primary font-mono text-[9px] px-1.5">Root</span>':""}\n ${e.isJunction?'<span class="badge badge-xs badge-accent font-mono text-[9px] px-1.5">Junction (M:N)</span>':""}\n ${e.hasSelfRef?'<span class="badge badge-xs badge-info font-mono text-[9px] px-1.5">Self-Ref</span>':""}\n </div>\n <div class="text-[10px] text-base-content/50 mt-0.5 flex gap-2 font-mono">\n <span>← ${e.parents.length} parents</span>\n <span>→ ${e.children.length} children</span>\n <span>(${e.rowCount} rows)</span>\n </div>\n </div>\n </div>\n\n <div class="flex items-center gap-2">\n <select class="chain-table-mode field-select !py-0.5 text-xs font-semibold w-36" data-table="${pe(e.name)}">\n <option value="generate" ${"generate"===r[e.name]?"selected":""}>🌱 Generate Data</option>\n <option value="use_existing" ${"use_existing"===r[e.name]?"selected":""}>📦 Use Existing</option>\n <option value="generate_if_empty" ${"generate_if_empty"===r[e.name]?"selected":""}>⚡ If Empty</option>\n <option value="skip" ${"skip"===r[e.name]?"selected":""}>⛔ Skip</option>\n </select>\n\n <input type="number" min="1" max="5000" value="${m[e.name]}"\n class="chain-table-rows field-input !py-0.5 w-16 text-right font-mono font-bold text-xs" data-table="${pe(e.name)}" />\n\n <button type="button" class="btn btn-xs btn-ghost border border-base-300 btn-toggle-cols" data-table="${pe(e.name)}">\n Cols ▼\n </button>\n </div>\n </div>\n\n <div class="chain-col-drawer hidden pt-3 mt-2.5 border-t border-base-200">\n <div class="chain-cols-container space-y-2"></div>\n </div>\n `;const o=s.querySelector(".chain-table-mode"),l=s.querySelector(".chain-table-rows"),c=s.querySelector(".btn-toggle-cols"),i=s.querySelector(".chain-col-drawer"),p=s.querySelector(".chain-cols-container");o.addEventListener("change",()=>{r[e.name]=o.value,s.dataset.nodeMode=o.value,l.disabled="use_existing"===o.value||"skip"===o.value,be(300)}),l.addEventListener("input",()=>{m[e.name]=Math.max(1,parseInt(l.value,10)||10),be(400)}),c.addEventListener("click",()=>{const t=i.classList.toggle("hidden");c.textContent=t?"Cols ▼":"Hide ▲",t||0!==p.children.length||ye(e.name,e.columns,p)}),A.appendChild(s)}),be(100)}catch(e){A.innerHTML=`<div class="alert alert-error text-xs font-semibold">${pe(e.message)}</div>`}}async function Le(){if(!V)return;"chain"!==c||p||await we(),V.innerHTML='<tr><td colspan="100" class="text-center py-12 text-base-content/40"><span class="loading loading-spinner loading-sm text-primary mb-2"></span><br><span class="font-bold text-xs">Generating data preview…</span></td></tr>',ae&&ae.classList.add("hidden"),G&&G.classList.add("hidden"),ne&&(ne.disabled=!0);const e=fe();try{u=await Api.post("/api/seed/preview",{plan:e}),ne&&(ne.disabled=!1),u.validation&&xe(u.validation),Q&&(Q.textContent=u.sql||"-- No SQL statements generated.");const t="single"===c,a=t?[n]:u.executionOrder||Object.keys(u.tableResults||{});!t&&a.includes(g)||(g=n),!t&&a.length>1&&G?(G.classList.remove("hidden"),G.innerHTML="",a.forEach(e=>{const t=u.tableResults[e],n=document.createElement("button");n.dataset.table=e,n.className="tab tab-bordered h-8 text-xs font-mono whitespace-nowrap shrink-0 "+(e===g?"tab-active text-primary font-bold border-b-2 border-b-primary":"text-base-content/60"),n.innerHTML=`${pe(e)} <span class="badge badge-xs ml-1.5 ${e===g?"badge-primary":"badge-ghost"}">${t?.rows?.length||0}</span>`,n.onclick=t=>{t.preventDefault(),g=e,Array.from(G.children).forEach(e=>{e.classList.remove("tab-active","text-primary","border-b-2","border-b-primary","font-bold"),e.classList.add("text-base-content/60"),e.querySelector(".badge")?.classList.replace("badge-primary","badge-ghost")}),n.classList.add("tab-active","text-primary","border-b-2","border-b-primary","font-bold"),n.classList.remove("text-base-content/60"),n.querySelector(".badge")?.classList.replace("badge-ghost","badge-primary");const a=u.tableResults[g];Ie(g,a?.previewRows||[])},G.appendChild(n)})):G&&G.classList.add("hidden");const s=u.tableResults[g]||{previewRows:[]};Ie(g,s?.previewRows||[]),ke(u.warnings||[])}catch(e){V&&(V.innerHTML=`<tr><td colspan="100" class="text-center py-10 text-error text-xs font-bold">${pe(e.message)}</td></tr>`)}}function Ie(e,t){if(!J||!V)return;if(J.innerHTML="",V.innerHTML="",!t||0===t.length)return void(V.innerHTML='<tr><td colspan="100" class="text-center py-10 text-base-content/40 text-xs font-semibold">No rows generated for this table.</td></tr>');const n=new Set;t.forEach(e=>{e&&"object"==typeof e&&Object.keys(e).forEach(e=>n.add(e))});const a=Array.from(n);if(0===a.length)return void(V.innerHTML='<tr><td colspan="100" class="text-center py-10 text-base-content/40 text-xs font-semibold">No columns in generated rows.</td></tr>');const s=document.createElement("tr");s.innerHTML='<th class="w-10 text-center font-mono">#</th>'+a.map(e=>`<th class="font-mono">${pe(e)}</th>`).join(""),J.appendChild(s),t.forEach((e,t)=>{const n=document.createElement("tr");let s=`<td class="text-center text-base-content/40 font-mono text-[10px]">${t+1}</td>`;a.forEach(t=>{const n=e[t];s+=null==n?'<td><span class="badge badge-xs badge-ghost opacity-60 font-mono font-bold text-[9px] text-base-content/50">NULL</span></td>':"(default)"===n||"(auto)"===n?`<td><span class="badge badge-xs badge-outline opacity-60 font-mono text-[9px]">${pe(n)}</span></td>`:"boolean"==typeof n?`<td><span class="badge badge-xs ${n?"badge-success":"badge-ghost"} font-mono font-bold text-[9px]">${n?"TRUE":"FALSE"}</span></td>`:`<td class="truncate max-w-[200px] font-mono text-[11px] text-base-content/90" title="${pe(String(n))}">${pe(String(n))}</td>`}),n.innerHTML=s,V.appendChild(n)})}function ke(e){ae&&(e&&0!==e.length?(ae.innerHTML=`\n <div class="alert alert-warning text-xs font-semibold rounded-lg shadow-sm">\n <svg class="h-4 w-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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>${pe(e)}</p>`).join("")}</div>\n </div>\n `,ae.classList.remove("hidden")):ae.classList.add("hidden"))}B&&B.addEventListener("input",()=>{const e=B.value.toLowerCase().trim(),t=I.querySelectorAll(".seed-col-row");let n=0;t.forEach(t=>{const a=!e||t.dataset.colName.includes(e);t.classList.toggle("hidden",!a),a&&n++}),C&&(C.textContent=`${n} of ${a.length} cols`)});const $e="admindb_seed_profiles";function Se(){try{const e=localStorage.getItem($e);return e?JSON.parse(e):[]}catch{return[]}}function Be(){try{const e=Se();if(de.innerHTML="",!e||0===e.length)return void(de.innerHTML='<div class="py-4 text-center text-xs text-base-content/40">No custom profiles saved yet.</div>');e.forEach(e=>{const t=document.createElement("div");t.className="p-2.5 rounded-lg bg-base-200/50 border border-base-300 flex items-center justify-between gap-2 text-xs hover:border-primary/40 transition-colors",t.innerHTML=`\n <div class="min-w-0">\n <div class="font-bold text-base-content truncate">${pe(e.name)}</div>\n <div class="text-[10px] text-base-content/50">${pe(e.description||`Root: ${e.plan?.rootTable||"N/A"}`)}</div>\n </div>\n <div class="flex items-center gap-1.5">\n <button type="button" class="btn btn-xs btn-primary btn-load-profile font-bold" data-id="${pe(e.id)}">Apply</button>\n <button type="button" class="btn btn-xs btn-ghost text-error btn-del-profile" data-id="${pe(e.id)}">✕</button>\n </div>\n `,t.querySelector(".btn-load-profile").onclick=()=>Ce(e),t.querySelector(".btn-del-profile").onclick=()=>{!function(e){const t=Se().filter(t=>t.id!==e);try{localStorage.setItem($e,JSON.stringify(t))}catch{}}(e.id),Be()},de.appendChild(t)})}catch{de.innerHTML='<div class="py-4 text-center text-xs text-error font-semibold">Failed to load profiles.</div>'}}function Ce(e){if(!e||!e.plan)return;const t=e.plan;null!=t.options?.seed&&(i=t.options.seed,M&&(M.value=i)),t.tables&&(Object.entries(t.tables).forEach(([e,t])=>{r[e]=t.mode||"generate",m[e]=t.rows||10,t.columns&&(d[e]=Object.assign({},t.columns))}),t.tables[n]&&k&&(k.value=t.tables[n].rows||10)),oe&&oe.close(),window.UI&&UI.showToast&&UI.showToast(`Applied profile "${e.name}".`,"success"),I&&ye(n,a,I),be(100)}async function Me(){"chain"!==c||p||await we();const e=fe();ne&&(ne.disabled=!0,ne.classList.add("loading"));try{const t=await Api.post("/api/seed/execute",{plan:e,truncate:Y?.checked??!1});window.UI&&UI.showToast&&UI.showToast(t.message||`Seeded ${t.totalInserted} rows successfully.`,"success"),ke(t.warnings||[])}catch(e){window.UI&&UI.showError&&UI.showError(e.message)}finally{ne&&(ne.disabled=!1,ne.classList.remove("loading")),Z&&Z.close()}}f&&f.addEventListener("click",()=>ve("single")),x&&x.addEventListener("click",()=>ve("chain")),h&&h.addEventListener("click",()=>he(2)),y&&y.addEventListener("click",()=>he(1)),v&&v.addEventListener("click",()=>he(3)),E&&E.addEventListener("click",()=>he(2)),$&&S&&k&&($.addEventListener("click",()=>{k.value=Math.max(1,(parseInt(k.value,10)||10)-1),be(300)}),S.addEventListener("click",()=>{k.value=Math.min(5e3,(parseInt(k.value,10)||10)+1),be(300)}),k.addEventListener("input",()=>be(400))),document.querySelectorAll(".preset-pill").forEach(e=>{e.addEventListener("click",()=>{document.querySelectorAll(".preset-pill").forEach(e=>e.classList.remove("btn-primary")),e.classList.add("btn-primary"),k&&e.dataset.count&&(k.value=e.dataset.count,be(200))})}),M&&M.addEventListener("input",()=>{i=""===M.value?null:Number(M.value),be(400)}),T&&T.addEventListener("click",()=>{i=Math.floor(1e6*Math.random()),M.value=i,window.UI&&UI.showToast&&UI.showToast(`Set Random Seed: ${i}`,"info"),be(100)}),q&&q.addEventListener("change",()=>we()),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);p&&p.tables&&p.tables.forEach(e=>{m[e.name]=Math.max(1,Math.round((e.suggestedCount||10)*t));const n=A?.querySelector(`.chain-table-rows[data-table="${CSS.escape(e.name)}"]`);n&&(n.value=m[e.name])}),be(200)})}),D&&z&&(D.addEventListener("click",()=>{D.classList.add("btn-active"),z.classList.remove("btn-active"),P.classList.remove("hidden"),F.classList.add("hidden")}),z.addEventListener("click",()=>{z.classList.add("btn-active"),D.classList.remove("btn-active"),F.classList.remove("hidden"),P.classList.add("hidden")})),W&&W.addEventListener("click",()=>{Q&&(navigator.clipboard.writeText(Q.textContent||""),window.UI&&UI.showToast&&UI.showToast("SQL script copied to clipboard.","success"))}),K&&K.addEventListener("click",()=>Le()),Y&&X&&Y.addEventListener("change",()=>{X.classList.toggle("hidden",!Y.checked)}),ne&&ne.addEventListener("click",()=>{Y?.checked?Z.showModal():Me()}),ee&&ee.addEventListener("click",()=>Me()),te&&te.addEventListener("click",()=>Z.close()),se&&oe&&(se.addEventListener("click",()=>{oe.showModal(),Be()}),le&&le.addEventListener("click",()=>oe.close())),document.querySelectorAll(".btn-preset-profile").forEach(e=>{e.addEventListener("click",()=>{const t=e.dataset.preset,n="smoke"===t?10:"realistic"===t?100:1e3;k&&(k.value=n),Object.keys(m).forEach(e=>m[e]=n),oe&&oe.close(),window.UI&&UI.showToast&&UI.showToast(`Applied ${n} rows preset blueprint.`,"info"),be(100)})}),ie&&ie.addEventListener("click",()=>{const e=ce.value.trim();if(!e)return;!function(e,t,n){const a=Se(),s={id:`profile_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,name:e,description:n,createdAt:(new Date).toISOString(),plan:t};a.unshift(s);try{localStorage.setItem($e,JSON.stringify(a))}catch{}}(e,fe()),ce.value="",window.UI&&UI.showToast&&UI.showToast(`Saved profile "${e}".`,"success"),Be()}),re&&re.addEventListener("click",()=>{const e=fe(),t=new Blob([JSON.stringify(e,null,2)],{type:"application/json"}),a=document.createElement("a");a.href=URL.createObjectURL(t),a.download=`seed-plan-${n}-${Date.now()}.json`,a.click()}),me&&me.addEventListener("change",e=>{const t=e.target.files?.[0];if(!t)return;const n=new FileReader;n.onload=e=>{try{const n=JSON.parse(e.target?.result);Ce({name:t.name,plan:n})}catch{window.UI&&UI.showError&&UI.showError("Invalid JSON seed plan file.")}},n.readAsText(t)}),B&&B.addEventListener("input",()=>{const e=B.value.trim().toLowerCase(),t=I?.querySelectorAll(".seed-col-row")||[];let n=0;t.forEach(t=>{const a=!e||(t.dataset.colName||"").includes(e);t.classList.toggle("hidden",!a),a&&n++}),C&&(C.textContent=`${n} cols`)}),I&&0===I.children.length&&ye(n,a,I),f&&f.addEventListener("click",()=>ve("single",!0)),x&&x.addEventListener("click",()=>ve("chain",!0)),ve(o,!1)}();
|
package/dist/routes/api/erd.js
CHANGED
|
@@ -11,15 +11,10 @@ function registerErdRoutes(router, ctx) {
|
|
|
11
11
|
return (0, helpers_1.fail)(res, tablesResult.error ?? 'Failed to list tables.', 500);
|
|
12
12
|
}
|
|
13
13
|
const tableNames = tablesResult.data.map((t) => t.name);
|
|
14
|
-
const
|
|
14
|
+
const tables = await Promise.all(tableNames.map(async (name) => {
|
|
15
15
|
const infoResult = await db.getTableInfo(name);
|
|
16
16
|
if (!infoResult.success || !infoResult.data) {
|
|
17
|
-
return {
|
|
18
|
-
name,
|
|
19
|
-
columns: [],
|
|
20
|
-
foreignKeys: [],
|
|
21
|
-
primaryKey: [],
|
|
22
|
-
};
|
|
17
|
+
return { name, columns: [], foreignKeys: [], primaryKey: [] };
|
|
23
18
|
}
|
|
24
19
|
const { columns, foreignKeys, primaryKey } = infoResult.data;
|
|
25
20
|
return {
|
|
@@ -41,7 +36,7 @@ function registerErdRoutes(router, ctx) {
|
|
|
41
36
|
primaryKey,
|
|
42
37
|
};
|
|
43
38
|
}));
|
|
44
|
-
(0, helpers_1.ok)(res, { tables
|
|
39
|
+
(0, helpers_1.ok)(res, { tables });
|
|
45
40
|
}
|
|
46
41
|
catch (err) {
|
|
47
42
|
(0, helpers_1.fail)(res, err.message ?? 'Unknown error.', 500);
|
|
@@ -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,type ErChainScope} from '../../data/index';export interface ApiContext{db:IDatabase;logger:Logger;}export declare
|
|
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,type GenerationPlan,type TableGenerationMode} from '../../data/index';export interface ApiContext{db:IDatabase;logger:Logger;}export declare const ok:(res:Response,data:unknown,status?:number)=>Response;export declare const fail:(res:Response,error:string,status?:number,details?:Record<string,unknown>|null)=>Response;export declare const 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;seed?:number|null;};export declare function parseChainSeedRequest(body:unknown):{scope:ErChainScope;counts:Record<string,number>;plans:Record<string,Record<string,ColumnPlan>>;truncate:boolean;modes?:Record<string,TableGenerationMode>;seed?:number|null;};export declare function parseUnifiedGenerationPlan(body:unknown):GenerationPlan;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,41 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
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;
|
|
3
|
+
exports.MAX_BULK_ROWS = exports.wrap = exports.fail = exports.ok = void 0;
|
|
7
4
|
exports.requireTable = requireTable;
|
|
8
5
|
exports.buildFields = buildFields;
|
|
9
6
|
exports.buildUpdateFields = buildUpdateFields;
|
|
10
7
|
exports.pkWhere = pkWhere;
|
|
11
8
|
exports.parseSeedRequest = parseSeedRequest;
|
|
12
9
|
exports.parseChainSeedRequest = parseChainSeedRequest;
|
|
10
|
+
exports.parseUnifiedGenerationPlan = parseUnifiedGenerationPlan;
|
|
13
11
|
exports.resolvePkRows = resolvePkRows;
|
|
14
12
|
exports.computeBulkImpact = computeBulkImpact;
|
|
15
13
|
const common_1 = require("../../utils/common");
|
|
16
14
|
const index_1 = require("../../data/index");
|
|
17
15
|
const generator_1 = require("../../sql/generator");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
|
16
|
+
const ok = (res, data, status = 200) => res.status(status).json({ success: true, data });
|
|
17
|
+
exports.ok = ok;
|
|
18
|
+
const fail = (res, error, status = 400, details) => res.status(status).json({ success: false, error, ...(details ? { details } : {}) });
|
|
19
|
+
exports.fail = fail;
|
|
20
|
+
const wrap = (fn) => async (req, res) => {
|
|
21
|
+
try {
|
|
22
|
+
await fn(req, res);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
(0, exports.fail)(res, (0, common_1.errorMessage)(err), 500);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
exports.wrap = wrap;
|
|
34
29
|
async function requireTable(db, table) {
|
|
35
30
|
const info = await db.getTableInfo(table);
|
|
36
|
-
|
|
37
|
-
return null;
|
|
38
|
-
return info.data;
|
|
31
|
+
return info.success && info.data?.columns?.length ? info.data : null;
|
|
39
32
|
}
|
|
40
33
|
function buildFields(info, values, opts = {}) {
|
|
41
34
|
const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
|
|
@@ -80,6 +73,7 @@ function parseSeedRequest(body) {
|
|
|
80
73
|
const b = (body ?? {});
|
|
81
74
|
const count = Math.max(1, Math.min(index_1.MAX_SEED_ROWS, Number.parseInt(String(b.count ?? '10'), 10) || 10));
|
|
82
75
|
const truncate = Boolean(b.truncate);
|
|
76
|
+
const seed = b.seed != null && b.seed !== '' ? Number(b.seed) : null;
|
|
83
77
|
const plan = {};
|
|
84
78
|
if (b.plan && typeof b.plan === 'object') {
|
|
85
79
|
for (const [k, v] of Object.entries(b.plan)) {
|
|
@@ -88,7 +82,7 @@ function parseSeedRequest(body) {
|
|
|
88
82
|
plan[k] = p;
|
|
89
83
|
}
|
|
90
84
|
}
|
|
91
|
-
return { count, plan, truncate };
|
|
85
|
+
return { count, plan, truncate, seed };
|
|
92
86
|
}
|
|
93
87
|
function parseChainSeedRequest(body) {
|
|
94
88
|
const b = (body ?? {});
|
|
@@ -96,6 +90,7 @@ function parseChainSeedRequest(body) {
|
|
|
96
90
|
? b.scope
|
|
97
91
|
: 'chain';
|
|
98
92
|
const truncate = Boolean(b.truncate);
|
|
93
|
+
const seed = b.seed != null && b.seed !== '' ? Number(b.seed) : null;
|
|
99
94
|
const counts = {};
|
|
100
95
|
if (b.counts && typeof b.counts === 'object') {
|
|
101
96
|
for (const [table, rawN] of Object.entries(b.counts)) {
|
|
@@ -104,6 +99,15 @@ function parseChainSeedRequest(body) {
|
|
|
104
99
|
counts[table] = Math.min(2000, n);
|
|
105
100
|
}
|
|
106
101
|
}
|
|
102
|
+
const modes = {};
|
|
103
|
+
if (b.modes && typeof b.modes === 'object') {
|
|
104
|
+
for (const [table, rawM] of Object.entries(b.modes)) {
|
|
105
|
+
const m = String(rawM);
|
|
106
|
+
if (m === 'generate' || m === 'use_existing' || m === 'generate_if_empty' || m === 'skip') {
|
|
107
|
+
modes[table] = m;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
107
111
|
const plans = {};
|
|
108
112
|
if (b.plans && typeof b.plans === 'object') {
|
|
109
113
|
for (const [table, tablePlans] of Object.entries(b.plans)) {
|
|
@@ -118,7 +122,53 @@ function parseChainSeedRequest(body) {
|
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
}
|
|
121
|
-
return { scope, counts, plans, truncate };
|
|
125
|
+
return { scope, counts, plans, truncate, modes, seed };
|
|
126
|
+
}
|
|
127
|
+
function parseUnifiedGenerationPlan(body) {
|
|
128
|
+
const b = (body ?? {});
|
|
129
|
+
const rawPlan = (b.plan && typeof b.plan === 'object' ? b.plan : b);
|
|
130
|
+
const tables = {};
|
|
131
|
+
if (rawPlan.tables && typeof rawPlan.tables === 'object') {
|
|
132
|
+
for (const [tableName, rawSpec] of Object.entries(rawPlan.tables)) {
|
|
133
|
+
if (!rawSpec || typeof rawSpec !== 'object')
|
|
134
|
+
continue;
|
|
135
|
+
const spec = rawSpec;
|
|
136
|
+
const rawMode = String(spec.mode || 'generate');
|
|
137
|
+
const mode = rawMode === 'use_existing' || rawMode === 'generate_if_empty' || rawMode === 'skip' ? rawMode : 'generate';
|
|
138
|
+
const rows = Math.max(1, Math.min(index_1.MAX_SEED_ROWS, Number.parseInt(String(spec.rows ?? '10'), 10) || 10));
|
|
139
|
+
const columns = {};
|
|
140
|
+
if (spec.columns && typeof spec.columns === 'object') {
|
|
141
|
+
for (const [colName, rawColPlan] of Object.entries(spec.columns)) {
|
|
142
|
+
const sanitized = (0, index_1.sanitizeColumnPlan)(rawColPlan);
|
|
143
|
+
if (sanitized)
|
|
144
|
+
columns[colName] = sanitized;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
tables[tableName] = {
|
|
148
|
+
mode,
|
|
149
|
+
rows,
|
|
150
|
+
columns,
|
|
151
|
+
truncate: Boolean(spec.truncate),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const rawSeed = b.seed !== undefined ? b.seed : rawPlan.options?.seed;
|
|
156
|
+
const seed = rawSeed != null && rawSeed !== '' ? Number(rawSeed) : null;
|
|
157
|
+
return {
|
|
158
|
+
name: typeof rawPlan.name === 'string' ? rawPlan.name : undefined,
|
|
159
|
+
description: typeof rawPlan.description === 'string' ? rawPlan.description : undefined,
|
|
160
|
+
rootTable: typeof rawPlan.rootTable === 'string' ? rawPlan.rootTable : undefined,
|
|
161
|
+
scope: rawPlan.scope || 'chain',
|
|
162
|
+
tables,
|
|
163
|
+
relationships: Array.isArray(rawPlan.relationships) ? rawPlan.relationships : undefined,
|
|
164
|
+
options: {
|
|
165
|
+
seed: Number.isFinite(seed) ? seed : null,
|
|
166
|
+
transaction: rawPlan.options ? Boolean(rawPlan.options.transaction ?? true) : true,
|
|
167
|
+
rollbackOnError: rawPlan.options ? Boolean(rawPlan.options.rollbackOnError ?? true) : true,
|
|
168
|
+
truncateAll: Boolean(b.truncate || rawPlan.options?.truncateAll),
|
|
169
|
+
batchSize: Number(rawPlan.options?.batchSize ?? 500) || 500,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
122
172
|
}
|
|
123
173
|
function resolvePkRows(info, ids) {
|
|
124
174
|
const list = Array.isArray(ids) ? ids : ids == null ? [] : [ids];
|
|
@@ -48,8 +48,7 @@ function registerImportExportRoutes(router, ctx) {
|
|
|
48
48
|
const col = header[i];
|
|
49
49
|
if (!typeMap.has(col))
|
|
50
50
|
continue;
|
|
51
|
-
|
|
52
|
-
fields.push({ column: col, value: (0, common_1.coerceFormValue)(raw, typeMap.get(col)) });
|
|
51
|
+
fields.push({ column: col, value: (0, common_1.coerceFormValue)(line[i] ?? '', typeMap.get(col)) });
|
|
53
52
|
}
|
|
54
53
|
return fields;
|
|
55
54
|
});
|
package/dist/routes/api/query.js
CHANGED
|
@@ -21,10 +21,7 @@ function registerQueryRoutes(router, ctx) {
|
|
|
21
21
|
const details = await (0, error_analyzer_1.analyzeSqlError)(rawSql, r.error ?? 'Script execution failed.', db);
|
|
22
22
|
return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400, details);
|
|
23
23
|
}
|
|
24
|
-
return (0, helpers_1.ok)(res, {
|
|
25
|
-
kind: 'script',
|
|
26
|
-
message: 'Script executed successfully.',
|
|
27
|
-
});
|
|
24
|
+
return (0, helpers_1.ok)(res, { kind: 'script', message: 'Script executed successfully.' });
|
|
28
25
|
}
|
|
29
26
|
if (kind === 'select' || kind === 'read' || kind === 'count') {
|
|
30
27
|
const r = await db.all(rawSql);
|
|
@@ -33,8 +30,7 @@ function registerQueryRoutes(router, ctx) {
|
|
|
33
30
|
return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400, details);
|
|
34
31
|
}
|
|
35
32
|
const rows = (r.data ?? []).map(common_1.normalizeRow);
|
|
36
|
-
|
|
37
|
-
return (0, helpers_1.ok)(res, { kind: 'select', columns, rows });
|
|
33
|
+
return (0, helpers_1.ok)(res, { kind: 'select', columns: rows.length ? Object.keys(rows[0]) : [], rows });
|
|
38
34
|
}
|
|
39
35
|
if (db.isReadOnly) {
|
|
40
36
|
return (0, helpers_1.fail)(res, 'Database is open in read-only mode — write statements are disabled.', 403);
|
package/dist/routes/api/rows.js
CHANGED
|
@@ -144,8 +144,7 @@ function registerRowRoutes(router, ctx) {
|
|
|
144
144
|
const fields = (0, helpers_1.buildFields)(info, req.body?.values ?? req.body ?? {});
|
|
145
145
|
if (!fields.length)
|
|
146
146
|
return (0, helpers_1.fail)(res, 'No valid column values provided.');
|
|
147
|
-
|
|
148
|
-
(0, helpers_1.ok)(res, { sql });
|
|
147
|
+
(0, helpers_1.ok)(res, { sql: (0, generator_1.generateInsert)(req.params.table, fields) });
|
|
149
148
|
}));
|
|
150
149
|
const generateUpdateHandler = (0, helpers_1.wrap)(async (req, res) => {
|
|
151
150
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
@@ -161,8 +160,7 @@ function registerRowRoutes(router, ctx) {
|
|
|
161
160
|
const fields = (0, helpers_1.buildUpdateFields)(info, req.body?.values ?? req.body ?? {}, nulls);
|
|
162
161
|
if (!fields.length)
|
|
163
162
|
return (0, helpers_1.fail)(res, 'No fields to update.');
|
|
164
|
-
|
|
165
|
-
(0, helpers_1.ok)(res, { sql });
|
|
163
|
+
(0, helpers_1.ok)(res, { sql: (0, generator_1.generateUpdate)(req.params.table, fields, where) });
|
|
166
164
|
});
|
|
167
165
|
router.put('/api/tables/:table/row/:id/generate', generateUpdateHandler);
|
|
168
166
|
router.post('/api/tables/:table/row/:id/generate', generateUpdateHandler);
|
|
@@ -286,13 +284,12 @@ function registerRowRoutes(router, ctx) {
|
|
|
286
284
|
const fkR = await db.getRowsByFk(item.table, ref.from, val);
|
|
287
285
|
if (fkR.success && fkR.data) {
|
|
288
286
|
const rows = fkR.data.rows.map(common_1.normalizeRow);
|
|
289
|
-
const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
290
287
|
results.push({
|
|
291
288
|
table: item.table,
|
|
292
289
|
from: ref.from,
|
|
293
290
|
to: targetCol,
|
|
294
291
|
value: val,
|
|
295
|
-
columns,
|
|
292
|
+
columns: rows.length > 0 ? Object.keys(rows[0]) : [],
|
|
296
293
|
rows,
|
|
297
294
|
count: fkR.data.total,
|
|
298
295
|
total: fkR.data.total,
|
|
@@ -329,8 +326,7 @@ function registerRowRoutes(router, ctx) {
|
|
|
329
326
|
const wheres = (0, helpers_1.resolvePkRows)(info, ids);
|
|
330
327
|
if (!wheres)
|
|
331
328
|
return (0, helpers_1.ok)(res, { references: [], total: 0 });
|
|
332
|
-
|
|
333
|
-
(0, helpers_1.ok)(res, impact);
|
|
329
|
+
(0, helpers_1.ok)(res, await (0, helpers_1.computeBulkImpact)(db, info, wheres));
|
|
334
330
|
}));
|
|
335
331
|
router.post('/api/tables/:table/rows/bulk-export', (0, helpers_1.wrap)(async (req, res) => {
|
|
336
332
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
package/dist/routes/api/seed.js
CHANGED
|
@@ -6,18 +6,36 @@ const index_1 = require("../../data/index");
|
|
|
6
6
|
const helpers_1 = require("./helpers");
|
|
7
7
|
function registerSeedRoutes(router, ctx) {
|
|
8
8
|
const { db } = ctx;
|
|
9
|
-
router.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
(0, helpers_1.ok)(res,
|
|
9
|
+
router.post('/api/seed/validate', (0, helpers_1.wrap)(async (req, res) => {
|
|
10
|
+
(0, helpers_1.ok)(res, await (0, index_1.validateGenerationPlan)(db, (0, helpers_1.parseUnifiedGenerationPlan)(req.body)));
|
|
11
|
+
}));
|
|
12
|
+
router.post('/api/seed/preview', (0, helpers_1.wrap)(async (req, res) => {
|
|
13
|
+
(0, helpers_1.ok)(res, await index_1.SeedEngine.executePlan(db, (0, helpers_1.parseUnifiedGenerationPlan)(req.body), { previewLimit: 50 }));
|
|
14
14
|
}));
|
|
15
|
-
router.
|
|
15
|
+
router.post('/api/seed/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
16
|
+
(0, helpers_1.ok)(res, await index_1.SeedEngine.executePlan(db, (0, helpers_1.parseUnifiedGenerationPlan)(req.body)));
|
|
17
|
+
}));
|
|
18
|
+
router.post('/api/seed/execute', (0, helpers_1.wrap)(async (req, res) => {
|
|
19
|
+
const plan = (0, helpers_1.parseUnifiedGenerationPlan)(req.body);
|
|
20
|
+
const truncate = Boolean(req.body?.truncate || plan.options?.truncateAll);
|
|
21
|
+
const gen = await index_1.SeedEngine.executePlan(db, plan, { truncateAll: truncate });
|
|
22
|
+
const exec = await (0, index_1.executeChainInsert)(db, gen, truncate);
|
|
23
|
+
(0, helpers_1.ok)(res, {
|
|
24
|
+
message: `${truncate ? 'Cleared tables and seeded' : 'Seeded'} ${exec.totalInserted} row(s) across ${Object.keys(exec.inserted).length} table(s) in ${exec.elapsedMs}ms.`,
|
|
25
|
+
inserted: exec.inserted,
|
|
26
|
+
totalInserted: exec.totalInserted,
|
|
27
|
+
elapsedMs: exec.elapsedMs,
|
|
28
|
+
warnings: exec.warnings,
|
|
29
|
+
}, 201);
|
|
30
|
+
}));
|
|
31
|
+
const getSeedConfigHandler = (0, helpers_1.wrap)(async (req, res) => {
|
|
16
32
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
17
33
|
if (!info)
|
|
18
34
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
19
35
|
(0, helpers_1.ok)(res, { table: req.params.table, columns: (0, index_1.buildColumnConfigs)(info), maxRows: index_1.MAX_SEED_ROWS });
|
|
20
|
-
})
|
|
36
|
+
});
|
|
37
|
+
router.get('/api/tables/:table/seed/config', getSeedConfigHandler);
|
|
38
|
+
router.get('/api/tables/:table/seed/plan', getSeedConfigHandler);
|
|
21
39
|
router.post('/api/tables/:table/seed/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
22
40
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
23
41
|
if (!info)
|
|
@@ -80,38 +98,35 @@ function registerSeedRoutes(router, ctx) {
|
|
|
80
98
|
const scope = rawScope === 'ancestors' || rawScope === 'descendants' || rawScope === 'all' || rawScope === 'single'
|
|
81
99
|
? rawScope
|
|
82
100
|
: 'chain';
|
|
83
|
-
|
|
84
|
-
(0, helpers_1.ok)(res, config);
|
|
101
|
+
(0, helpers_1.ok)(res, await (0, index_1.getErChainConfig)(db, req.params.table, scope));
|
|
85
102
|
}));
|
|
86
103
|
router.post('/api/tables/:table/seed/chain/preview', (0, helpers_1.wrap)(async (req, res) => {
|
|
87
104
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
88
105
|
if (!info)
|
|
89
106
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
90
|
-
const { scope, counts, plans } = (0, helpers_1.parseChainSeedRequest)(req.body);
|
|
107
|
+
const { scope, counts, plans, modes, seed } = (0, helpers_1.parseChainSeedRequest)(req.body);
|
|
91
108
|
const config = await (0, index_1.getErChainConfig)(db, req.params.table, scope);
|
|
92
109
|
const previewCounts = {};
|
|
93
110
|
for (const t of config.tables) {
|
|
94
111
|
previewCounts[t.name] = Math.min(50, counts[t.name] ?? t.suggestedCount ?? 10);
|
|
95
112
|
}
|
|
96
|
-
|
|
97
|
-
(0, helpers_1.ok)(res, gen);
|
|
113
|
+
(0, helpers_1.ok)(res, await (0, index_1.generateChainRows)(db, config, plans, previewCounts, false, modes, seed));
|
|
98
114
|
}));
|
|
99
115
|
router.post('/api/tables/:table/seed/chain/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
100
116
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
101
117
|
if (!info)
|
|
102
118
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
103
|
-
const { scope, counts, plans } = (0, helpers_1.parseChainSeedRequest)(req.body);
|
|
119
|
+
const { scope, counts, plans, modes, seed } = (0, helpers_1.parseChainSeedRequest)(req.body);
|
|
104
120
|
const config = await (0, index_1.getErChainConfig)(db, req.params.table, scope);
|
|
105
|
-
|
|
106
|
-
(0, helpers_1.ok)(res, gen);
|
|
121
|
+
(0, helpers_1.ok)(res, await (0, index_1.generateChainRows)(db, config, plans, counts, false, modes, seed));
|
|
107
122
|
}));
|
|
108
123
|
router.post('/api/tables/:table/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
|
|
109
124
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
110
125
|
if (!info)
|
|
111
126
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
112
|
-
const { scope, counts, plans, truncate } = (0, helpers_1.parseChainSeedRequest)(req.body);
|
|
127
|
+
const { scope, counts, plans, truncate, modes, seed } = (0, helpers_1.parseChainSeedRequest)(req.body);
|
|
113
128
|
const config = await (0, index_1.getErChainConfig)(db, req.params.table, scope);
|
|
114
|
-
const gen = await (0, index_1.generateChainRows)(db, config, plans, counts, truncate);
|
|
129
|
+
const gen = await (0, index_1.generateChainRows)(db, config, plans, counts, truncate, modes, seed);
|
|
115
130
|
const exec = await (0, index_1.executeChainInsert)(db, gen, truncate);
|
|
116
131
|
(0, helpers_1.ok)(res, {
|
|
117
132
|
message: `${truncate ? 'Cleared tables in reverse order and seeded' : 'Seeded'} ${exec.totalInserted} row(s) across ${Object.keys(exec.inserted).length} table(s) in ${exec.elapsedMs}ms.`,
|
|
@@ -130,17 +145,16 @@ function registerSeedRoutes(router, ctx) {
|
|
|
130
145
|
const rootTable = rawRoot || (await db.listTables()).data?.[0]?.name || '';
|
|
131
146
|
if (!rootTable)
|
|
132
147
|
return (0, helpers_1.fail)(res, 'No tables found in database.', 404);
|
|
133
|
-
|
|
134
|
-
(0, helpers_1.ok)(res, config);
|
|
148
|
+
(0, helpers_1.ok)(res, await (0, index_1.getErChainConfig)(db, rootTable, scope));
|
|
135
149
|
}));
|
|
136
150
|
router.post('/api/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
|
|
137
|
-
const { scope, counts, plans, truncate } = (0, helpers_1.parseChainSeedRequest)(req.body);
|
|
151
|
+
const { scope, counts, plans, truncate, modes, seed } = (0, helpers_1.parseChainSeedRequest)(req.body);
|
|
138
152
|
const rawRoot = String(req.body?.rootTable ?? '');
|
|
139
153
|
const rootTable = rawRoot || (await db.listTables()).data?.[0]?.name || '';
|
|
140
154
|
if (!rootTable)
|
|
141
155
|
return (0, helpers_1.fail)(res, 'No tables found in database.', 404);
|
|
142
156
|
const config = await (0, index_1.getErChainConfig)(db, rootTable, scope);
|
|
143
|
-
const gen = await (0, index_1.generateChainRows)(db, config, plans, counts, truncate);
|
|
157
|
+
const gen = await (0, index_1.generateChainRows)(db, config, plans, counts, truncate, modes, seed);
|
|
144
158
|
const exec = await (0, index_1.executeChainInsert)(db, gen, truncate);
|
|
145
159
|
(0, helpers_1.ok)(res, {
|
|
146
160
|
message: `${truncate ? 'Cleared tables in reverse order and seeded' : 'Seeded'} ${exec.totalInserted} row(s) across ${Object.keys(exec.inserted).length} table(s) in ${exec.elapsedMs}ms.`,
|
|
@@ -38,8 +38,7 @@ function registerTableRoutes(router, ctx) {
|
|
|
38
38
|
}));
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
catch {
|
|
42
|
-
}
|
|
41
|
+
catch { }
|
|
43
42
|
}
|
|
44
43
|
(0, helpers_1.ok)(res, options);
|
|
45
44
|
}));
|
|
@@ -75,30 +74,23 @@ function registerTableRoutes(router, ctx) {
|
|
|
75
74
|
return (0, helpers_1.fail)(res, 'No tables specified.');
|
|
76
75
|
const dropped = [];
|
|
77
76
|
const failed = [];
|
|
78
|
-
if (force && db.dialect === 'sqlite')
|
|
77
|
+
if (force && db.dialect === 'sqlite')
|
|
79
78
|
await db.run('PRAGMA foreign_keys = OFF');
|
|
80
|
-
}
|
|
81
79
|
for (const table of tables) {
|
|
82
80
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
else {
|
|
89
|
-
const r = await db.dropTable(table);
|
|
90
|
-
if (!r.success)
|
|
91
|
-
throw new Error(r.error ?? 'Failed to drop table.');
|
|
92
|
-
}
|
|
81
|
+
const r = force && db.dialect === 'postgres'
|
|
82
|
+
? await db.run(`DROP TABLE IF EXISTS ${(0, generator_1.quoteIdentifier)(table)} CASCADE`)
|
|
83
|
+
: await db.dropTable(table);
|
|
84
|
+
if (!r.success)
|
|
85
|
+
throw new Error(r.error ?? 'Failed to drop table.');
|
|
93
86
|
dropped.push(table);
|
|
94
87
|
}
|
|
95
88
|
catch (err) {
|
|
96
89
|
failed.push({ table, error: err.message });
|
|
97
90
|
}
|
|
98
91
|
}
|
|
99
|
-
if (force && db.dialect === 'sqlite')
|
|
92
|
+
if (force && db.dialect === 'sqlite')
|
|
100
93
|
await db.run('PRAGMA foreign_keys = ON');
|
|
101
|
-
}
|
|
102
94
|
(0, helpers_1.ok)(res, {
|
|
103
95
|
dropped,
|
|
104
96
|
failed,
|
|
@@ -114,18 +106,13 @@ function registerTableRoutes(router, ctx) {
|
|
|
114
106
|
return (0, helpers_1.fail)(res, 'No tables specified.');
|
|
115
107
|
const cleared = [];
|
|
116
108
|
const failed = [];
|
|
117
|
-
if (force && db.dialect === 'sqlite')
|
|
109
|
+
if (force && db.dialect === 'sqlite')
|
|
118
110
|
await db.run('PRAGMA foreign_keys = OFF');
|
|
119
|
-
}
|
|
120
111
|
for (const table of tables) {
|
|
121
112
|
try {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
r = await db.run(`DELETE FROM ${(0, generator_1.quoteIdentifier)(table)}`);
|
|
128
|
-
}
|
|
113
|
+
const r = force && db.dialect === 'postgres'
|
|
114
|
+
? await db.run(`TRUNCATE ${(0, generator_1.quoteIdentifier)(table)} RESTART IDENTITY CASCADE`)
|
|
115
|
+
: await db.run(`DELETE FROM ${(0, generator_1.quoteIdentifier)(table)}`);
|
|
129
116
|
if (!r.success)
|
|
130
117
|
throw new Error(r.error ?? 'Failed to clear table.');
|
|
131
118
|
cleared.push(table);
|
|
@@ -134,9 +121,8 @@ function registerTableRoutes(router, ctx) {
|
|
|
134
121
|
failed.push({ table, error: err.message });
|
|
135
122
|
}
|
|
136
123
|
}
|
|
137
|
-
if (force && db.dialect === 'sqlite')
|
|
124
|
+
if (force && db.dialect === 'sqlite')
|
|
138
125
|
await db.run('PRAGMA foreign_keys = ON');
|
|
139
|
-
}
|
|
140
126
|
(0, helpers_1.ok)(res, {
|
|
141
127
|
cleared,
|
|
142
128
|
failed,
|
|
@@ -151,8 +137,7 @@ function registerTableRoutes(router, ctx) {
|
|
|
151
137
|
if (!Array.isArray(columns) || columns.length === 0)
|
|
152
138
|
return (0, helpers_1.fail)(res, 'At least one column is required.');
|
|
153
139
|
try {
|
|
154
|
-
|
|
155
|
-
(0, helpers_1.ok)(res, { sql });
|
|
140
|
+
(0, helpers_1.ok)(res, { sql: (0, generator_1.generateCreateTable)(name, columns) });
|
|
156
141
|
}
|
|
157
142
|
catch (err) {
|
|
158
143
|
(0, helpers_1.fail)(res, err.message);
|
|
@@ -181,7 +166,7 @@ function registerTableRoutes(router, ctx) {
|
|
|
181
166
|
router.post('/api/tables/create', createTableHandler);
|
|
182
167
|
router.post('/api/tables/:table/columns', (0, helpers_1.wrap)(async (req, res) => {
|
|
183
168
|
const col = req.body;
|
|
184
|
-
if (!col
|
|
169
|
+
if (!col?.name)
|
|
185
170
|
return (0, helpers_1.fail)(res, 'Column name is required.');
|
|
186
171
|
const r = await db.addColumn(req.params.table, col);
|
|
187
172
|
if (!r.success)
|