admindb 2.3.1 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -16
- package/dist/app.js +119 -171
- package/dist/public/css/app.css +1 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/databases.js +1 -1
- package/dist/public/js/designer.js +1 -1
- package/dist/public/js/forms.js +1 -1
- package/dist/public/js/home.js +1 -1
- package/dist/public/js/inspector.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/public/js/schema.js +1 -1
- package/dist/public/js/seed.js +1 -1
- package/dist/routes/api/erd.js +2 -2
- package/dist/routes/api/helpers.d.ts +1 -1
- package/dist/routes/api/import-export.js +8 -6
- package/dist/routes/api/index.js +16 -9
- package/dist/routes/api/query.js +8 -5
- package/dist/routes/api/rows.js +36 -21
- package/dist/routes/api/seed.js +29 -16
- package/dist/routes/api/tables.js +32 -18
- package/dist/routes/databases.js +2 -0
- package/dist/routes/pages.d.ts +1 -1
- package/dist/routes/pages.js +137 -43
- package/dist/views/layouts/main.hbs +1 -1
- package/dist/views/pages/databases.hbs +1 -1
- package/dist/views/pages/erd.hbs +1 -1
- package/dist/views/pages/form.hbs +1 -1
- package/dist/views/pages/home.hbs +1 -1
- package/dist/views/pages/info.hbs +1 -1
- package/dist/views/pages/schema.hbs +1 -1
- package/dist/views/pages/seed-select.hbs +6 -5
- package/dist/views/pages/seed.hbs +3 -3
- package/dist/views/pages/table.hbs +1 -1
- package/dist/views/partials/navbar.hbs +1 -1
- package/dist/views/partials/sidebar.hbs +1 -1
- package/docs/API.md +60 -55
- package/package.json +1 -1
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=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)}();
|
|
1
|
+
!function(){"use strict";const e=document.getElementById("seed-config");if(!e)return;const t=JSON.parse(e.textContent),n=encodeURIComponent(window.APP&&window.APP.dbId||t&&t.dbId||""),a=t.table,s=t.columns||[],o=new URLSearchParams(window.location.search),l=t.mode||o.get("mode")||"single";let i=2,c="chain"===l?"chain":"single",d=null;const r={},m={},p={};let u=null,g=null,b=a,f=null;r[a]={},m[a]="generate",p[a]=10,s.forEach(e=>{r[a][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 x=document.getElementById("btn-select-single"),h=document.getElementById("btn-select-chain"),v=document.getElementById("btn-next-1"),y=document.getElementById("btn-next-2"),w=document.getElementById("btn-prev-2"),E=document.getElementById("btn-prev-3"),L=document.getElementById("config-single"),I=document.getElementById("config-chain"),k=document.getElementById("seed-columns"),$=document.getElementById("seed-count"),S=document.getElementById("seed-count-dec"),B=document.getElementById("seed-count-inc"),C=document.getElementById("seed-col-search"),M=document.getElementById("col-count-badge"),T=document.getElementById("plan-seed-input"),U=document.getElementById("btn-random-seed"),N=document.getElementById("plan-transaction-opt"),q=document.getElementById("plan-rollback-opt"),A=document.getElementById("chain-scope"),H=document.getElementById("chain-pipeline-container"),R=document.getElementById("chain-summary-badge"),j=document.getElementById("validation-status-dot"),O=document.getElementById("validation-status-text"),P=document.getElementById("validation-issues-list"),_=document.getElementById("validation-summary-chip"),D=document.getElementById("preview-tab-grid"),z=document.getElementById("preview-tab-sql"),F=document.getElementById("preview-view-grid"),J=document.getElementById("preview-view-sql"),V=document.getElementById("seed-preview-thead"),Q=document.getElementById("seed-preview-tbody"),W=document.getElementById("preview-sql-code"),G=document.getElementById("btn-copy-sql"),K=document.getElementById("seed-chain-table-tabs"),Y=document.getElementById("seed-refresh-preview"),X=document.getElementById("seed-truncate"),Z=document.getElementById("seed-truncate-banner"),ee=document.getElementById("seed-truncate-dialog"),te=document.getElementById("seed-truncate-confirm"),ne=document.getElementById("seed-truncate-cancel"),ae=document.getElementById("seed-insert"),se=document.getElementById("seed-feedback"),oe=document.getElementById("btn-profiles-modal"),le=document.getElementById("seed-profiles-dialog"),ie=document.getElementById("btn-close-profiles"),ce=document.getElementById("profile-name-input"),de=document.getElementById("btn-save-profile"),re=document.getElementById("profiles-list-container"),me=document.getElementById("btn-export-plan-json"),pe=document.getElementById("input-import-plan-json");function ue(e){return window.UI&&UI.escapeHtml?UI.escapeHtml(e):String(null==e?"":e)}function ge(e){return e[Math.floor(Math.random()*e.length)]}function be(e,t){return Math.floor(Math.random()*(t-e+1))+e}function fe(e=400){f&&clearTimeout(f),f=setTimeout(()=>{!async function(){"chain"!==c||u||await Le();const e=xe();try{he(await Api.post("/api/seed/"+n+"/validate",{plan:e}))}catch{}}(),Ie()},e)}function xe(){const e="single"===c,t={name:e?`Single Table ${a}`:`ER Relational Seed ${a}`,rootTable:a,scope:e?"single":A?.value||"chain",tables:{},options:{seed:null!=d&&Number.isFinite(d)?d:null,transaction:N?.checked??!0,rollbackOnError:q?.checked??!0,truncateAll:X?.checked??!1}};return e?t.tables[a]={mode:"generate",rows:Math.max(1,parseInt($?.value,10)||10),columns:r[a]||{},truncate:X?.checked??!1}:u&&u.tables&&(u.tables.forEach(e=>{const n=m[e.name]||"generate";t.tables[e.name]={mode:n,rows:Math.max(1,p[e.name]||e.suggestedCount||10),columns:r[e.name]||{},truncate:X?.checked??!1}}),t.relationships=u.relationships),t}function he(e){if(!e)return;const t=(e.issues||[]).filter(e=>"error"===e.type),n=(e.issues||[]).filter(e=>"warning"===e.type);j&&O&&_&&(0===t.length?(j.className="w-2 h-2 rounded-full bg-success",O.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"):(j.className="w-2 h-2 rounded-full bg-error animate-pulse",O.textContent=`${t.length} Issue(s)`,_.className="badge badge-sm badge-error font-mono text-[10px] font-bold",_.textContent="Action Required")),P&&(P.innerHTML="",0===t.length&&0===n.length?P.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=>{P.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>${ue(e.table||"")}:</strong> ${ue(e.message)}</span>\n </div>\n `}),n.forEach(e=>{P.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>${ue(e.table||"")}:</strong> ${ue(e.message)}</span>\n </div>\n `})))}function ve(e){i=e}function ye(e,t=!1){if(c="chain"===e?"chain":"single",x&&x.classList.toggle("active","single"===c),h&&h.classList.toggle("active","chain"===c),L&&L.classList.toggle("hidden","single"!==c),I&&I.classList.toggle("hidden","chain"!==c),"single"===c?k&&0===k.children.length&&we(a,s,k):"chain"===c&&(u||Le()),t&&window.history&&window.history.replaceState){const e=new URL(window.location.href);e.searchParams.set("mode",c),window.history.replaceState({},"",e.toString())}fe(100)}function we(e,t,n){n.innerHTML="",r[e]||(r[e]={}),t.forEach(a=>{r[e][a.name]||(r[e][a.name]=Object.assign({},a.defaultPlan||{strategy:"skip"}));const s=r[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">↳ ${ue(a.fk.table)}.${ue(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">${ue(a.name)}</span>\n <span class="font-mono text-[10px] text-base-content/40 uppercase">${ue(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":""}>${ue(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 i=o.querySelector(".col-strategy-select"),c=o.querySelector(".col-options-box"),d=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?ge(t.values):"(empty list)":"pattern"===a?(t.pattern||"INV-####").replace(/#/g,()=>be(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(be(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?ge(["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,r[e][a.name],t);d.textContent=n,o.dataset.strategyType="template"===s.strategy?"template":"skip"===s.strategy?"skip":"null"===s.strategy?"null":"fixed"===s.strategy?"fixed":"generate",fe(400)};d.addEventListener("click",()=>{navigator.clipboard.writeText(d.textContent||""),window.UI&&UI.showToast&&UI.showToast(`Copied "${d.textContent}" to clipboard`,"info")}),i.addEventListener("change",()=>{r[e][a.name].strategy=i.value,Ee(c,a,r[e][a.name],t,m),m()}),Ee(c,a,s,t,m),m(),n.appendChild(o)}),M&&(M.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 i=(t,a,o="text",l="")=>{const i=document.createElement("div");i.innerHTML=`<label class="block text-[9px] uppercase font-bold text-base-content/50 mb-0.5">${ue(t)}</label>`;const c=document.createElement("textarea"===o?"textarea":"input");"textarea"!==o&&(c.type=o),c.placeholder=l,c.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)?c.value=d.join("\n"):void 0!==d&&(c.value=d),c.addEventListener("input",()=>{n[a]="textarea"===o?c.value.split("\n").map(e=>e.trim()).filter(Boolean):"number"===o?""===c.value?void 0:Number(c.value):c.value,s()}),i.appendChild(c),e.appendChild(i),c};if("fixed"===o&&i("Constant Value","value","text","Default string"),"list"===o&&i("Choices (One per line)","values","textarea","admin\nmember\nguest"),"template"===o){const o=i("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 c=document.createElement("div");c.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()},c.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()},c.appendChild(t)}),l.appendChild(c),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,i("Min Value","min","number","1"),i("Max Value","max","number","1000"),"decimal"===o&&i("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,i("From Date","from","text","2020-01-01"),i("To Date","to","text","2026-12-31"),e=n}if("pattern"===o&&i("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,i("Start Sequence","start","number","1"),i("Step","step","number","1"),e=n}}async function Le(){const e=A?.value||"chain";H.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{u=await Api.get(`/api/tables/${n}/${encodeURIComponent(a)}/seed/chain?scope=${e}`),R&&(R.textContent=`${u.tables.length} Tables`),H.innerHTML="",u.tables.forEach((e,t)=>{p[e.name]||(p[e.name]=e.suggestedCount||10),m[e.name]||(m[e.name]="generate"),r[e.name]||(r[e.name]={},(e.columns||[]).forEach(t=>{r[e.name][t.name]=Object.assign({},t.defaultPlan||{strategy:"skip"})}));const n=e.name===a,s=document.createElement("div");s.className="pipeline-node",s.dataset.nodeMode=m[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 ${n?"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">${ue(e.name)}</span>\n ${n?'<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="${ue(e.name)}">\n <option value="generate" ${"generate"===m[e.name]?"selected":""}>🌱 Generate Data</option>\n <option value="use_existing" ${"use_existing"===m[e.name]?"selected":""}>📦 Use Existing</option>\n <option value="generate_if_empty" ${"generate_if_empty"===m[e.name]?"selected":""}>⚡ If Empty</option>\n <option value="skip" ${"skip"===m[e.name]?"selected":""}>⛔ Skip</option>\n </select>\n\n <input type="number" min="1" max="5000" value="${p[e.name]}"\n class="chain-table-rows field-input !py-0.5 w-16 text-right font-mono font-bold text-xs" data-table="${ue(e.name)}" />\n\n <button type="button" class="btn btn-xs btn-ghost border border-base-300 btn-toggle-cols" data-table="${ue(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"),i=s.querySelector(".btn-toggle-cols"),c=s.querySelector(".chain-col-drawer"),d=s.querySelector(".chain-cols-container");o.addEventListener("change",()=>{m[e.name]=o.value,s.dataset.nodeMode=o.value,l.disabled="use_existing"===o.value||"skip"===o.value,fe(300)}),l.addEventListener("input",()=>{p[e.name]=Math.max(1,parseInt(l.value,10)||10),fe(400)}),i.addEventListener("click",()=>{const t=c.classList.toggle("hidden");i.textContent=t?"Cols ▼":"Hide ▲",t||0!==d.children.length||we(e.name,e.columns,d)}),H.appendChild(s)}),fe(100)}catch(e){H.innerHTML=`<div class="alert alert-error text-xs font-semibold">${ue(e.message)}</div>`}}async function Ie(){if(!Q)return;"chain"!==c||u||await Le(),Q.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>',se&&se.classList.add("hidden"),K&&K.classList.add("hidden"),ae&&(ae.disabled=!0);const e=xe();try{g=await Api.post("/api/seed/"+n+"/preview",{plan:e}),ae&&(ae.disabled=!1),g.validation&&he(g.validation),W&&(W.textContent=g.sql||"-- No SQL statements generated.");const t="single"===c,s=t?[a]:g.executionOrder||Object.keys(g.tableResults||{});!t&&s.includes(b)||(b=a),!t&&s.length>1&&K?(K.classList.remove("hidden"),K.innerHTML="",s.forEach(e=>{const t=g.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===b?"tab-active text-primary font-bold border-b-2 border-b-primary":"text-base-content/60"),n.innerHTML=`${ue(e)} <span class="badge badge-xs ml-1.5 ${e===b?"badge-primary":"badge-ghost"}">${t?.rows?.length||0}</span>`,n.onclick=t=>{t.preventDefault(),b=e,Array.from(K.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=g.tableResults[b];ke(b,a?.previewRows||[])},K.appendChild(n)})):K&&K.classList.add("hidden");const o=g.tableResults[b]||{previewRows:[]};ke(b,o?.previewRows||[]),$e(g.warnings||[])}catch(e){Q&&(Q.innerHTML=`<tr><td colspan="100" class="text-center py-10 text-error text-xs font-bold">${ue(e.message)}</td></tr>`)}}function ke(e,t){if(!V||!Q)return;if(V.innerHTML="",Q.innerHTML="",!t||0===t.length)return void(Q.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(Q.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">${ue(e)}</th>`).join(""),V.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]">${ue(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="${ue(String(n))}">${ue(String(n))}</td>`}),n.innerHTML=s,Q.appendChild(n)})}function $e(e){se&&(e&&0!==e.length?(se.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>${ue(e)}</p>`).join("")}</div>\n </div>\n `,se.classList.remove("hidden")):se.classList.add("hidden"))}C&&C.addEventListener("input",()=>{const e=C.value.toLowerCase().trim(),t=k.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++}),M&&(M.textContent=`${n} of ${s.length} cols`)});const Se="admindb_seed_profiles";function Be(){try{const e=localStorage.getItem(Se);return e?JSON.parse(e):[]}catch{return[]}}function Ce(){try{const e=Be();if(re.innerHTML="",!e||0===e.length)return void(re.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">${ue(e.name)}</div>\n <div class="text-[10px] text-base-content/50">${ue(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="${ue(e.id)}">Apply</button>\n <button type="button" class="btn btn-xs btn-ghost text-error btn-del-profile" data-id="${ue(e.id)}">✕</button>\n </div>\n `,t.querySelector(".btn-load-profile").onclick=()=>Me(e),t.querySelector(".btn-del-profile").onclick=()=>{!function(e){const t=Be().filter(t=>t.id!==e);try{localStorage.setItem(Se,JSON.stringify(t))}catch{}}(e.id),Ce()},re.appendChild(t)})}catch{re.innerHTML='<div class="py-4 text-center text-xs text-error font-semibold">Failed to load profiles.</div>'}}function Me(e){if(!e||!e.plan)return;const t=e.plan;null!=t.options?.seed&&(d=t.options.seed,T&&(T.value=d)),t.tables&&(Object.entries(t.tables).forEach(([e,t])=>{m[e]=t.mode||"generate",p[e]=t.rows||10,t.columns&&(r[e]=Object.assign({},t.columns))}),t.tables[a]&&$&&($.value=t.tables[a].rows||10)),le&&le.close(),window.UI&&UI.showToast&&UI.showToast(`Applied profile "${e.name}".`,"success"),k&&we(a,s,k),fe(100)}async function Te(){"chain"!==c||u||await Le();const e=xe();ae&&(ae.disabled=!0,ae.classList.add("loading"));try{const t=await Api.post("/api/seed/"+n+"/execute",{plan:e,truncate:X?.checked??!1});window.UI&&UI.showToast&&UI.showToast(t.message||`Seeded ${t.totalInserted} rows successfully.`,"success"),$e(t.warnings||[])}catch(e){window.UI&&UI.showError&&UI.showError(e.message)}finally{ae&&(ae.disabled=!1,ae.classList.remove("loading")),ee&&ee.close()}}x&&x.addEventListener("click",()=>ye("single")),h&&h.addEventListener("click",()=>ye("chain")),v&&v.addEventListener("click",()=>ve(2)),w&&w.addEventListener("click",()=>ve(1)),y&&y.addEventListener("click",()=>ve(3)),E&&E.addEventListener("click",()=>ve(2)),S&&B&&$&&(S.addEventListener("click",()=>{$.value=Math.max(1,(parseInt($.value,10)||10)-1),fe(300)}),B.addEventListener("click",()=>{$.value=Math.min(5e3,(parseInt($.value,10)||10)+1),fe(300)}),$.addEventListener("input",()=>fe(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"),$&&e.dataset.count&&($.value=e.dataset.count,fe(200))})}),T&&T.addEventListener("input",()=>{d=""===T.value?null:Number(T.value),fe(400)}),U&&U.addEventListener("click",()=>{d=Math.floor(1e6*Math.random()),T.value=d,window.UI&&UI.showToast&&UI.showToast(`Set Random Seed: ${d}`,"info"),fe(100)}),A&&A.addEventListener("change",()=>Le()),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);u&&u.tables&&u.tables.forEach(e=>{p[e.name]=Math.max(1,Math.round((e.suggestedCount||10)*t));const n=H?.querySelector(`.chain-table-rows[data-table="${CSS.escape(e.name)}"]`);n&&(n.value=p[e.name])}),fe(200)})}),D&&z&&(D.addEventListener("click",()=>{D.classList.add("btn-active"),z.classList.remove("btn-active"),F.classList.remove("hidden"),J.classList.add("hidden")}),z.addEventListener("click",()=>{z.classList.add("btn-active"),D.classList.remove("btn-active"),J.classList.remove("hidden"),F.classList.add("hidden")})),G&&G.addEventListener("click",()=>{W&&(navigator.clipboard.writeText(W.textContent||""),window.UI&&UI.showToast&&UI.showToast("SQL script copied to clipboard.","success"))}),Y&&Y.addEventListener("click",()=>Ie()),X&&Z&&X.addEventListener("change",()=>{Z.classList.toggle("hidden",!X.checked)}),ae&&ae.addEventListener("click",()=>{X?.checked?ee.showModal():Te()}),te&&te.addEventListener("click",()=>Te()),ne&&ne.addEventListener("click",()=>ee.close()),oe&&le&&(oe.addEventListener("click",()=>{le.showModal(),Ce()}),ie&&ie.addEventListener("click",()=>le.close())),document.querySelectorAll(".btn-preset-profile").forEach(e=>{e.addEventListener("click",()=>{const t=e.dataset.preset,n="smoke"===t?10:"realistic"===t?100:1e3;$&&($.value=n),Object.keys(p).forEach(e=>p[e]=n),le&&le.close(),window.UI&&UI.showToast&&UI.showToast(`Applied ${n} rows preset blueprint.`,"info"),fe(100)})}),de&&de.addEventListener("click",()=>{const e=ce.value.trim();if(!e)return;!function(e,t,n){const a=Be(),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(Se,JSON.stringify(a))}catch{}}(e,xe()),ce.value="",window.UI&&UI.showToast&&UI.showToast(`Saved profile "${e}".`,"success"),Ce()}),me&&me.addEventListener("click",()=>{const e=xe(),t=new Blob([JSON.stringify(e,null,2)],{type:"application/json"}),n=document.createElement("a");n.href=URL.createObjectURL(t),n.download=`seed-plan-${a}-${Date.now()}.json`,n.click()}),pe&&pe.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);Me({name:t.name,plan:n})}catch{window.UI&&UI.showError&&UI.showError("Invalid JSON seed plan file.")}},n.readAsText(t)}),C&&C.addEventListener("input",()=>{const e=C.value.trim().toLowerCase(),t=k?.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++}),M&&(M.textContent=`${n} cols`)}),k&&0===k.children.length&&we(a,s,k),x&&x.addEventListener("click",()=>ye("single",!0)),h&&h.addEventListener("click",()=>ye("chain",!0)),ye(l,!1)}();
|
package/dist/routes/api/erd.js
CHANGED
|
@@ -3,9 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerErdRoutes = registerErdRoutes;
|
|
4
4
|
const helpers_1 = require("./helpers");
|
|
5
5
|
function registerErdRoutes(router, ctx) {
|
|
6
|
-
|
|
7
|
-
router.get('/api/erd', async (_req, res) => {
|
|
6
|
+
router.get('/api/erd/:db', async (req, res) => {
|
|
8
7
|
try {
|
|
8
|
+
const db = ctx.getDb(req);
|
|
9
9
|
const tablesResult = await db.listTables();
|
|
10
10
|
if (!tablesResult.success || !tablesResult.data) {
|
|
11
11
|
return (0, helpers_1.fail)(res, tablesResult.error ?? 'Failed to list tables.', 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,type GenerationPlan,type TableGenerationMode} from '../../data/index';export interface ApiContext{
|
|
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{getDb:(req:Request)=>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;}>;
|
|
@@ -5,8 +5,8 @@ const common_1 = require("../../utils/common");
|
|
|
5
5
|
const csv_1 = require("../../utils/csv");
|
|
6
6
|
const helpers_1 = require("./helpers");
|
|
7
7
|
function registerImportExportRoutes(router, ctx) {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
router.get('/api/tables/:db/:table/export', (0, helpers_1.wrap)(async (req, res) => {
|
|
9
|
+
const db = ctx.getDb(req);
|
|
10
10
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
11
11
|
if (!info)
|
|
12
12
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -27,6 +27,7 @@ function registerImportExportRoutes(router, ctx) {
|
|
|
27
27
|
res.send((0, csv_1.toCsv)(rows, columns));
|
|
28
28
|
}));
|
|
29
29
|
const importCsvHandler = (0, helpers_1.wrap)(async (req, res) => {
|
|
30
|
+
const db = ctx.getDb(req);
|
|
30
31
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
31
32
|
if (!info)
|
|
32
33
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -57,10 +58,11 @@ function registerImportExportRoutes(router, ctx) {
|
|
|
57
58
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to import CSV.');
|
|
58
59
|
(0, helpers_1.ok)(res, { message: `${r.data?.inserted ?? 0} row(s) imported.`, inserted: r.data?.inserted });
|
|
59
60
|
});
|
|
60
|
-
router.post('/api/tables/:table/import', importCsvHandler);
|
|
61
|
-
router.post('/api/tables/:table/rows/import', importCsvHandler);
|
|
62
|
-
router.post('/api/tables/:table/import/csv', importCsvHandler);
|
|
63
|
-
router.post('/api/query/export', (0, helpers_1.wrap)(async (req, res) => {
|
|
61
|
+
router.post('/api/tables/:db/:table/import', importCsvHandler);
|
|
62
|
+
router.post('/api/tables/:db/:table/rows/import', importCsvHandler);
|
|
63
|
+
router.post('/api/tables/:db/:table/import/csv', importCsvHandler);
|
|
64
|
+
router.post('/api/query/:db/export', (0, helpers_1.wrap)(async (req, res) => {
|
|
65
|
+
const db = ctx.getDb(req);
|
|
64
66
|
const sql = String(req.body?.sql ?? '').trim();
|
|
65
67
|
const format = String(req.body?.format ?? 'csv').toLowerCase();
|
|
66
68
|
if (!sql)
|
package/dist/routes/api/index.js
CHANGED
|
@@ -9,23 +9,29 @@ const query_1 = require("./query");
|
|
|
9
9
|
const erd_1 = require("./erd");
|
|
10
10
|
const export_1 = require("../../db/export");
|
|
11
11
|
function registerApi(router, ctx) {
|
|
12
|
-
const { db } = ctx;
|
|
13
12
|
router.use((req, res, next) => {
|
|
14
|
-
if (
|
|
13
|
+
if (req.method === 'GET')
|
|
15
14
|
return next();
|
|
16
15
|
const p = req.path;
|
|
17
16
|
if (p.endsWith('/generate') ||
|
|
18
17
|
p.endsWith('/preview') ||
|
|
19
|
-
p.
|
|
20
|
-
p.endsWith('/api/query') ||
|
|
18
|
+
p.includes('/api/query/') ||
|
|
21
19
|
p.endsWith('/rows/bulk-impact') ||
|
|
22
20
|
p.endsWith('/rows/bulk-export')) {
|
|
23
21
|
return next();
|
|
24
22
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
try {
|
|
24
|
+
const db = ctx.getDb(req);
|
|
25
|
+
if (db && db.isReadOnly) {
|
|
26
|
+
return res.status(403).json({
|
|
27
|
+
success: false,
|
|
28
|
+
error: 'Database is open in read-only mode — write operations are disabled.',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
}
|
|
34
|
+
return next();
|
|
29
35
|
});
|
|
30
36
|
(0, tables_1.registerTableRoutes)(router, ctx);
|
|
31
37
|
(0, rows_1.registerRowRoutes)(router, ctx);
|
|
@@ -33,8 +39,9 @@ function registerApi(router, ctx) {
|
|
|
33
39
|
(0, seed_1.registerSeedRoutes)(router, ctx);
|
|
34
40
|
(0, query_1.registerQueryRoutes)(router, ctx);
|
|
35
41
|
(0, erd_1.registerErdRoutes)(router, ctx);
|
|
36
|
-
router.get('/api/info/ddl', async (
|
|
42
|
+
router.get('/api/info/:db/ddl', async (req, res) => {
|
|
37
43
|
try {
|
|
44
|
+
const db = ctx.getDb(req);
|
|
38
45
|
const dump = await (0, export_1.generateSchemaDump)(db);
|
|
39
46
|
if (!dump.success) {
|
|
40
47
|
return res.status(500).json({ success: false, error: dump.error });
|
package/dist/routes/api/query.js
CHANGED
|
@@ -6,8 +6,8 @@ const error_analyzer_1 = require("../../sql/error-analyzer");
|
|
|
6
6
|
const common_1 = require("../../utils/common");
|
|
7
7
|
const helpers_1 = require("./helpers");
|
|
8
8
|
function registerQueryRoutes(router, ctx) {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
router.post('/api/query/:db', (0, helpers_1.wrap)(async (req, res) => {
|
|
10
|
+
const db = ctx.getDb(req);
|
|
11
11
|
const rawSql = String(req.body?.sql ?? '').trim();
|
|
12
12
|
if (!rawSql)
|
|
13
13
|
return (0, helpers_1.fail)(res, 'SQL query is required.');
|
|
@@ -47,13 +47,15 @@ function registerQueryRoutes(router, ctx) {
|
|
|
47
47
|
message: changes != null ? `${changes} row(s) affected.` : 'Statement executed successfully.',
|
|
48
48
|
});
|
|
49
49
|
}));
|
|
50
|
-
router.get('/api/queries', (0, helpers_1.wrap)(async (
|
|
50
|
+
router.get('/api/queries/:db', (0, helpers_1.wrap)(async (req, res) => {
|
|
51
|
+
const db = ctx.getDb(req);
|
|
51
52
|
const r = await db.listSavedQueries();
|
|
52
53
|
if (!r.success)
|
|
53
54
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to load queries.', 500);
|
|
54
55
|
(0, helpers_1.ok)(res, r.data);
|
|
55
56
|
}));
|
|
56
|
-
router.post('/api/queries', (0, helpers_1.wrap)(async (req, res) => {
|
|
57
|
+
router.post('/api/queries/:db', (0, helpers_1.wrap)(async (req, res) => {
|
|
58
|
+
const db = ctx.getDb(req);
|
|
57
59
|
const name = String(req.body?.name ?? '').trim();
|
|
58
60
|
const sql = String(req.body?.sql ?? '').trim();
|
|
59
61
|
if (!name)
|
|
@@ -65,7 +67,8 @@ function registerQueryRoutes(router, ctx) {
|
|
|
65
67
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to save query.', 400);
|
|
66
68
|
(0, helpers_1.ok)(res, { message: 'Query saved.' }, 201);
|
|
67
69
|
}));
|
|
68
|
-
router.delete('/api/queries/:id', (0, helpers_1.wrap)(async (req, res) => {
|
|
70
|
+
router.delete('/api/queries/:db/:id', (0, helpers_1.wrap)(async (req, res) => {
|
|
71
|
+
const db = ctx.getDb(req);
|
|
69
72
|
const r = await db.deleteSavedQuery(req.params.id);
|
|
70
73
|
if (!r.success)
|
|
71
74
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to delete query.', 400);
|
package/dist/routes/api/rows.js
CHANGED
|
@@ -7,8 +7,8 @@ const csv_1 = require("../../utils/csv");
|
|
|
7
7
|
const generator_1 = require("../../sql/generator");
|
|
8
8
|
const helpers_1 = require("./helpers");
|
|
9
9
|
function registerRowRoutes(router, ctx) {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
router.get('/api/tables/:db/:table/rows', (0, helpers_1.wrap)(async (req, res) => {
|
|
11
|
+
const db = ctx.getDb(req);
|
|
12
12
|
const page = Number.parseInt(String(req.query.page ?? '1'), 10);
|
|
13
13
|
const limit = Number.parseInt(String(req.query.limit ?? '50'), 10);
|
|
14
14
|
const orderBy = req.query.orderBy ? String(req.query.orderBy) : undefined;
|
|
@@ -28,14 +28,16 @@ function registerRowRoutes(router, ctx) {
|
|
|
28
28
|
limit,
|
|
29
29
|
});
|
|
30
30
|
}));
|
|
31
|
-
router.get('/api/tables/:table/rows/count', (0, helpers_1.wrap)(async (req, res) => {
|
|
31
|
+
router.get('/api/tables/:db/:table/rows/count', (0, helpers_1.wrap)(async (req, res) => {
|
|
32
|
+
const db = ctx.getDb(req);
|
|
32
33
|
const filters = (0, common_1.parseFilters)(req.query.f);
|
|
33
34
|
const r = await db.getRowCount(req.params.table, filters);
|
|
34
35
|
if (!r.success)
|
|
35
36
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to count rows.');
|
|
36
37
|
(0, helpers_1.ok)(res, { count: r.data });
|
|
37
38
|
}));
|
|
38
|
-
router.get('/api/tables/:table/row/:id', (0, helpers_1.wrap)(async (req, res) => {
|
|
39
|
+
router.get('/api/tables/:db/:table/row/:id', (0, helpers_1.wrap)(async (req, res) => {
|
|
40
|
+
const db = ctx.getDb(req);
|
|
39
41
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
40
42
|
if (!info)
|
|
41
43
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -49,7 +51,8 @@ function registerRowRoutes(router, ctx) {
|
|
|
49
51
|
return (0, helpers_1.fail)(res, 'Row not found.', 404);
|
|
50
52
|
(0, helpers_1.ok)(res, (0, common_1.normalizeRow)(r.data));
|
|
51
53
|
}));
|
|
52
|
-
router.get('/api/tables/:table/row/:id/blob/:column', (0, helpers_1.wrap)(async (req, res) => {
|
|
54
|
+
router.get('/api/tables/:db/:table/row/:id/blob/:column', (0, helpers_1.wrap)(async (req, res) => {
|
|
55
|
+
const db = ctx.getDb(req);
|
|
53
56
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
54
57
|
if (!info)
|
|
55
58
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -86,7 +89,8 @@ function registerRowRoutes(router, ctx) {
|
|
|
86
89
|
}
|
|
87
90
|
res.end(buf);
|
|
88
91
|
}));
|
|
89
|
-
router.get('/api/tables/:table/row/:id/blob/:column/meta', (0, helpers_1.wrap)(async (req, res) => {
|
|
92
|
+
router.get('/api/tables/:db/:table/row/:id/blob/:column/meta', (0, helpers_1.wrap)(async (req, res) => {
|
|
93
|
+
const db = ctx.getDb(req);
|
|
90
94
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
91
95
|
if (!info)
|
|
92
96
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -106,7 +110,8 @@ function registerRowRoutes(router, ctx) {
|
|
|
106
110
|
const meta = (0, datatype_1.analyzeBlob)(rawVal);
|
|
107
111
|
(0, helpers_1.ok)(res, { isNull: false, ...meta });
|
|
108
112
|
}));
|
|
109
|
-
router.put('/api/tables/:table/row/:id/blob/:column', (0, helpers_1.wrap)(async (req, res) => {
|
|
113
|
+
router.put('/api/tables/:db/:table/row/:id/blob/:column', (0, helpers_1.wrap)(async (req, res) => {
|
|
114
|
+
const db = ctx.getDb(req);
|
|
110
115
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
111
116
|
if (!info)
|
|
112
117
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -137,7 +142,8 @@ function registerRowRoutes(router, ctx) {
|
|
|
137
142
|
return (0, helpers_1.fail)(res, updateRes.error ?? 'Failed to update BLOB.');
|
|
138
143
|
(0, helpers_1.ok)(res, { message: `Updated BLOB in column "${colName}".`, size: buf.length });
|
|
139
144
|
}));
|
|
140
|
-
router.post('/api/tables/:table/rows/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
145
|
+
router.post('/api/tables/:db/:table/rows/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
146
|
+
const db = ctx.getDb(req);
|
|
141
147
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
142
148
|
if (!info)
|
|
143
149
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -147,6 +153,7 @@ function registerRowRoutes(router, ctx) {
|
|
|
147
153
|
(0, helpers_1.ok)(res, { sql: (0, generator_1.generateInsert)(req.params.table, fields) });
|
|
148
154
|
}));
|
|
149
155
|
const generateUpdateHandler = (0, helpers_1.wrap)(async (req, res) => {
|
|
156
|
+
const db = ctx.getDb(req);
|
|
150
157
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
151
158
|
if (!info)
|
|
152
159
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -162,10 +169,11 @@ function registerRowRoutes(router, ctx) {
|
|
|
162
169
|
return (0, helpers_1.fail)(res, 'No fields to update.');
|
|
163
170
|
(0, helpers_1.ok)(res, { sql: (0, generator_1.generateUpdate)(req.params.table, fields, where) });
|
|
164
171
|
});
|
|
165
|
-
router.put('/api/tables/:table/row/:id/generate', generateUpdateHandler);
|
|
166
|
-
router.post('/api/tables/:table/row/:id/generate', generateUpdateHandler);
|
|
167
|
-
router.put('/api/tables/:table/rows/generate', generateUpdateHandler);
|
|
168
|
-
router.post('/api/tables/:table/rows', (0, helpers_1.wrap)(async (req, res) => {
|
|
172
|
+
router.put('/api/tables/:db/:table/row/:id/generate', generateUpdateHandler);
|
|
173
|
+
router.post('/api/tables/:db/:table/row/:id/generate', generateUpdateHandler);
|
|
174
|
+
router.put('/api/tables/:db/:table/rows/generate', generateUpdateHandler);
|
|
175
|
+
router.post('/api/tables/:db/:table/rows', (0, helpers_1.wrap)(async (req, res) => {
|
|
176
|
+
const db = ctx.getDb(req);
|
|
169
177
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
170
178
|
if (!info)
|
|
171
179
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -191,6 +199,7 @@ function registerRowRoutes(router, ctx) {
|
|
|
191
199
|
(0, helpers_1.ok)(res, { message: `${r.data?.inserted ?? 0} row(s) inserted.`, inserted: r.data?.inserted }, 201);
|
|
192
200
|
}));
|
|
193
201
|
const updateSingleRowHandler = (0, helpers_1.wrap)(async (req, res) => {
|
|
202
|
+
const db = ctx.getDb(req);
|
|
194
203
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
195
204
|
if (!info)
|
|
196
205
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -210,6 +219,7 @@ function registerRowRoutes(router, ctx) {
|
|
|
210
219
|
(0, helpers_1.ok)(res, { message: 'Row updated.' });
|
|
211
220
|
});
|
|
212
221
|
const bulkUpdateHandler = (0, helpers_1.wrap)(async (req, res) => {
|
|
222
|
+
const db = ctx.getDb(req);
|
|
213
223
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
214
224
|
if (!info)
|
|
215
225
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -240,10 +250,11 @@ function registerRowRoutes(router, ctx) {
|
|
|
240
250
|
}
|
|
241
251
|
return updateSingleRowHandler(req, res);
|
|
242
252
|
});
|
|
243
|
-
router.put('/api/tables/:table/row/:id', updateSingleRowHandler);
|
|
244
|
-
router.put('/api/tables/:table/rows', bulkUpdateHandler);
|
|
245
|
-
router.post('/api/tables/:table/rows/bulk-update', bulkUpdateHandler);
|
|
253
|
+
router.put('/api/tables/:db/:table/row/:id', updateSingleRowHandler);
|
|
254
|
+
router.put('/api/tables/:db/:table/rows', bulkUpdateHandler);
|
|
255
|
+
router.post('/api/tables/:db/:table/rows/bulk-update', bulkUpdateHandler);
|
|
246
256
|
const deleteSingleRowHandler = (0, helpers_1.wrap)(async (req, res) => {
|
|
257
|
+
const db = ctx.getDb(req);
|
|
247
258
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
248
259
|
if (!info)
|
|
249
260
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -258,9 +269,10 @@ function registerRowRoutes(router, ctx) {
|
|
|
258
269
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to delete row.');
|
|
259
270
|
(0, helpers_1.ok)(res, { message: 'Row deleted.' });
|
|
260
271
|
});
|
|
261
|
-
router.delete('/api/tables/:table/row/:id', deleteSingleRowHandler);
|
|
262
|
-
router.delete('/api/tables/:table/rows', deleteSingleRowHandler);
|
|
263
|
-
router.get('/api/tables/:table/rows/:id/references', (0, helpers_1.wrap)(async (req, res) => {
|
|
272
|
+
router.delete('/api/tables/:db/:table/row/:id', deleteSingleRowHandler);
|
|
273
|
+
router.delete('/api/tables/:db/:table/rows', deleteSingleRowHandler);
|
|
274
|
+
router.get('/api/tables/:db/:table/rows/:id/references', (0, helpers_1.wrap)(async (req, res) => {
|
|
275
|
+
const db = ctx.getDb(req);
|
|
264
276
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
265
277
|
if (!info)
|
|
266
278
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -299,7 +311,8 @@ function registerRowRoutes(router, ctx) {
|
|
|
299
311
|
}
|
|
300
312
|
(0, helpers_1.ok)(res, { references: results });
|
|
301
313
|
}));
|
|
302
|
-
router.post('/api/tables/:table/rows/bulk-delete', (0, helpers_1.wrap)(async (req, res) => {
|
|
314
|
+
router.post('/api/tables/:db/:table/rows/bulk-delete', (0, helpers_1.wrap)(async (req, res) => {
|
|
315
|
+
const db = ctx.getDb(req);
|
|
303
316
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
304
317
|
if (!info)
|
|
305
318
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -316,7 +329,8 @@ function registerRowRoutes(router, ctx) {
|
|
|
316
329
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to delete rows.');
|
|
317
330
|
(0, helpers_1.ok)(res, { message: `${r.data?.deleted ?? 0} row(s) deleted.`, deleted: r.data?.deleted });
|
|
318
331
|
}));
|
|
319
|
-
router.post('/api/tables/:table/rows/bulk-impact', (0, helpers_1.wrap)(async (req, res) => {
|
|
332
|
+
router.post('/api/tables/:db/:table/rows/bulk-impact', (0, helpers_1.wrap)(async (req, res) => {
|
|
333
|
+
const db = ctx.getDb(req);
|
|
320
334
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
321
335
|
if (!info)
|
|
322
336
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -328,7 +342,8 @@ function registerRowRoutes(router, ctx) {
|
|
|
328
342
|
return (0, helpers_1.ok)(res, { references: [], total: 0 });
|
|
329
343
|
(0, helpers_1.ok)(res, await (0, helpers_1.computeBulkImpact)(db, info, wheres));
|
|
330
344
|
}));
|
|
331
|
-
router.post('/api/tables/:table/rows/bulk-export', (0, helpers_1.wrap)(async (req, res) => {
|
|
345
|
+
router.post('/api/tables/:db/:table/rows/bulk-export', (0, helpers_1.wrap)(async (req, res) => {
|
|
346
|
+
const db = ctx.getDb(req);
|
|
332
347
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
333
348
|
if (!info)
|
|
334
349
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
package/dist/routes/api/seed.js
CHANGED
|
@@ -5,17 +5,20 @@ const generator_1 = require("../../sql/generator");
|
|
|
5
5
|
const index_1 = require("../../data/index");
|
|
6
6
|
const helpers_1 = require("./helpers");
|
|
7
7
|
function registerSeedRoutes(router, ctx) {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
router.post('/api/seed/:db/validate', (0, helpers_1.wrap)(async (req, res) => {
|
|
9
|
+
const db = ctx.getDb(req);
|
|
10
10
|
(0, helpers_1.ok)(res, await (0, index_1.validateGenerationPlan)(db, (0, helpers_1.parseUnifiedGenerationPlan)(req.body)));
|
|
11
11
|
}));
|
|
12
|
-
router.post('/api/seed/preview', (0, helpers_1.wrap)(async (req, res) => {
|
|
12
|
+
router.post('/api/seed/:db/preview', (0, helpers_1.wrap)(async (req, res) => {
|
|
13
|
+
const db = ctx.getDb(req);
|
|
13
14
|
(0, helpers_1.ok)(res, await index_1.SeedEngine.executePlan(db, (0, helpers_1.parseUnifiedGenerationPlan)(req.body), { previewLimit: 50 }));
|
|
14
15
|
}));
|
|
15
|
-
router.post('/api/seed/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
16
|
+
router.post('/api/seed/:db/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
17
|
+
const db = ctx.getDb(req);
|
|
16
18
|
(0, helpers_1.ok)(res, await index_1.SeedEngine.executePlan(db, (0, helpers_1.parseUnifiedGenerationPlan)(req.body)));
|
|
17
19
|
}));
|
|
18
|
-
router.post('/api/seed/execute', (0, helpers_1.wrap)(async (req, res) => {
|
|
20
|
+
router.post('/api/seed/:db/execute', (0, helpers_1.wrap)(async (req, res) => {
|
|
21
|
+
const db = ctx.getDb(req);
|
|
19
22
|
const plan = (0, helpers_1.parseUnifiedGenerationPlan)(req.body);
|
|
20
23
|
const truncate = Boolean(req.body?.truncate || plan.options?.truncateAll);
|
|
21
24
|
const gen = await index_1.SeedEngine.executePlan(db, plan, { truncateAll: truncate });
|
|
@@ -29,14 +32,16 @@ function registerSeedRoutes(router, ctx) {
|
|
|
29
32
|
}, 201);
|
|
30
33
|
}));
|
|
31
34
|
const getSeedConfigHandler = (0, helpers_1.wrap)(async (req, res) => {
|
|
35
|
+
const db = ctx.getDb(req);
|
|
32
36
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
33
37
|
if (!info)
|
|
34
38
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
35
39
|
(0, helpers_1.ok)(res, { table: req.params.table, columns: (0, index_1.buildColumnConfigs)(info), maxRows: index_1.MAX_SEED_ROWS });
|
|
36
40
|
});
|
|
37
|
-
router.get('/api/tables/:table/seed/config', getSeedConfigHandler);
|
|
38
|
-
router.get('/api/tables/:table/seed/plan', getSeedConfigHandler);
|
|
39
|
-
router.post('/api/tables/:table/seed/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
41
|
+
router.get('/api/tables/:db/:table/seed/config', getSeedConfigHandler);
|
|
42
|
+
router.get('/api/tables/:db/:table/seed/plan', getSeedConfigHandler);
|
|
43
|
+
router.post('/api/tables/:db/:table/seed/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
44
|
+
const db = ctx.getDb(req);
|
|
40
45
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
41
46
|
if (!info)
|
|
42
47
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -51,7 +56,8 @@ function registerSeedRoutes(router, ctx) {
|
|
|
51
56
|
warnings: gen.warnings,
|
|
52
57
|
});
|
|
53
58
|
}));
|
|
54
|
-
router.post('/api/tables/:table/seed/preview', (0, helpers_1.wrap)(async (req, res) => {
|
|
59
|
+
router.post('/api/tables/:db/:table/seed/preview', (0, helpers_1.wrap)(async (req, res) => {
|
|
60
|
+
const db = ctx.getDb(req);
|
|
55
61
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
56
62
|
if (!info)
|
|
57
63
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -64,7 +70,8 @@ function registerSeedRoutes(router, ctx) {
|
|
|
64
70
|
warnings: gen.warnings,
|
|
65
71
|
});
|
|
66
72
|
}));
|
|
67
|
-
router.post('/api/tables/:table/seed', (0, helpers_1.wrap)(async (req, res) => {
|
|
73
|
+
router.post('/api/tables/:db/:table/seed', (0, helpers_1.wrap)(async (req, res) => {
|
|
74
|
+
const db = ctx.getDb(req);
|
|
68
75
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
69
76
|
if (!info)
|
|
70
77
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -90,7 +97,8 @@ function registerSeedRoutes(router, ctx) {
|
|
|
90
97
|
elapsedMs,
|
|
91
98
|
}, 201);
|
|
92
99
|
}));
|
|
93
|
-
router.get('/api/tables/:table/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
|
|
100
|
+
router.get('/api/tables/:db/:table/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
|
|
101
|
+
const db = ctx.getDb(req);
|
|
94
102
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
95
103
|
if (!info)
|
|
96
104
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -100,7 +108,8 @@ function registerSeedRoutes(router, ctx) {
|
|
|
100
108
|
: 'chain';
|
|
101
109
|
(0, helpers_1.ok)(res, await (0, index_1.getErChainConfig)(db, req.params.table, scope));
|
|
102
110
|
}));
|
|
103
|
-
router.post('/api/tables/:table/seed/chain/preview', (0, helpers_1.wrap)(async (req, res) => {
|
|
111
|
+
router.post('/api/tables/:db/:table/seed/chain/preview', (0, helpers_1.wrap)(async (req, res) => {
|
|
112
|
+
const db = ctx.getDb(req);
|
|
104
113
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
105
114
|
if (!info)
|
|
106
115
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -112,7 +121,8 @@ function registerSeedRoutes(router, ctx) {
|
|
|
112
121
|
}
|
|
113
122
|
(0, helpers_1.ok)(res, await (0, index_1.generateChainRows)(db, config, plans, previewCounts, false, modes, seed));
|
|
114
123
|
}));
|
|
115
|
-
router.post('/api/tables/:table/seed/chain/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
124
|
+
router.post('/api/tables/:db/:table/seed/chain/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
125
|
+
const db = ctx.getDb(req);
|
|
116
126
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
117
127
|
if (!info)
|
|
118
128
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -120,7 +130,8 @@ function registerSeedRoutes(router, ctx) {
|
|
|
120
130
|
const config = await (0, index_1.getErChainConfig)(db, req.params.table, scope);
|
|
121
131
|
(0, helpers_1.ok)(res, await (0, index_1.generateChainRows)(db, config, plans, counts, false, modes, seed));
|
|
122
132
|
}));
|
|
123
|
-
router.post('/api/tables/:table/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
|
|
133
|
+
router.post('/api/tables/:db/:table/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
|
|
134
|
+
const db = ctx.getDb(req);
|
|
124
135
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
125
136
|
if (!info)
|
|
126
137
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -136,7 +147,8 @@ function registerSeedRoutes(router, ctx) {
|
|
|
136
147
|
warnings: exec.warnings,
|
|
137
148
|
}, 201);
|
|
138
149
|
}));
|
|
139
|
-
router.get('/api/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
|
|
150
|
+
router.get('/api/seed/:db/chain', (0, helpers_1.wrap)(async (req, res) => {
|
|
151
|
+
const db = ctx.getDb(req);
|
|
140
152
|
const rawRoot = String(req.query.table ?? '');
|
|
141
153
|
const rawScope = String(req.query.scope ?? (rawRoot ? 'chain' : 'all'));
|
|
142
154
|
const scope = rawScope === 'ancestors' || rawScope === 'descendants' || rawScope === 'all' || rawScope === 'single'
|
|
@@ -147,7 +159,8 @@ function registerSeedRoutes(router, ctx) {
|
|
|
147
159
|
return (0, helpers_1.fail)(res, 'No tables found in database.', 404);
|
|
148
160
|
(0, helpers_1.ok)(res, await (0, index_1.getErChainConfig)(db, rootTable, scope));
|
|
149
161
|
}));
|
|
150
|
-
router.post('/api/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
|
|
162
|
+
router.post('/api/seed/:db/chain', (0, helpers_1.wrap)(async (req, res) => {
|
|
163
|
+
const db = ctx.getDb(req);
|
|
151
164
|
const { scope, counts, plans, truncate, modes, seed } = (0, helpers_1.parseChainSeedRequest)(req.body);
|
|
152
165
|
const rawRoot = String(req.body?.rootTable ?? '');
|
|
153
166
|
const rootTable = rawRoot || (await db.listTables()).data?.[0]?.name || '';
|