@promakeai/cli 0.4.1 → 0.4.2
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/dist/index.js +1 -1
- package/dist/registry/blog-core.json +3 -1
- package/dist/registry/db.json +6 -6
- package/dist/registry/docs/blog-core.md +5 -0
- package/dist/registry/docs/ecommerce-core.md +5 -0
- package/dist/registry/ecommerce-core.json +3 -1
- package/package.json +1 -1
- package/template/src/lib/api.ts +2 -3
package/dist/index.js
CHANGED
|
@@ -494,7 +494,7 @@ constants.json (full)`)),console.log(C.dim(JSON.stringify(B.constantsConfig,null
|
|
|
494
494
|
`)),D6("promake create <name>","Create new project"),D6("promake add <modules...>","Add modules"),D6("promake add <page> --module-name <name>","Add page with custom name"),D6("promake remove <modules...>","Remove modules"),D6("promake sync","Sync from promake.json"),D6("promake theme --list","List theme options"),D6("promake theme --preset <name>","Apply color preset"),D6("promake list","List available modules"),D6("promake doctor","Check project health"),D6("promake doctor --runtime","Include runtime checks"),D6("promake discover --json","Project info for AI"),D6("promake seo","Sync SEO meta tags"),D6("promake usage","Show this guide"),D6("promake <command> --help","Command-specific help"),console.log()}var fr=new $5("info").description("Show detailed information about a module").argument("<module>","Module name").option("--json","Output as JSON").action(async(D,X)=>{let Z=E2(`Fetching ${D}...`).start();try{let J=await T4(D);if(Z.stop(),!J)console.error(C.red(`Module not found: ${D}`)),process.exit(1);if(X.json){console.log(JSON.stringify(J,null,2));return}if(console.log(""),console.log(C.bold.cyan(J.name)),console.log(C.dim("─".repeat(J.name.length+4))),J.title)console.log(`${C.gray("Title:")} ${J.title}`);if(console.log(`${C.gray("Type:")} ${J.type.replace("registry:","")}`),J.description)console.log(`${C.gray("Description:")} ${J.description}`);if(J.registryDependencies?.length)console.log(""),console.log(C.gray("Dependencies:")),J.registryDependencies.forEach((Y)=>{let $=!Y.includes("-")||["button","card","input","badge","dialog","sheet","dropdown-menu","accordion","tabs","select","checkbox","slider","separator","avatar","tooltip","popover","navigation-menu","scroll-area"].includes(Y)?C.dim("(shadcn)"):"";console.log(` ${C.yellow("•")} ${Y} ${$}`)});if(J.dependencies?.length)console.log(""),console.log(C.gray("NPM Packages:")),J.dependencies.forEach((Y)=>{console.log(` ${C.blue("•")} ${Y}`)});if(J.files?.length)console.log(""),console.log(C.gray("Files:")),J.files.forEach((Y)=>{console.log(` ${C.dim("•")} ${Y.path}`)});if(J.exports){if(console.log(""),console.log(C.gray("Exports:")),J.exports.types?.length)console.log(` ${C.magenta("Types:")} ${J.exports.types.join(", ")}`);if(J.exports.variables?.length)console.log(` ${C.green("Components:")} ${J.exports.variables.join(", ")}`)}if(J.route)console.log(""),console.log(C.gray("Route:")),console.log(` ${C.cyan("Path:")} ${J.route.path}`),console.log(` ${C.cyan("Component:")} ${J.route.componentName}`);if(J.usage)console.log(""),console.log(C.gray("Usage:")),console.log(C.dim("─".repeat(40))),console.log(J.usage);console.log("")}catch(J){if(Z.fail("Failed to fetch module info"),J instanceof Error)console.error(C.red(J.message));process.exit(1)}});import s91 from"path";var vK=k1(r8(),1);import{execSync as wx0}from"child_process";import xq from"path";async function rE(D,X=!1){let Z=Date.now(),J=[],Y=xq.join(D,"tsconfig.json");if(!await vK.default.pathExists(Y))return{name:"TypeScript",status:"skip",severity:"info",duration:Date.now()-Z,items:[],summary:"No tsconfig.json found"};let Q=xq.join(D,"package.json"),$=!1;if(await vK.default.pathExists(Q))$=!!(await vK.default.readJson(Q)).scripts?.check;let K=X$(D),G=K==="npm"?"npm run":`${K} run`;try{let F=$?`${G} check 2>&1`:"npx tsc --noEmit 2>&1";if(!X)console.log(C.dim(` $ ${$?`${G} check`:"tsc --noEmit"}`));let W=wx0(F,{cwd:D,encoding:"utf-8",stdio:"pipe"});if(!X&&W.trim())console.log(C.dim(W));return{name:"TypeScript",status:"pass",severity:"error",duration:Date.now()-Z,items:[],summary:"No type errors"}}catch(F){let W=F.stdout||F.stderr||F.message||"";if(W.includes("This is not the tsc command")||W.includes("typescript"))return{name:"TypeScript",status:"skip",severity:"info",duration:Date.now()-Z,items:[],summary:"TypeScript not installed"};if(!X&&W.trim())console.log(C.dim(W));return J.push(...Sx0(W,D)),{name:"TypeScript",status:"fail",severity:"error",duration:Date.now()-Z,items:J,summary:`${J.length} type error(s)`}}}function Sx0(D,X){let Z=[],J=/^(.+)\((\d+),(\d+)\):\s*(error|warning)\s*(TS\d+):\s*(.+)$/gm,Y=/^(.+):(\d+):(\d+)\s*-\s*(error|warning)\s*(TS\d+):\s*(.+)$/gm;for(let $ of[J,Y]){let K;while((K=$.exec(D))!==null){let G=K[1].trim();Z.push({file:xq.isAbsolute(G)?xq.relative(X,G):G,line:parseInt(K[2]),column:parseInt(K[3]),code:K[5],message:K[6]})}}let Q=new Set;return Z.filter(($)=>{let K=`${$.file}:${$.line}:${$.column}:${$.message}`;if(Q.has(K))return!1;return Q.add(K),!0})}var oD=k1(r8(),1);import{execSync as _x0}from"child_process";import bK from"path";var kx0=["eslint.config.js","eslint.config.mjs","eslint.config.cjs",".eslintrc.js",".eslintrc.cjs",".eslintrc.json",".eslintrc.yml",".eslintrc.yaml",".eslintrc"];async function hx0(D){for(let Z of kx0)if(await oD.default.pathExists(bK.join(D,Z)))return!0;let X=bK.join(D,"package.json");if(await oD.default.pathExists(X))try{if((await oD.default.readJson(X)).eslintConfig)return!0}catch{}return!1}async function sE(D,X=!1,Z=!1){let J=Date.now();if(!await hx0(D))return{name:"ESLint",status:"skip",severity:"warning",duration:Date.now()-J,items:[],summary:"No ESLint config found"};let Y=bK.join(D,"package.json"),Q=!1;if(await oD.default.pathExists(Y))Q=!!(await oD.default.readJson(Y)).scripts?.lint;let $=X$(D),K=$==="npm"?"npm run":`${$} run`,G=X?" -- --fix":"";try{let F=Q?`${K} lint${G} 2>&1`:`npx eslint .${X?" --fix":""} 2>&1`;if(!Z)console.log(C.dim(` $ ${Q?`${K} lint${G}`:`eslint .${X?" --fix":""}`}`));let W=_x0(F,{cwd:D,encoding:"utf-8",stdio:"pipe",maxBuffer:10485760});if(!Z&&W.trim())console.log(C.dim(W));return{name:"ESLint",status:"pass",severity:"warning",duration:Date.now()-J,items:[],summary:"No linting issues"}}catch(F){let W=F.stdout||F.stderr||F.message||"";if(W.includes("eslint: not found")||W.includes("'eslint' is not recognized"))return{name:"ESLint",status:"skip",severity:"info",duration:Date.now()-J,items:[],summary:"ESLint not installed"};if(!Z&&W.trim())console.log(C.dim(W));let V=xx0(W,D);if(V.length===0&&W.includes("error"))return{name:"ESLint",status:"fail",severity:"warning",duration:Date.now()-J,items:[{message:"ESLint encountered an error"}],summary:"ESLint error"};return{name:"ESLint",status:V.length>0?"fail":"pass",severity:"warning",duration:Date.now()-J,items:V,summary:V.length>0?`${V.length} linting issue(s)`:"No linting issues"}}}function xx0(D,X){let Z=[],J=/^([^\s].*\.(ts|tsx|js|jsx))$/gm,Y=/^\s+(\d+):(\d+)\s+(error|warning)\s+(.+?)\s{2,}(\S+)?$/gm,Q=null,$=D.split(`
|
|
495
495
|
`);for(let K of $){let G=K.match(/^([^\s].*\.(ts|tsx|js|jsx))$/);if(G){Q=G[1];continue}let F=K.match(/^\s+(\d+):(\d+)\s+(error|warning)\s+(.+?)\s{2,}(\S+)?$/);if(F&&Q)Z.push({file:bK.isAbsolute(Q)?bK.relative(X,Q):Q,line:parseInt(F[1]),column:parseInt(F[2]),code:F[5]||void 0,message:F[4].trim()})}return Z}var yq=k1(r8(),1);import{execSync as yx0}from"child_process";import fK from"path";async function aE(D){let X=Date.now(),Z=["vite.config.ts","vite.config.js","vite.config.mjs"],J=!1;for(let Y of Z)if(await yq.default.pathExists(fK.join(D,Y))){J=!0;break}if(!J)return{name:"Build",status:"skip",severity:"error",duration:Date.now()-X,items:[],summary:"No vite.config found"};try{yx0("npx vite build 2>&1",{cwd:D,encoding:"utf-8",stdio:"pipe",env:{...process.env,NODE_ENV:"production"}});let Y=fK.join(D,"dist");if(await yq.default.pathExists(Y))await yq.default.remove(Y);return{name:"Build",status:"pass",severity:"error",duration:Date.now()-X,items:[],summary:"Build succeeded"}}catch(Y){let Q=Y.stdout||Y.stderr||Y.message||"",$=vx0(Q,D);return{name:"Build",status:"fail",severity:"error",duration:Date.now()-X,items:$.length>0?$:[{message:"Build failed"}],summary:"Build failed"}}}function vx0(D,X){let Z=[],J=D.split(`
|
|
496
496
|
`),Y=[/^(.+):(\d+):(\d+):\s*(.+)$/,/\[vite\]:\s*Error:\s*(.+)/,/Error:\s*(.+?)\s+at\s+(.+):(\d+):(\d+)/,/Could not resolve ["']([^"']+)["']\s+from\s+["']([^"']+)["']/,/RollupError:\s*(.+)/];for(let $=0;$<J.length;$++){let K=J[$].trim();if(!K)continue;if(K.includes("building for production")||K.includes("transforming")||K.includes("rendering chunks")||K.includes("computing gzip")||K.startsWith("✓")||K.startsWith("vite v"))continue;for(let G of Y){let F=K.match(G);if(F){if(G.source.includes("Could not resolve"))Z.push({file:fK.relative(X,F[2]),message:`Cannot find module '${F[1]}'`,suggestion:"Check if the module is installed or the path is correct"});else if(F.length===5)Z.push({file:fK.isAbsolute(F[1]||F[2])?fK.relative(X,F[1]||F[2]):F[1]||F[2],line:parseInt(F[2]||F[3]),column:parseInt(F[3]||F[4]),message:F[4]||F[1]});else Z.push({message:F[1]||K});break}}if(Z.length===0&&(K.toLowerCase().includes("error")||K.toLowerCase().includes("failed")))Z.push({message:K})}let Q=new Set;return Z.filter(($)=>{let K=`${$.file}:${$.line}:${$.message}`;if(Q.has(K))return!1;return Q.add(K),!0})}var lS=k1(r8(),1);import DF from"path";var v91=[process.env["PROGRAMFILES(X86)"]&&DF.join(process.env["PROGRAMFILES(X86)"],"Google","Chrome","Application","chrome.exe"),process.env.PROGRAMFILES&&DF.join(process.env.PROGRAMFILES,"Google","Chrome","Application","chrome.exe"),process.env.LOCALAPPDATA&&DF.join(process.env.LOCALAPPDATA,"Google","Chrome","Application","chrome.exe"),process.env["PROGRAMFILES(X86)"]&&DF.join(process.env["PROGRAMFILES(X86)"],"Microsoft","Edge","Application","msedge.exe"),process.env.PROGRAMFILES&&DF.join(process.env.PROGRAMFILES,"Microsoft","Edge","Application","msedge.exe")].filter(Boolean),b91=["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge","/Applications/Chromium.app/Contents/MacOS/Chromium"],f91=["/usr/bin/google-chrome","/usr/bin/google-chrome-stable","/usr/bin/chromium-browser","/usr/bin/chromium","/snap/bin/chromium"];async function v$0(){let D=await g91();if(!D)return null;try{return{type:"chrome",browser:await(await Promise.resolve().then(() => (y$0(),x$0))).default.launch({executablePath:D,headless:!0,args:["--no-sandbox","--disable-setuid-sandbox","--disable-gpu"]})}}catch{return null}}async function g91(){if(process.env.CHROME_PATH){if(await lS.default.pathExists(process.env.CHROME_PATH))return process.env.CHROME_PATH}let D;switch(process.platform){case"win32":D=v91;break;case"darwin":D=b91;break;default:D=f91}for(let X of D)if(await lS.default.pathExists(X))return X;return null}async function b$0(D){try{await D.browser.close()}catch{}}async function pS(D,X,Z="/"){let J=Date.now();if(!await u91(D))return{name:"Runtime",status:"skip",severity:"error",duration:Date.now()-J,items:[{message:`No server running on port ${D}`,suggestion:"Start dev server first: npm run dev (or use --port to specify different port)"}],summary:`Skipped (no server on port ${D})`};let Q=null;try{Q=await v$0()}catch{}if(!Q)return{name:"Runtime",status:"skip",severity:"info",duration:Date.now()-J,items:[{message:"No browser available (Chrome/Chromium/Edge not found)",suggestion:"Set CHROME_PATH environment variable or install Chrome/Edge"}],summary:"Skipped (no browser)"};let $=[],{browser:K,type:G}=Q;try{let W=await K.newPage();W.on("console",(H)=>{let q=H.type();if(q==="error"||q==="warning"){let U=H.location();$.push({type:q==="warning"?"warning":"console",message:H.text(),fileName:U?.url,lineNumber:U?.lineNumber,columnNumber:U?.columnNumber})}}),W.on("pageerror",(H)=>{$.push({type:"error",message:H.message,stack:H.stack})});let V=Z.startsWith("/")?Z:`/${Z}`;await W.goto(`http://localhost:${D}${V}`,{waitUntil:"networkidle0",timeout:X}),await m91(3000);let z=await W.evaluate(()=>{return window.__earlyErrors||[]});$.push(...z)}catch(W){let V=W.message||"Unknown error";if(V.includes("timeout")||V.includes("Timeout"))$.push({type:"error",message:"Page load timeout - app may have crashed or is unresponsive"});else if(V.includes("net::ERR"))$.push({type:"error",message:`Network error: ${V}`});else $.push({type:"error",message:`Runtime check error: ${V}`})}finally{await b$0(Q)}let F=c91($);return{name:`Runtime (${G})`,status:F.length>0?"fail":"pass",severity:"error",duration:Date.now()-J,items:F,summary:F.length>0?`${F.length} runtime error(s)`:"No runtime errors"}}async function u91(D){try{let X=await fetch(`http://localhost:${D}`,{method:"HEAD",signal:AbortSignal.timeout(3000)});return X.ok||X.status<500}catch{return!1}}function m91(D){return new Promise((X)=>setTimeout(X,D))}function c91(D){return D.map((X)=>{let Z=[];for(let[J,Y]of Object.entries(X))if(Y!==void 0&&Y!==null)Z.push(`${J}: ${Y}`);return{message:Z.join(`
|
|
497
|
-
`)}})}function iS(D,X){console.log(""),console.log(C.bold("Health Check")+C.dim(` (${r91(D.duration)})`)),console.log(C.dim("─".repeat(40)));for(let Z of D.checks)d91(Z,X);console.log(C.dim("─".repeat(40))),o91(D)}function d91(D,X){let Z=i91(D.status),J=n91(D.status,D.severity),Y=C.dim(`${D.duration}ms`);console.log(`${Z} ${J(D.name)} ${Y} ${C.dim("·")} ${C.dim(D.summary||"")}`);let Q=D.name.startsWith("Runtime"),$=X||Q?D.items:D.items.slice(0,3),K=D.items.length-$.length;for(let G of $)l91(G,D.severity);if(K>0)console.log(C.dim(` ... +${K} more (--verbose)`))}function l91(D,X){let Z=X==="error"?C.red:C.yellow,J=p91(D),Y=J?C.cyan(J)+" ":"";console.log(` ${Z("›")} ${Y}${D.message}`)}function p91(D){if(!D.file)return"";let X=D.file;if(D.line!==void 0){if(X+=`:${D.line}`,D.column!==void 0)X+=`:${D.column}`}return X}function i91(D){switch(D){case"pass":return C.green("✓");case"fail":return C.red("✗");case"skip":return C.dim("○");default:return C.dim("?")}}function n91(D,X){if(D==="fail")return X==="error"?C.red:C.yellow;if(D==="pass")return C.green;return C.dim}function o91(D){let{summary:X}=D,Z=[];if(X.passed>0)Z.push(C.green(`${X.passed} passed`));if(X.failed>0)Z.push(C.red(`${X.failed} failed`));if(X.skipped>0)Z.push(C.dim(`${X.skipped} skipped`));if(console.log(Z.join(C.dim(" · "))),X.errors>0)console.log(C.red(`${X.errors} error(s) need attention`));else if(X.warnings>0)console.log(C.yellow(`${X.warnings} warning(s) to review`));else console.log(C.green("All good!"))}function r91(D){if(D<1000)return`${D}ms`;return`${(D/1000).toFixed(1)}s`}var f$0=new $5("doctor").description("Analyze project for issues (TypeScript, ESLint, build, runtime)").option("--cwd <path>","Working directory").option("--port <number>","Vite dev server port","5174").option("--fix","Auto-fix ESLint issues where possible").option("--runtime","Run runtime error detection (requires running dev server)").option("--runtime-timeout <ms>","Runtime check timeout in ms","10000").option("--route <path>","Route path for runtime check","/").option("--json","Output as JSON (for CI/CD)").option("-v, --verbose","Show all details including all errors").option("-q, --quiet","Hide command output logs (only show summary)").option("--no-typecheck","Skip TypeScript type checking").option("--no-lint","Skip ESLint checking").option("--no-build","Skip Vite build checking").action(async(D)=>{let X=D.cwd||process.cwd(),Z=parseInt(D.port||"5173"),J=parseInt(D.runtimeTimeout||"10000"),Y=D.route||"/",Q=Date.now(),$=D.quiet||D.json||!1;if(!await T2(X)&&!D.json)console.log(C.yellow("Warning: No promake.json found. Running checks anyway..."));let G=D.json?null:E2("Running health checks...").start(),F=[];if(D.typecheck!==!1)F.push(()=>rE(X,$));if(D.lint!==!1)F.push(()=>sE(X,D.fix,$));if(D.build!==!1)F.push(()=>aE(X));if(D.runtime)F.push(()=>pS(Z,J,Y));let W=[];try{if(W=await a91(F,1),G)G.stop()}catch(z){if(G)G.fail("Health check failed");if(!D.json)console.error(C.red(z.message));process.exit(1)}let V={project:s91.basename(X),timestamp:new Date().toISOString(),duration:Date.now()-Q,checks:W,summary:{passed:W.filter((z)=>z.status==="pass").length,failed:W.filter((z)=>z.status==="fail").length,skipped:W.filter((z)=>z.status==="skip").length,errors:W.filter((z)=>z.status==="fail"&&z.severity==="error").reduce((z,H)=>z+H.items.length,0),warnings:W.filter((z)=>z.status==="fail"&&z.severity==="warning").reduce((z,H)=>z+H.items.length,0)}};if(D.json)console.log(JSON.stringify(V,null,2));else iS(V,D.verbose||!1);if(V.summary.errors>0)process.exit(1)});async function a91(D,X){let Z=[],J=0;async function Y(){let $=J++;if($>=D.length)return;Z[$]=await D[$](),await Y()}let Q=Array(Math.min(X,D.length)).fill(null).map(()=>Y());return await Promise.all(Q),Z}var g$0={name:"@promakeai/cli",version:"0.4.
|
|
497
|
+
`)}})}function iS(D,X){console.log(""),console.log(C.bold("Health Check")+C.dim(` (${r91(D.duration)})`)),console.log(C.dim("─".repeat(40)));for(let Z of D.checks)d91(Z,X);console.log(C.dim("─".repeat(40))),o91(D)}function d91(D,X){let Z=i91(D.status),J=n91(D.status,D.severity),Y=C.dim(`${D.duration}ms`);console.log(`${Z} ${J(D.name)} ${Y} ${C.dim("·")} ${C.dim(D.summary||"")}`);let Q=D.name.startsWith("Runtime"),$=X||Q?D.items:D.items.slice(0,3),K=D.items.length-$.length;for(let G of $)l91(G,D.severity);if(K>0)console.log(C.dim(` ... +${K} more (--verbose)`))}function l91(D,X){let Z=X==="error"?C.red:C.yellow,J=p91(D),Y=J?C.cyan(J)+" ":"";console.log(` ${Z("›")} ${Y}${D.message}`)}function p91(D){if(!D.file)return"";let X=D.file;if(D.line!==void 0){if(X+=`:${D.line}`,D.column!==void 0)X+=`:${D.column}`}return X}function i91(D){switch(D){case"pass":return C.green("✓");case"fail":return C.red("✗");case"skip":return C.dim("○");default:return C.dim("?")}}function n91(D,X){if(D==="fail")return X==="error"?C.red:C.yellow;if(D==="pass")return C.green;return C.dim}function o91(D){let{summary:X}=D,Z=[];if(X.passed>0)Z.push(C.green(`${X.passed} passed`));if(X.failed>0)Z.push(C.red(`${X.failed} failed`));if(X.skipped>0)Z.push(C.dim(`${X.skipped} skipped`));if(console.log(Z.join(C.dim(" · "))),X.errors>0)console.log(C.red(`${X.errors} error(s) need attention`));else if(X.warnings>0)console.log(C.yellow(`${X.warnings} warning(s) to review`));else console.log(C.green("All good!"))}function r91(D){if(D<1000)return`${D}ms`;return`${(D/1000).toFixed(1)}s`}var f$0=new $5("doctor").description("Analyze project for issues (TypeScript, ESLint, build, runtime)").option("--cwd <path>","Working directory").option("--port <number>","Vite dev server port","5174").option("--fix","Auto-fix ESLint issues where possible").option("--runtime","Run runtime error detection (requires running dev server)").option("--runtime-timeout <ms>","Runtime check timeout in ms","10000").option("--route <path>","Route path for runtime check","/").option("--json","Output as JSON (for CI/CD)").option("-v, --verbose","Show all details including all errors").option("-q, --quiet","Hide command output logs (only show summary)").option("--no-typecheck","Skip TypeScript type checking").option("--no-lint","Skip ESLint checking").option("--no-build","Skip Vite build checking").action(async(D)=>{let X=D.cwd||process.cwd(),Z=parseInt(D.port||"5173"),J=parseInt(D.runtimeTimeout||"10000"),Y=D.route||"/",Q=Date.now(),$=D.quiet||D.json||!1;if(!await T2(X)&&!D.json)console.log(C.yellow("Warning: No promake.json found. Running checks anyway..."));let G=D.json?null:E2("Running health checks...").start(),F=[];if(D.typecheck!==!1)F.push(()=>rE(X,$));if(D.lint!==!1)F.push(()=>sE(X,D.fix,$));if(D.build!==!1)F.push(()=>aE(X));if(D.runtime)F.push(()=>pS(Z,J,Y));let W=[];try{if(W=await a91(F,1),G)G.stop()}catch(z){if(G)G.fail("Health check failed");if(!D.json)console.error(C.red(z.message));process.exit(1)}let V={project:s91.basename(X),timestamp:new Date().toISOString(),duration:Date.now()-Q,checks:W,summary:{passed:W.filter((z)=>z.status==="pass").length,failed:W.filter((z)=>z.status==="fail").length,skipped:W.filter((z)=>z.status==="skip").length,errors:W.filter((z)=>z.status==="fail"&&z.severity==="error").reduce((z,H)=>z+H.items.length,0),warnings:W.filter((z)=>z.status==="fail"&&z.severity==="warning").reduce((z,H)=>z+H.items.length,0)}};if(D.json)console.log(JSON.stringify(V,null,2));else iS(V,D.verbose||!1);if(V.summary.errors>0)process.exit(1)});async function a91(D,X){let Z=[],J=0;async function Y(){let $=J++;if($>=D.length)return;Z[$]=await D[$](),await Y()}let Q=Array(Math.min(X,D.length)).fill(null).map(()=>Y());return await Promise.all(Q),Z}var g$0={name:"@promakeai/cli",version:"0.4.2",type:"module",bin:{promake:"dist/index.js"},files:["dist/index.js","dist/registry","template"],scripts:{dev:"bun run src/index.ts","dev:app":"cd dev && bun run dev","dev:populate":"bun run scripts/populate-dev.ts","dev:fresh":"bun run dev:populate && bun run dev:app","playground:create":"rm -rf playground && bun run dev -- create playground --template empty --pm bun","playground:reset":"bun run playground:create","playground:add":"cd playground && bun run ../src/index.ts add","playground:ecommerce":"rm -rf playground && bun run dev -- create playground --template ecommerce --pm bun",build:"bun run build:cli && bun run build:registry","build:cli":"bun build src/index.ts --outdir dist --target node --minify","build:registry":"bun run scripts/build-registry.ts",typecheck:"tsc --noEmit",prepublishOnly:"bun run build",test:"bun test","test:watch":"bun test --watch","test:coverage":"bun test --coverage",release:"bun run build && npm publish --access public"},dependencies:{"adm-zip":"^0.5.16",archiver:"^7.0.1",chalk:"^5.3.0",commander:"^12.1.0",culori:"^4.0.2",dotenv:"^17.2.3","fs-extra":"^11.3.3",glob:"^11.0.0",ora:"^8.1.1",prompts:"^2.4.2","puppeteer-core":"^24.36.0"},devDependencies:{"@types/archiver":"^7.0.0","@types/bun":"^1.1.14","@types/culori":"^4.0.1","@types/fs-extra":"^11.0.4","@types/node":"^22.10.2","@types/prompts":"^2.4.9",typescript:"^5.7.2"}};var P8=new $5;P8.name("promake").description(`Modular React template CLI - Build React apps with pre-built components
|
|
498
498
|
|
|
499
499
|
Quick Start:
|
|
500
500
|
$ promake create my-app
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
"dependencies": [
|
|
7
7
|
"zustand"
|
|
8
8
|
],
|
|
9
|
-
"registryDependencies": [
|
|
9
|
+
"registryDependencies": [
|
|
10
|
+
"db"
|
|
11
|
+
],
|
|
10
12
|
"usage": "import { useBlog, useDbPosts, useDbPostBySlug } from '@/modules/blog-core';\n\n// No provider needed - just use the hooks:\nconst { favorites, addToFavorites, isFavorite } = useBlog();\nconst { posts, loading } = useDbPosts();\n\n// Or use store directly with selectors:\nconst favorites = useBlogStore((s) => s.favorites);",
|
|
11
13
|
"files": [
|
|
12
14
|
{
|
package/dist/registry/db.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"path": "db/index.ts",
|
|
14
14
|
"type": "registry:index",
|
|
15
15
|
"target": "$modules$/db/index.ts",
|
|
16
|
-
"content": "/**\n * DB Module\n * Universal data management with adapter pattern + React Query\n */\n\n// Core\nexport { DataManager } from \"./core/DataManager\";\nexport type {\n QueryOptions,\n OrderBy,\n PaginatedResult,\n // Complex query types\n WhereOperator,\n WhereCondition,\n WhereGroup,\n JoinType,\n JoinClause,\n} from \"./core/types\";\n\n// Adapters\nexport type { IDataAdapter } from \"./adapters/IDataAdapter\";\nexport { SqliteAdapter } from \"./adapters/SqliteAdapter\";\n\n// Config\nexport { getAdapter, createAdapter, resetAdapter } from \"./config\";\nexport type { DataConfig, AdapterType } from \"./config\";\n\n// React exports (re-export from react/index.ts)\nexport * from \"./react\";\n\n// Re-export for convenience\nexport { DBQueryProvider as QueryProvider } from \"./react\";\n\n// Utility exports (parsers for client-side data transformation)\nexport {\n parseCommaSeparatedString,\n parseJSONStringToArray,\n parseStringToArray,\n parseJSONString,\n parseSQLiteBoolean,\n parseNumberSafe,\n} from \"./utils/parsers\";\n"
|
|
16
|
+
"content": "/**\r\n * DB Module\r\n * Universal data management with adapter pattern + React Query\r\n */\r\n\r\n// Core\r\nexport { DataManager } from \"./core/DataManager\";\r\nexport type {\r\n QueryOptions,\r\n OrderBy,\r\n PaginatedResult,\r\n // Complex query types\r\n WhereOperator,\r\n WhereCondition,\r\n WhereGroup,\r\n JoinType,\r\n JoinClause,\r\n} from \"./core/types\";\r\n\r\n// Adapters\r\nexport type { IDataAdapter } from \"./adapters/IDataAdapter\";\r\nexport { SqliteAdapter } from \"./adapters/SqliteAdapter\";\r\n\r\n// Config\r\nexport { getAdapter, createAdapter, resetAdapter } from \"./config\";\r\nexport type { DataConfig, AdapterType } from \"./config\";\r\n\r\n// React exports (re-export from react/index.ts)\r\nexport * from \"./react\";\r\n\r\n// Re-export for convenience\r\nexport { DBQueryProvider as QueryProvider } from \"./react\";\r\n\r\n// Utility exports (parsers for client-side data transformation)\r\nexport {\r\n parseCommaSeparatedString,\r\n parseJSONStringToArray,\r\n parseStringToArray,\r\n parseJSONString,\r\n parseSQLiteBoolean,\r\n parseNumberSafe,\r\n} from \"./utils/parsers\";\r\n"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "db/config.ts",
|
|
@@ -25,19 +25,19 @@
|
|
|
25
25
|
"path": "db/core/DataManager.ts",
|
|
26
26
|
"type": "registry:lib",
|
|
27
27
|
"target": "$modules$/db/core/DataManager.ts",
|
|
28
|
-
"content": "import type { IDataAdapter } from \"../adapters/IDataAdapter\";\nimport type { QueryOptions, PaginatedResult } from \"./types\";\n\n/**\n * DataManager - Simple proxy to adapter\n */\nexport class DataManager {\n private static instance: DataManager;\n private adapter: IDataAdapter;\n\n private constructor(adapter: IDataAdapter) {\n this.adapter = adapter;\n }\n\n static getInstance(adapter: IDataAdapter): DataManager {\n if (!DataManager.instance) {\n DataManager.instance = new DataManager(adapter);\n }\n return DataManager.instance;\n }\n\n // Simple pass-through methods - no cache logic\n async query<T = any>(table: string, options?: QueryOptions): Promise<T[]> {\n return this.adapter.findMany<T>(table, options);\n }\n\n async queryOne<T = any>(\n table: string,\n options?: QueryOptions,\n ): Promise<T | null> {\n const results = await this.query<T>(table, { ...options, limit: 1 });\n return results[0] || null;\n }\n\n async queryById<T = any>(\n table: string,\n id: number | string,\n ): Promise<T | null> {\n return this.adapter.findById<T>(table, id);\n }\n\n async count(table: string, options?: QueryOptions): Promise<number> {\n return this.adapter.count(table, options);\n }\n\n async paginate<T = any>(\n table: string,\n page: number = 1,\n limit: number = 10,\n options?: QueryOptions,\n ): Promise<PaginatedResult<T>> {\n const offset = (page - 1) * limit;\n\n const [data, total] = await Promise.all([\n this.query<T>(table, { ...options, limit, offset }),\n this.count(table, options),\n ]);\n\n return {\n data,\n page,\n limit,\n total,\n totalPages: Math.ceil(total / limit),\n hasMore: page * limit < total,\n };\n }\n\n // Mutations - no cache invalidation here (React Query handles it)\n async create<T = any>(table: string, data: Partial<T>): Promise<T> {\n return this.adapter.create<T>(table, data);\n }\n\n async update<T = any>(\n table: string,\n id: number | string,\n data: Partial<T>,\n ): Promise<T> {\n return this.adapter.update<T>(table, id, data);\n }\n\n async delete(table: string, id: number | string): Promise<boolean> {\n return this.adapter.delete(table, id);\n }\n\n // Direct adapter access for advanced use cases\n getAdapter(): IDataAdapter {\n return this.adapter;\n }\n\n // ============================================\n // RAW SQL QUERIES\n // ============================================\n\n /**\n * Execute raw SQL query - returns multiple rows\n * Use for complex queries that can't be expressed with QueryOptions\n *\n * @example\n * const posts = await dm.raw<Post>(`\n * SELECT DISTINCT p.*, c.name as category_name\n * FROM posts p\n * JOIN post_categories pc ON p.id = pc.post_id\n * JOIN blog_categories c ON pc.category_id = c.id\n * WHERE c.slug = ? AND p.published = 1\n * `, [categorySlug]);\n */\n async raw<T = any>(sql: string, params?: any[]): Promise<T[]> {\n return this.adapter.raw<T>(sql, params);\n }\n\n /**\n * Execute raw SQL query - returns single row or null\n * Use for aggregations, single record lookups\n *\n * @example\n * const priceRange = await dm.rawOne<{min: number, max: number}>(`\n * SELECT MIN(price) as min, MAX(price) as max\n * FROM products WHERE published = 1\n * `);\n */\n async rawOne<T = any>(sql: string, params?: any[]): Promise<T | null> {\n return this.adapter.rawOne<T>(sql, params);\n }\n}\n"
|
|
28
|
+
"content": "import type { IDataAdapter } from \"../adapters/IDataAdapter\";\r\nimport type { QueryOptions, PaginatedResult } from \"./types\";\r\n\r\n/**\r\n * DataManager - Simple proxy to adapter\r\n */\r\nexport class DataManager {\r\n private static instance: DataManager;\r\n private adapter: IDataAdapter;\r\n\r\n private constructor(adapter: IDataAdapter) {\r\n this.adapter = adapter;\r\n }\r\n\r\n static getInstance(adapter: IDataAdapter): DataManager {\r\n if (!DataManager.instance) {\r\n DataManager.instance = new DataManager(adapter);\r\n }\r\n return DataManager.instance;\r\n }\r\n\r\n // Simple pass-through methods - no cache logic\r\n async query<T = any>(table: string, options?: QueryOptions): Promise<T[]> {\r\n return this.adapter.findMany<T>(table, options);\r\n }\r\n\r\n async queryOne<T = any>(\r\n table: string,\r\n options?: QueryOptions,\r\n ): Promise<T | null> {\r\n const results = await this.query<T>(table, { ...options, limit: 1 });\r\n return results[0] || null;\r\n }\r\n\r\n async queryById<T = any>(\r\n table: string,\r\n id: number | string,\r\n ): Promise<T | null> {\r\n return this.adapter.findById<T>(table, id);\r\n }\r\n\r\n async count(table: string, options?: QueryOptions): Promise<number> {\r\n return this.adapter.count(table, options);\r\n }\r\n\r\n async paginate<T = any>(\r\n table: string,\r\n page: number = 1,\r\n limit: number = 10,\r\n options?: QueryOptions,\r\n ): Promise<PaginatedResult<T>> {\r\n const offset = (page - 1) * limit;\r\n\r\n const [data, total] = await Promise.all([\r\n this.query<T>(table, { ...options, limit, offset }),\r\n this.count(table, options),\r\n ]);\r\n\r\n return {\r\n data,\r\n page,\r\n limit,\r\n total,\r\n totalPages: Math.ceil(total / limit),\r\n hasMore: page * limit < total,\r\n };\r\n }\r\n\r\n // Mutations - no cache invalidation here (React Query handles it)\r\n async create<T = any>(table: string, data: Partial<T>): Promise<T> {\r\n return this.adapter.create<T>(table, data);\r\n }\r\n\r\n async update<T = any>(\r\n table: string,\r\n id: number | string,\r\n data: Partial<T>,\r\n ): Promise<T> {\r\n return this.adapter.update<T>(table, id, data);\r\n }\r\n\r\n async delete(table: string, id: number | string): Promise<boolean> {\r\n return this.adapter.delete(table, id);\r\n }\r\n\r\n // Direct adapter access for advanced use cases\r\n getAdapter(): IDataAdapter {\r\n return this.adapter;\r\n }\r\n\r\n // ============================================\r\n // RAW SQL QUERIES\r\n // ============================================\r\n\r\n /**\r\n * Execute raw SQL query - returns multiple rows\r\n * Use for complex queries that can't be expressed with QueryOptions\r\n *\r\n * @example\r\n * const posts = await dm.raw<Post>(`\r\n * SELECT DISTINCT p.*, c.name as category_name\r\n * FROM posts p\r\n * JOIN post_categories pc ON p.id = pc.post_id\r\n * JOIN blog_categories c ON pc.category_id = c.id\r\n * WHERE c.slug = ? AND p.published = 1\r\n * `, [categorySlug]);\r\n */\r\n async raw<T = any>(sql: string, params?: any[]): Promise<T[]> {\r\n return this.adapter.raw<T>(sql, params);\r\n }\r\n\r\n /**\r\n * Execute raw SQL query - returns single row or null\r\n * Use for aggregations, single record lookups\r\n *\r\n * @example\r\n * const priceRange = await dm.rawOne<{min: number, max: number}>(`\r\n * SELECT MIN(price) as min, MAX(price) as max\r\n * FROM products WHERE published = 1\r\n * `);\r\n */\r\n async rawOne<T = any>(sql: string, params?: any[]): Promise<T | null> {\r\n return this.adapter.rawOne<T>(sql, params);\r\n }\r\n}\r\n"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "db/core/types.ts",
|
|
32
32
|
"type": "registry:type",
|
|
33
33
|
"target": "$modules$/db/core/types.ts",
|
|
34
|
-
"content": "/**\n * Core type definitions for data-access module\n */\n\n// ============================================\n// WHERE CONDITIONS\n// ============================================\n\n/** WHERE condition operators */\nexport type WhereOperator =\n | \"=\"\n | \"!=\"\n | \"<>\"\n | \">\"\n | \"<\"\n | \">=\"\n | \"<=\"\n | \"LIKE\"\n | \"NOT LIKE\"\n | \"IN\"\n | \"NOT IN\"\n | \"BETWEEN\"\n | \"NOT BETWEEN\"\n | \"IS NULL\"\n | \"IS NOT NULL\";\n\n/** Single WHERE condition */\nexport interface WhereCondition {\n field: string;\n operator: WhereOperator;\n value: any;\n}\n\n/** WHERE groups (AND/OR) - recursive structure */\nexport interface WhereGroup {\n type: \"AND\" | \"OR\";\n conditions: (WhereCondition | WhereGroup)[];\n}\n\n// ============================================\n// JOIN DEFINITIONS\n// ============================================\n\n/** JOIN types */\nexport type JoinType = \"INNER\" | \"LEFT\" | \"RIGHT\" | \"CROSS\";\n\n/** JOIN definition */\nexport interface JoinClause {\n type: JoinType;\n table: string;\n alias?: string;\n on: {\n leftField: string;\n rightField: string;\n };\n}\n\n// ============================================\n// QUERY OPTIONS\n// ============================================\n\n/** Order definition */\nexport interface OrderBy {\n field: string;\n direction: \"ASC\" | \"DESC\";\n}\n\n/** Extended Query Options */\nexport interface QueryOptions {\n // Existing (backwards compatible)\n where?: Record<string, any>;\n limit?: number;\n offset?: number;\n orderBy?: OrderBy[];\n include?: string[];\n\n // New features\n select?: string[]; // SELECT fields: ['p.*', 'c.name as category_name']\n distinct?: boolean; // DISTINCT usage\n joins?: JoinClause[]; // JOIN definitions\n whereAdvanced?: WhereGroup; // Complex WHERE conditions (AND/OR groups)\n groupBy?: string[]; // GROUP BY fields\n having?: WhereGroup; // HAVING conditions\n}\n\n// ============================================\n// RESULT TYPES\n// ============================================\n\n/** Paginated result */\nexport interface PaginatedResult<T> {\n data: T[];\n page: number;\n limit: number;\n total: number;\n totalPages: number;\n hasMore: boolean;\n}\n\n/** Query key (for cache) */\nexport type QueryKey = (string | number | object)[];\n"
|
|
34
|
+
"content": "/**\r\n * Core type definitions for data-access module\r\n */\r\n\r\n// ============================================\r\n// WHERE CONDITIONS\r\n// ============================================\r\n\r\n/** WHERE condition operators */\r\nexport type WhereOperator =\r\n | \"=\"\r\n | \"!=\"\r\n | \"<>\"\r\n | \">\"\r\n | \"<\"\r\n | \">=\"\r\n | \"<=\"\r\n | \"LIKE\"\r\n | \"NOT LIKE\"\r\n | \"IN\"\r\n | \"NOT IN\"\r\n | \"BETWEEN\"\r\n | \"NOT BETWEEN\"\r\n | \"IS NULL\"\r\n | \"IS NOT NULL\";\r\n\r\n/** Single WHERE condition */\r\nexport interface WhereCondition {\r\n field: string;\r\n operator: WhereOperator;\r\n value: any;\r\n}\r\n\r\n/** WHERE groups (AND/OR) - recursive structure */\r\nexport interface WhereGroup {\r\n type: \"AND\" | \"OR\";\r\n conditions: (WhereCondition | WhereGroup)[];\r\n}\r\n\r\n// ============================================\r\n// JOIN DEFINITIONS\r\n// ============================================\r\n\r\n/** JOIN types */\r\nexport type JoinType = \"INNER\" | \"LEFT\" | \"RIGHT\" | \"CROSS\";\r\n\r\n/** JOIN definition */\r\nexport interface JoinClause {\r\n type: JoinType;\r\n table: string;\r\n alias?: string;\r\n on: {\r\n leftField: string;\r\n rightField: string;\r\n };\r\n}\r\n\r\n// ============================================\r\n// QUERY OPTIONS\r\n// ============================================\r\n\r\n/** Order definition */\r\nexport interface OrderBy {\r\n field: string;\r\n direction: \"ASC\" | \"DESC\";\r\n}\r\n\r\n/** Extended Query Options */\r\nexport interface QueryOptions {\r\n // Existing (backwards compatible)\r\n where?: Record<string, any>;\r\n limit?: number;\r\n offset?: number;\r\n orderBy?: OrderBy[];\r\n include?: string[];\r\n\r\n // New features\r\n select?: string[]; // SELECT fields: ['p.*', 'c.name as category_name']\r\n distinct?: boolean; // DISTINCT usage\r\n joins?: JoinClause[]; // JOIN definitions\r\n whereAdvanced?: WhereGroup; // Complex WHERE conditions (AND/OR groups)\r\n groupBy?: string[]; // GROUP BY fields\r\n having?: WhereGroup; // HAVING conditions\r\n}\r\n\r\n// ============================================\r\n// RESULT TYPES\r\n// ============================================\r\n\r\n/** Paginated result */\r\nexport interface PaginatedResult<T> {\r\n data: T[];\r\n page: number;\r\n limit: number;\r\n total: number;\r\n totalPages: number;\r\n hasMore: boolean;\r\n}\r\n\r\n/** Query key (for cache) */\r\nexport type QueryKey = (string | number | object)[];\r\n"
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"path": "db/adapters/IDataAdapter.ts",
|
|
38
38
|
"type": "registry:type",
|
|
39
39
|
"target": "$modules$/db/adapters/IDataAdapter.ts",
|
|
40
|
-
"content": "import type { QueryOptions } from \"../core/types\";\n\n/**\n * Data Adapter Interface\n * Implement this interface to create custom data sources\n */\nexport interface IDataAdapter {\n // Connection\n connect(): Promise<void>;\n disconnect(): Promise<void>;\n\n // CRUD operations\n findMany<T>(table: string, options?: QueryOptions): Promise<T[]>;\n findOne<T>(table: string, options: QueryOptions): Promise<T | null>;\n findById<T>(table: string, id: number | string): Promise<T | null>;\n create<T>(table: string, data: Partial<T>): Promise<T>;\n update<T>(table: string, id: number | string, data: Partial<T>): Promise<T>;\n delete(table: string, id: number | string): Promise<boolean>;\n\n // Utilities\n count(table: string, options?: QueryOptions): Promise<number>;\n\n // Raw SQL queries - for complex queries that can't be expressed with QueryOptions\n raw<T>(sql: string, params?: any[]): Promise<T[]>;\n rawOne<T>(sql: string, params?: any[]): Promise<T | null>;\n}\n"
|
|
40
|
+
"content": "import type { QueryOptions } from \"../core/types\";\r\n\r\n/**\r\n * Data Adapter Interface\r\n * Implement this interface to create custom data sources\r\n */\r\nexport interface IDataAdapter {\r\n // Connection\r\n connect(): Promise<void>;\r\n disconnect(): Promise<void>;\r\n\r\n // CRUD operations\r\n findMany<T>(table: string, options?: QueryOptions): Promise<T[]>;\r\n findOne<T>(table: string, options: QueryOptions): Promise<T | null>;\r\n findById<T>(table: string, id: number | string): Promise<T | null>;\r\n create<T>(table: string, data: Partial<T>): Promise<T>;\r\n update<T>(table: string, id: number | string, data: Partial<T>): Promise<T>;\r\n delete(table: string, id: number | string): Promise<boolean>;\r\n\r\n // Utilities\r\n count(table: string, options?: QueryOptions): Promise<number>;\r\n\r\n // Raw SQL queries - for complex queries that can't be expressed with QueryOptions\r\n raw<T>(sql: string, params?: any[]): Promise<T[]>;\r\n rawOne<T>(sql: string, params?: any[]): Promise<T | null>;\r\n}\r\n"
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"path": "db/adapters/index.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"path": "db/adapters/SqliteAdapter.ts",
|
|
50
50
|
"type": "registry:lib",
|
|
51
51
|
"target": "$modules$/db/adapters/SqliteAdapter.ts",
|
|
52
|
-
"content": "import initSqlJs from \"sql.js\";\nimport type { Database } from \"sql.js\";\nimport type { IDataAdapter } from \"./IDataAdapter\";\nimport type {\n QueryOptions,\n WhereCondition,\n WhereGroup,\n JoinClause,\n} from \"../core/types\";\n\n/**\n * SQLite Adapter\n * Loads database from file using sql.js\n * Supports complex queries: JOIN, WHERE groups, LIKE, IN, BETWEEN\n */\nexport class SqliteAdapter implements IDataAdapter {\n private db: Database | null = null;\n private dbPath: string;\n\n constructor(dbPath: string = \"/data/database.db\") {\n this.dbPath = dbPath;\n }\n\n // ============================================\n // CONNECTION\n // ============================================\n\n async connect(): Promise<void> {\n if (this.db) return;\n\n try {\n const SQL = await initSqlJs({\n locateFile: (file: string) => `https://sql.js.org/dist/${file}`,\n });\n\n const response = await fetch(this.dbPath);\n if (!response.ok) {\n throw new Error(`Database file not found: ${this.dbPath}`);\n }\n\n const buffer = await response.arrayBuffer();\n this.db = new SQL.Database(new Uint8Array(buffer));\n console.log(\"SQLite adapter connected\");\n } catch (error) {\n console.error(\"SQLite adapter connection failed:\", error);\n throw error;\n }\n }\n\n async disconnect(): Promise<void> {\n if (this.db) {\n this.db.close();\n this.db = null;\n }\n }\n\n // ============================================\n // CRUD OPERATIONS\n // ============================================\n\n async findMany<T>(table: string, options?: QueryOptions): Promise<T[]> {\n await this.connect();\n if (!this.db) {\n console.warn(\"Database not connected\");\n return [];\n }\n try {\n const { sql, params } = this.buildSelectQuery(table, options);\n return this.executeQuery<T>(sql, params);\n } catch (error) {\n console.error(`Error querying table ${table}:`, error);\n return [];\n }\n }\n\n async findOne<T>(table: string, options: QueryOptions): Promise<T | null> {\n const results = await this.findMany<T>(table, { ...options, limit: 1 });\n return results[0] || null;\n }\n\n async findById<T>(table: string, id: number | string): Promise<T | null> {\n return this.findOne<T>(table, { where: { id } });\n }\n\n async create<T>(table: string, data: Partial<T>): Promise<T> {\n await this.connect();\n\n // Otomatik timestamp - sadece yoksa ekle\n const dataWithTimestamps: any = { ...data };\n\n if (!dataWithTimestamps.created_at) {\n dataWithTimestamps.created_at = new Date().toISOString();\n }\n\n if (!dataWithTimestamps.updated_at) {\n dataWithTimestamps.updated_at = new Date().toISOString();\n }\n\n const keys = Object.keys(dataWithTimestamps);\n const values = Object.values(dataWithTimestamps) as any[];\n const placeholders = keys.map(() => \"?\").join(\", \");\n\n const sql = `INSERT INTO ${table} (${keys.join(\", \")}) VALUES (${placeholders})`;\n this.db!.run(sql, values);\n\n const lastIdResult = this.db!.exec(\"SELECT last_insert_rowid() as id\");\n const lastId = lastIdResult[0]?.values[0]?.[0] as number;\n\n return this.findById<T>(table, lastId) as Promise<T>;\n }\n\n async update<T>(\n table: string,\n id: number | string,\n data: Partial<T>,\n ): Promise<T> {\n await this.connect();\n\n // Otomatik updated_at - her zaman güncelle\n const dataWithTimestamp = {\n ...data,\n updated_at: new Date().toISOString(),\n };\n\n const keys = Object.keys(dataWithTimestamp);\n const values = Object.values(dataWithTimestamp) as any[];\n const setClause = keys.map((key) => `${key} = ?`).join(\", \");\n\n const sql = `UPDATE ${table} SET ${setClause} WHERE id = ?`;\n this.db!.run(sql, [...values, id]);\n\n return this.findById<T>(table, id) as Promise<T>;\n }\n\n async delete(table: string, id: number | string): Promise<boolean> {\n await this.connect();\n const sql = `DELETE FROM ${table} WHERE id = ?`;\n this.db!.run(sql, [id]);\n return true;\n }\n\n async count(table: string, options?: QueryOptions): Promise<number> {\n await this.connect();\n const { sql, params } = this.buildCountQuery(table, options);\n const results = await this.executeQuery<{ count: number }>(sql, params);\n return results[0]?.count || 0;\n }\n\n // ============================================\n // RAW SQL QUERIES\n // ============================================\n\n async raw<T>(sql: string, params?: any[]): Promise<T[]> {\n await this.connect();\n return this.executeQuery<T>(sql, params);\n }\n\n async rawOne<T>(sql: string, params?: any[]): Promise<T | null> {\n const results = await this.raw<T>(sql, params);\n return results[0] || null;\n }\n\n // ============================================\n // PRIVATE: QUERY EXECUTION\n // ============================================\n\n private async executeQuery<T>(sql: string, params?: any[]): Promise<T[]> {\n if (!this.db) {\n console.warn(\"Database not connected\");\n return [];\n }\n try {\n const stmt = this.db.prepare(sql);\n if (params && params.length > 0) stmt.bind(params);\n\n const results: T[] = [];\n while (stmt.step()) {\n results.push(stmt.getAsObject() as T);\n }\n stmt.free();\n return results;\n } catch (error) {\n console.error(`Query error: ${sql}`, error);\n return [];\n }\n }\n\n // ============================================\n // PRIVATE: QUERY BUILDERS\n // ============================================\n\n private buildSelectQuery(\n table: string,\n options?: QueryOptions,\n ): { sql: string; params: any[] } {\n const params: any[] = [];\n\n // SELECT clause\n const selectFields = options?.select?.join(\", \") || \"*\";\n const distinct = options?.distinct ? \"DISTINCT \" : \"\";\n let sql = `SELECT ${distinct}${selectFields} FROM ${table}`;\n\n // JOIN clauses\n if (options?.joins && options.joins.length > 0) {\n sql += this.buildJoinClause(options.joins);\n }\n\n // WHERE clause\n const whereClause = this.buildWhereClause(options, params);\n if (whereClause) {\n sql += ` WHERE ${whereClause}`;\n }\n\n // GROUP BY clause\n if (options?.groupBy && options.groupBy.length > 0) {\n sql += ` GROUP BY ${options.groupBy.join(\", \")}`;\n }\n\n // HAVING clause\n if (options?.having) {\n const havingClause = this.buildWhereGroupClause(options.having, params);\n if (havingClause) {\n sql += ` HAVING ${havingClause}`;\n }\n }\n\n // ORDER BY clause\n if (options?.orderBy && options.orderBy.length > 0) {\n const orderClauses = options.orderBy.map(\n (o) => `${o.field} ${o.direction}`,\n );\n sql += ` ORDER BY ${orderClauses.join(\", \")}`;\n }\n\n // LIMIT & OFFSET\n if (options?.limit) {\n sql += ` LIMIT ?`;\n params.push(options.limit);\n }\n\n if (options?.offset) {\n sql += ` OFFSET ?`;\n params.push(options.offset);\n }\n\n return { sql, params };\n }\n\n private buildCountQuery(\n table: string,\n options?: QueryOptions,\n ): { sql: string; params: any[] } {\n const params: any[] = [];\n let sql = `SELECT COUNT(*) as count FROM ${table}`;\n\n // JOIN clauses\n if (options?.joins && options.joins.length > 0) {\n sql += this.buildJoinClause(options.joins);\n }\n\n // WHERE clause\n const whereClause = this.buildWhereClause(options, params);\n if (whereClause) {\n sql += ` WHERE ${whereClause}`;\n }\n\n return { sql, params };\n }\n\n private buildJoinClause(joins: JoinClause[]): string {\n return joins\n .map((join) => {\n const alias = join.alias ? ` ${join.alias}` : \"\";\n const leftField = join.on.leftField;\n const rightField = join.on.rightField;\n return ` ${join.type} JOIN ${join.table}${alias} ON ${leftField} = ${rightField}`;\n })\n .join(\"\");\n }\n\n private buildWhereClause(\n options: QueryOptions | undefined,\n params: any[],\n ): string {\n const clauses: string[] = [];\n\n // Simple where (backwards compatible)\n if (options?.where) {\n const simpleConditions = Object.entries(options.where).map(\n ([key, value]) => {\n params.push(value);\n return `${key} = ?`;\n },\n );\n if (simpleConditions.length > 0) {\n clauses.push(simpleConditions.join(\" AND \"));\n }\n }\n\n // Advanced where (new feature)\n if (options?.whereAdvanced) {\n const advancedClause = this.buildWhereGroupClause(\n options.whereAdvanced,\n params,\n );\n if (advancedClause) {\n clauses.push(advancedClause);\n }\n }\n\n return clauses.length > 0 ? clauses.join(\" AND \") : \"\";\n }\n\n private buildWhereGroupClause(group: WhereGroup, params: any[]): string {\n const conditions = group.conditions\n .map((condition) => {\n // Recursive: WhereGroup\n if (\"type\" in condition && \"conditions\" in condition) {\n return `(${this.buildWhereGroupClause(condition as WhereGroup, params)})`;\n }\n // WhereCondition\n return this.buildConditionClause(condition as WhereCondition, params);\n })\n .filter(Boolean);\n\n return conditions.join(` ${group.type} `);\n }\n\n private buildConditionClause(\n condition: WhereCondition,\n params: any[],\n ): string {\n const { field, operator, value } = condition;\n\n switch (operator) {\n case \"IS NULL\":\n return `${field} IS NULL`;\n\n case \"IS NOT NULL\":\n return `${field} IS NOT NULL`;\n\n case \"IN\":\n case \"NOT IN\":\n if (Array.isArray(value)) {\n const placeholders = value.map(() => \"?\").join(\", \");\n params.push(...value);\n return `${field} ${operator} (${placeholders})`;\n }\n return \"\";\n\n case \"BETWEEN\":\n case \"NOT BETWEEN\":\n if (Array.isArray(value) && value.length === 2) {\n params.push(value[0], value[1]);\n return `${field} ${operator} ? AND ?`;\n }\n return \"\";\n\n default:\n // =, !=, <>, >, <, >=, <=, LIKE, NOT LIKE\n params.push(value);\n return `${field} ${operator} ?`;\n }\n }\n}\n"
|
|
52
|
+
"content": "import initSqlJs from \"sql.js\";\r\nimport type { Database } from \"sql.js\";\r\nimport type { IDataAdapter } from \"./IDataAdapter\";\r\nimport type {\r\n QueryOptions,\r\n WhereCondition,\r\n WhereGroup,\r\n JoinClause,\r\n} from \"../core/types\";\r\n\r\n/**\r\n * SQLite Adapter\r\n * Loads database from file using sql.js\r\n * Supports complex queries: JOIN, WHERE groups, LIKE, IN, BETWEEN\r\n */\r\nexport class SqliteAdapter implements IDataAdapter {\r\n private db: Database | null = null;\r\n private dbPath: string;\r\n\r\n constructor(dbPath: string = \"/data/database.db\") {\r\n this.dbPath = dbPath;\r\n }\r\n\r\n // ============================================\r\n // CONNECTION\r\n // ============================================\r\n\r\n async connect(): Promise<void> {\r\n if (this.db) return;\r\n\r\n try {\r\n const SQL = await initSqlJs({\r\n locateFile: (file: string) => `https://sql.js.org/dist/${file}`,\r\n });\r\n\r\n const response = await fetch(this.dbPath);\r\n if (!response.ok) {\r\n throw new Error(`Database file not found: ${this.dbPath}`);\r\n }\r\n\r\n const buffer = await response.arrayBuffer();\r\n this.db = new SQL.Database(new Uint8Array(buffer));\r\n console.log(\"SQLite adapter connected\");\r\n } catch (error) {\r\n console.error(\"SQLite adapter connection failed:\", error);\r\n throw error;\r\n }\r\n }\r\n\r\n async disconnect(): Promise<void> {\r\n if (this.db) {\r\n this.db.close();\r\n this.db = null;\r\n }\r\n }\r\n\r\n // ============================================\r\n // CRUD OPERATIONS\r\n // ============================================\r\n\r\n async findMany<T>(table: string, options?: QueryOptions): Promise<T[]> {\r\n await this.connect();\r\n if (!this.db) {\r\n console.warn(\"Database not connected\");\r\n return [];\r\n }\r\n try {\r\n const { sql, params } = this.buildSelectQuery(table, options);\r\n return this.executeQuery<T>(sql, params);\r\n } catch (error) {\r\n console.error(`Error querying table ${table}:`, error);\r\n return [];\r\n }\r\n }\r\n\r\n async findOne<T>(table: string, options: QueryOptions): Promise<T | null> {\r\n const results = await this.findMany<T>(table, { ...options, limit: 1 });\r\n return results[0] || null;\r\n }\r\n\r\n async findById<T>(table: string, id: number | string): Promise<T | null> {\r\n return this.findOne<T>(table, { where: { id } });\r\n }\r\n\r\n async create<T>(table: string, data: Partial<T>): Promise<T> {\r\n await this.connect();\r\n\r\n // Otomatik timestamp - sadece yoksa ekle\r\n const dataWithTimestamps: any = { ...data };\r\n\r\n if (!dataWithTimestamps.created_at) {\r\n dataWithTimestamps.created_at = new Date().toISOString();\r\n }\r\n\r\n if (!dataWithTimestamps.updated_at) {\r\n dataWithTimestamps.updated_at = new Date().toISOString();\r\n }\r\n\r\n const keys = Object.keys(dataWithTimestamps);\r\n const values = Object.values(dataWithTimestamps) as any[];\r\n const placeholders = keys.map(() => \"?\").join(\", \");\r\n\r\n const sql = `INSERT INTO ${table} (${keys.join(\", \")}) VALUES (${placeholders})`;\r\n this.db!.run(sql, values);\r\n\r\n const lastIdResult = this.db!.exec(\"SELECT last_insert_rowid() as id\");\r\n const lastId = lastIdResult[0]?.values[0]?.[0] as number;\r\n\r\n return this.findById<T>(table, lastId) as Promise<T>;\r\n }\r\n\r\n async update<T>(\r\n table: string,\r\n id: number | string,\r\n data: Partial<T>,\r\n ): Promise<T> {\r\n await this.connect();\r\n\r\n // Otomatik updated_at - her zaman güncelle\r\n const dataWithTimestamp = {\r\n ...data,\r\n updated_at: new Date().toISOString(),\r\n };\r\n\r\n const keys = Object.keys(dataWithTimestamp);\r\n const values = Object.values(dataWithTimestamp) as any[];\r\n const setClause = keys.map((key) => `${key} = ?`).join(\", \");\r\n\r\n const sql = `UPDATE ${table} SET ${setClause} WHERE id = ?`;\r\n this.db!.run(sql, [...values, id]);\r\n\r\n return this.findById<T>(table, id) as Promise<T>;\r\n }\r\n\r\n async delete(table: string, id: number | string): Promise<boolean> {\r\n await this.connect();\r\n const sql = `DELETE FROM ${table} WHERE id = ?`;\r\n this.db!.run(sql, [id]);\r\n return true;\r\n }\r\n\r\n async count(table: string, options?: QueryOptions): Promise<number> {\r\n await this.connect();\r\n const { sql, params } = this.buildCountQuery(table, options);\r\n const results = await this.executeQuery<{ count: number }>(sql, params);\r\n return results[0]?.count || 0;\r\n }\r\n\r\n // ============================================\r\n // RAW SQL QUERIES\r\n // ============================================\r\n\r\n async raw<T>(sql: string, params?: any[]): Promise<T[]> {\r\n await this.connect();\r\n return this.executeQuery<T>(sql, params);\r\n }\r\n\r\n async rawOne<T>(sql: string, params?: any[]): Promise<T | null> {\r\n const results = await this.raw<T>(sql, params);\r\n return results[0] || null;\r\n }\r\n\r\n // ============================================\r\n // PRIVATE: QUERY EXECUTION\r\n // ============================================\r\n\r\n private async executeQuery<T>(sql: string, params?: any[]): Promise<T[]> {\r\n if (!this.db) {\r\n console.warn(\"Database not connected\");\r\n return [];\r\n }\r\n try {\r\n const stmt = this.db.prepare(sql);\r\n if (params && params.length > 0) stmt.bind(params);\r\n\r\n const results: T[] = [];\r\n while (stmt.step()) {\r\n results.push(stmt.getAsObject() as T);\r\n }\r\n stmt.free();\r\n return results;\r\n } catch (error) {\r\n console.error(`Query error: ${sql}`, error);\r\n return [];\r\n }\r\n }\r\n\r\n // ============================================\r\n // PRIVATE: QUERY BUILDERS\r\n // ============================================\r\n\r\n private buildSelectQuery(\r\n table: string,\r\n options?: QueryOptions,\r\n ): { sql: string; params: any[] } {\r\n const params: any[] = [];\r\n\r\n // SELECT clause\r\n const selectFields = options?.select?.join(\", \") || \"*\";\r\n const distinct = options?.distinct ? \"DISTINCT \" : \"\";\r\n let sql = `SELECT ${distinct}${selectFields} FROM ${table}`;\r\n\r\n // JOIN clauses\r\n if (options?.joins && options.joins.length > 0) {\r\n sql += this.buildJoinClause(options.joins);\r\n }\r\n\r\n // WHERE clause\r\n const whereClause = this.buildWhereClause(options, params);\r\n if (whereClause) {\r\n sql += ` WHERE ${whereClause}`;\r\n }\r\n\r\n // GROUP BY clause\r\n if (options?.groupBy && options.groupBy.length > 0) {\r\n sql += ` GROUP BY ${options.groupBy.join(\", \")}`;\r\n }\r\n\r\n // HAVING clause\r\n if (options?.having) {\r\n const havingClause = this.buildWhereGroupClause(options.having, params);\r\n if (havingClause) {\r\n sql += ` HAVING ${havingClause}`;\r\n }\r\n }\r\n\r\n // ORDER BY clause\r\n if (options?.orderBy && options.orderBy.length > 0) {\r\n const orderClauses = options.orderBy.map(\r\n (o) => `${o.field} ${o.direction}`,\r\n );\r\n sql += ` ORDER BY ${orderClauses.join(\", \")}`;\r\n }\r\n\r\n // LIMIT & OFFSET\r\n if (options?.limit) {\r\n sql += ` LIMIT ?`;\r\n params.push(options.limit);\r\n }\r\n\r\n if (options?.offset) {\r\n sql += ` OFFSET ?`;\r\n params.push(options.offset);\r\n }\r\n\r\n return { sql, params };\r\n }\r\n\r\n private buildCountQuery(\r\n table: string,\r\n options?: QueryOptions,\r\n ): { sql: string; params: any[] } {\r\n const params: any[] = [];\r\n let sql = `SELECT COUNT(*) as count FROM ${table}`;\r\n\r\n // JOIN clauses\r\n if (options?.joins && options.joins.length > 0) {\r\n sql += this.buildJoinClause(options.joins);\r\n }\r\n\r\n // WHERE clause\r\n const whereClause = this.buildWhereClause(options, params);\r\n if (whereClause) {\r\n sql += ` WHERE ${whereClause}`;\r\n }\r\n\r\n return { sql, params };\r\n }\r\n\r\n private buildJoinClause(joins: JoinClause[]): string {\r\n return joins\r\n .map((join) => {\r\n const alias = join.alias ? ` ${join.alias}` : \"\";\r\n const leftField = join.on.leftField;\r\n const rightField = join.on.rightField;\r\n return ` ${join.type} JOIN ${join.table}${alias} ON ${leftField} = ${rightField}`;\r\n })\r\n .join(\"\");\r\n }\r\n\r\n private buildWhereClause(\r\n options: QueryOptions | undefined,\r\n params: any[],\r\n ): string {\r\n const clauses: string[] = [];\r\n\r\n // Simple where (backwards compatible)\r\n if (options?.where) {\r\n const simpleConditions = Object.entries(options.where).map(\r\n ([key, value]) => {\r\n params.push(value);\r\n return `${key} = ?`;\r\n },\r\n );\r\n if (simpleConditions.length > 0) {\r\n clauses.push(simpleConditions.join(\" AND \"));\r\n }\r\n }\r\n\r\n // Advanced where (new feature)\r\n if (options?.whereAdvanced) {\r\n const advancedClause = this.buildWhereGroupClause(\r\n options.whereAdvanced,\r\n params,\r\n );\r\n if (advancedClause) {\r\n clauses.push(advancedClause);\r\n }\r\n }\r\n\r\n return clauses.length > 0 ? clauses.join(\" AND \") : \"\";\r\n }\r\n\r\n private buildWhereGroupClause(group: WhereGroup, params: any[]): string {\r\n const conditions = group.conditions\r\n .map((condition) => {\r\n // Recursive: WhereGroup\r\n if (\"type\" in condition && \"conditions\" in condition) {\r\n return `(${this.buildWhereGroupClause(condition as WhereGroup, params)})`;\r\n }\r\n // WhereCondition\r\n return this.buildConditionClause(condition as WhereCondition, params);\r\n })\r\n .filter(Boolean);\r\n\r\n return conditions.join(` ${group.type} `);\r\n }\r\n\r\n private buildConditionClause(\r\n condition: WhereCondition,\r\n params: any[],\r\n ): string {\r\n const { field, operator, value } = condition;\r\n\r\n switch (operator) {\r\n case \"IS NULL\":\r\n return `${field} IS NULL`;\r\n\r\n case \"IS NOT NULL\":\r\n return `${field} IS NOT NULL`;\r\n\r\n case \"IN\":\r\n case \"NOT IN\":\r\n if (Array.isArray(value)) {\r\n const placeholders = value.map(() => \"?\").join(\", \");\r\n params.push(...value);\r\n return `${field} ${operator} (${placeholders})`;\r\n }\r\n return \"\";\r\n\r\n case \"BETWEEN\":\r\n case \"NOT BETWEEN\":\r\n if (Array.isArray(value) && value.length === 2) {\r\n params.push(value[0], value[1]);\r\n return `${field} ${operator} ? AND ?`;\r\n }\r\n return \"\";\r\n\r\n default:\r\n // =, !=, <>, >, <, >=, <=, LIKE, NOT LIKE\r\n params.push(value);\r\n return `${field} ${operator} ?`;\r\n }\r\n }\r\n}\r\n"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"path": "db/react/index.ts",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"path": "db/react/useRepository.ts",
|
|
74
74
|
"type": "registry:hook",
|
|
75
75
|
"target": "$modules$/db/react/useRepository.ts",
|
|
76
|
-
"content": "import {\n useQuery,\n useMutation,\n useQueryClient,\n useInfiniteQuery,\n type UseQueryOptions,\n type UseMutationOptions,\n type UseInfiniteQueryOptions,\n} from \"@tanstack/react-query\";\nimport { DataManager } from \"../core/DataManager\";\nimport { getAdapter } from \"../config\";\nimport { queryKeys } from \"./queryClient\";\nimport type { QueryOptions } from \"../core/types\";\n\n// Singleton manager\nlet managerInstance: DataManager | null = null;\nfunction getManager() {\n if (!managerInstance) {\n managerInstance = DataManager.getInstance(getAdapter());\n }\n return managerInstance;\n}\n\n// ==========================================\n// QUERY HOOKS\n// ==========================================\n\n// Omit 'select' from QueryOptions to avoid conflict with React Query's select\nexport interface RepositoryQueryOptions<T> extends Omit<\n QueryOptions,\n \"select\"\n> {\n // SQL SELECT fields (renamed to avoid conflict)\n selectFields?: string[];\n\n // React Query options\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n refetchOnWindowFocus?: boolean;\n refetchInterval?: number | false;\n select?: (data: T[]) => any; // React Query data transformation\n}\n\n/**\n * Generic query hook - React Query handles all caching\n * @example\n * // Simple query\n * const { data: posts, isLoading } = useRepositoryQuery('posts', {\n * where: { published: 1 },\n * orderBy: [{ field: 'created_at', direction: 'DESC' }],\n * staleTime: 60000\n * });\n *\n * // With JOIN\n * const { data: posts } = useRepositoryQuery('posts', {\n * selectFields: ['posts.*', 'c.name as category_name'],\n * joins: [{\n * type: 'INNER',\n * table: 'post_categories',\n * alias: 'pc',\n * on: { leftField: 'posts.id', rightField: 'pc.post_id' }\n * }],\n * distinct: true\n * });\n *\n * // With complex WHERE\n * const { data: products } = useRepositoryQuery('products', {\n * whereAdvanced: {\n * type: 'AND',\n * conditions: [\n * { field: 'published', operator: '=', value: 1 },\n * { type: 'OR', conditions: [\n * { field: 'name', operator: 'LIKE', value: '%phone%' },\n * { field: 'description', operator: 'LIKE', value: '%phone%' }\n * ]}\n * ]\n * }\n * });\n */\nexport function useRepositoryQuery<T = any>(\n table: string,\n options: RepositoryQueryOptions<T> = {},\n queryOptions?: Omit<UseQueryOptions<T[], Error>, \"queryKey\" | \"queryFn\">,\n) {\n const manager = getManager();\n const {\n // QueryOptions fields\n where,\n limit,\n offset,\n orderBy,\n include,\n // New complex query fields\n selectFields,\n distinct,\n joins,\n whereAdvanced,\n groupBy,\n having,\n // React Query options\n select,\n enabled,\n staleTime,\n gcTime,\n refetchOnWindowFocus,\n refetchInterval,\n } = options;\n\n const queryOpts: QueryOptions = {\n where,\n limit,\n offset,\n orderBy,\n include,\n select: selectFields,\n distinct,\n joins,\n whereAdvanced,\n groupBy,\n having,\n };\n\n return useQuery<T[], Error>({\n queryKey: queryKeys.list(table, queryOpts),\n queryFn: () => manager.query<T>(table, queryOpts),\n select,\n enabled,\n staleTime,\n gcTime,\n refetchOnWindowFocus,\n refetchInterval,\n ...queryOptions,\n });\n}\n\n/**\n * Query single record\n * @example\n * const { data: post } = useRepositoryQueryOne('posts', {\n * where: { slug: 'my-post' }\n * });\n */\nexport function useRepositoryQueryOne<T = any>(\n table: string,\n options: RepositoryQueryOptions<T> = {},\n) {\n const result = useRepositoryQuery<T>(table, { ...options, limit: 1 });\n\n return {\n ...result,\n data: result.data?.[0] || null,\n };\n}\n\n/**\n * Query by ID - React Query caches by ID automatically\n * @example\n * const { data: post, isLoading } = useRepositoryQueryById('posts', postId);\n */\nexport function useRepositoryQueryById<T = any>(\n table: string,\n id: number | string | null | undefined,\n options: Omit<UseQueryOptions<T | null, Error>, \"queryKey\" | \"queryFn\"> = {},\n) {\n const manager = getManager();\n\n return useQuery<T | null, Error>({\n queryKey: queryKeys.detail(table, id as any),\n queryFn: () => manager.queryById<T>(table, id as any),\n enabled: options.enabled !== false && id != null,\n ...options,\n });\n}\n\n/**\n * Paginated query - React Query caches each page\n * @example\n * const { data, totalPages, hasMore } = useRepositoryPagination('products', page, 20);\n */\nexport function useRepositoryPagination<T = any>(\n table: string,\n page: number = 1,\n limit: number = 10,\n options: QueryOptions = {},\n) {\n const manager = getManager();\n\n return useQuery({\n queryKey: queryKeys.paginated(table, page, limit, options),\n queryFn: () => manager.paginate<T>(table, page, limit, options),\n });\n}\n\n/**\n * Infinite query for infinite scroll / load more\n * @example\n * const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =\n * useRepositoryInfiniteQuery('posts', 20, {\n * where: { published: 1 },\n * orderBy: [{ field: 'created_at', direction: 'DESC' }]\n * });\n *\n * // data.pages = [page1Data, page2Data, page3Data, ...]\n * const allPosts = data?.pages.flatMap(page => page.data) ?? [];\n */\nexport function useRepositoryInfiniteQuery<T = any>(\n table: string,\n pageSize: number = 20,\n options: QueryOptions = {},\n queryOptions?: Omit<\n UseInfiniteQueryOptions<\n { data: T[]; page: number; totalPages: number; hasMore: boolean },\n Error\n >,\n \"queryKey\" | \"queryFn\" | \"getNextPageParam\" | \"initialPageParam\"\n >,\n) {\n const manager = getManager();\n\n return useInfiniteQuery({\n queryKey: queryKeys.infinite(table, pageSize, options),\n queryFn: ({ pageParam }) =>\n manager.paginate<T>(table, pageParam as number, pageSize, options),\n initialPageParam: 1,\n getNextPageParam: (lastPage) => {\n if (!lastPage.hasMore) return undefined;\n return lastPage.page + 1;\n },\n ...queryOptions,\n });\n}\n\n// ==========================================\n// MUTATION HOOKS (Auto-invalidation via React Query)\n// ==========================================\n\n/**\n * Create mutation - React Query handles cache invalidation\n * @example\n * const { mutate: createPost } = useRepositoryCreate('posts', {\n * onSuccess: () => toast.success('Created!')\n * });\n */\nexport function useRepositoryCreate<T = any>(\n table: string,\n options: Omit<UseMutationOptions<T, Error, Partial<T>>, \"mutationFn\"> & {\n invalidate?: string[];\n } = {},\n) {\n const manager = getManager();\n const queryClient = useQueryClient();\n const { invalidate = [table], ...mutationOptions } = options;\n\n return useMutation<T, Error, Partial<T>>({\n mutationFn: (data) => manager.create<T>(table, data),\n onSuccess: () => {\n // React Query automatically invalidates and refetches\n invalidate.forEach((t) => {\n queryClient.invalidateQueries({ queryKey: queryKeys.all(t) });\n });\n },\n ...mutationOptions,\n });\n}\n\n/**\n * Update mutation - Optimistic update via React Query\n * @example\n * const { mutate: updatePost } = useRepositoryUpdate('posts', {\n * onSuccess: () => toast.success('Updated!')\n * });\n * updatePost({ id: 1, data: { title: 'New Title' } });\n */\nexport function useRepositoryUpdate<T = any>(\n table: string,\n options: Omit<\n UseMutationOptions<T, Error, { id: number | string; data: Partial<T> }>,\n \"mutationFn\"\n > = {},\n) {\n const manager = getManager();\n const queryClient = useQueryClient();\n\n return useMutation<T, Error, { id: number | string; data: Partial<T> }>({\n mutationFn: ({ id, data }) => manager.update<T>(table, id, data),\n onSuccess: (data, variables) => {\n // Invalidate list queries\n queryClient.invalidateQueries({ queryKey: queryKeys.all(table) });\n\n // Update detail cache optimistically\n queryClient.setQueryData(queryKeys.detail(table, variables.id), data);\n },\n ...options,\n });\n}\n\n/**\n * Delete mutation\n * @example\n * const { mutate: deletePost } = useRepositoryDelete('posts', {\n * onSuccess: () => toast.success('Deleted!')\n * });\n * deletePost(postId);\n */\nexport function useRepositoryDelete(\n table: string,\n options: Omit<\n UseMutationOptions<boolean, Error, number | string>,\n \"mutationFn\"\n > = {},\n) {\n const manager = getManager();\n const queryClient = useQueryClient();\n\n return useMutation<boolean, Error, number | string>({\n mutationFn: (id) => manager.delete(table, id),\n onSuccess: (_data, id) => {\n // Invalidate and remove from cache\n queryClient.invalidateQueries({ queryKey: queryKeys.all(table) });\n queryClient.removeQueries({ queryKey: queryKeys.detail(table, id) });\n },\n ...options,\n });\n}\n\n// ==========================================\n// RAW SQL QUERY HOOKS\n// ==========================================\n\n/**\n * Raw SQL query hook - for complex queries that can't be expressed with QueryOptions\n * @example\n * // Complex JOIN query\n * const { data: posts } = useRawQuery<Post>(\n * ['posts-with-categories', categorySlug],\n * `SELECT DISTINCT p.*, c.name as category_name\n * FROM posts p\n * JOIN post_categories pc ON p.id = pc.post_id\n * JOIN blog_categories c ON pc.category_id = c.id\n * WHERE c.slug = ? AND p.published = 1\n * ORDER BY p.published_at DESC`,\n * [categorySlug]\n * );\n *\n * // Aggregation query\n * const { data: stats } = useRawQuery<{total: number, avg: number}>(\n * ['product-stats'],\n * `SELECT COUNT(*) as total, AVG(price) as avg FROM products WHERE published = 1`\n * );\n */\nexport function useRawQuery<T = any>(\n queryKey: any[],\n sql: string,\n params?: any[],\n options?: Omit<UseQueryOptions<T[], Error>, \"queryKey\" | \"queryFn\">,\n) {\n const manager = getManager();\n\n return useQuery<T[], Error>({\n queryKey: [\"raw\", ...queryKey],\n queryFn: () => manager.raw<T>(sql, params),\n ...options,\n });\n}\n\n/**\n * Raw SQL query hook for single result - aggregations, single lookups\n * @example\n * // Get price range\n * const { data: priceRange } = useRawQueryOne<{min: number, max: number}>(\n * ['price-range'],\n * `SELECT MIN(price) as min, MAX(price) as max FROM products WHERE published = 1`\n * );\n *\n * // Get single post with category\n * const { data: post } = useRawQueryOne<Post>(\n * ['post-detail', slug],\n * `SELECT p.*, c.name as category_name\n * FROM posts p\n * LEFT JOIN post_categories pc ON p.id = pc.post_id\n * LEFT JOIN blog_categories c ON pc.category_id = c.id\n * WHERE p.slug = ?`,\n * [slug]\n * );\n */\nexport function useRawQueryOne<T = any>(\n queryKey: any[],\n sql: string,\n params?: any[],\n options?: Omit<UseQueryOptions<T | null, Error>, \"queryKey\" | \"queryFn\">,\n) {\n const manager = getManager();\n\n return useQuery<T | null, Error>({\n queryKey: [\"raw\", ...queryKey],\n queryFn: () => manager.rawOne<T>(sql, params),\n ...options,\n });\n}\n"
|
|
76
|
+
"content": "import {\r\n useQuery,\r\n useMutation,\r\n useQueryClient,\r\n useInfiniteQuery,\r\n type UseQueryOptions,\r\n type UseMutationOptions,\r\n type UseInfiniteQueryOptions,\r\n} from \"@tanstack/react-query\";\r\nimport { DataManager } from \"../core/DataManager\";\r\nimport { getAdapter } from \"../config\";\r\nimport { queryKeys } from \"./queryClient\";\r\nimport type { QueryOptions } from \"../core/types\";\r\n\r\n// Singleton manager\r\nlet managerInstance: DataManager | null = null;\r\nfunction getManager() {\r\n if (!managerInstance) {\r\n managerInstance = DataManager.getInstance(getAdapter());\r\n }\r\n return managerInstance;\r\n}\r\n\r\n// ==========================================\r\n// QUERY HOOKS\r\n// ==========================================\r\n\r\n// Omit 'select' from QueryOptions to avoid conflict with React Query's select\r\nexport interface RepositoryQueryOptions<T> extends Omit<\r\n QueryOptions,\r\n \"select\"\r\n> {\r\n // SQL SELECT fields (renamed to avoid conflict)\r\n selectFields?: string[];\r\n\r\n // React Query options\r\n enabled?: boolean;\r\n staleTime?: number;\r\n gcTime?: number;\r\n refetchOnWindowFocus?: boolean;\r\n refetchInterval?: number | false;\r\n select?: (data: T[]) => any; // React Query data transformation\r\n}\r\n\r\n/**\r\n * Generic query hook - React Query handles all caching\r\n * @example\r\n * // Simple query\r\n * const { data: posts, isLoading } = useRepositoryQuery('posts', {\r\n * where: { published: 1 },\r\n * orderBy: [{ field: 'created_at', direction: 'DESC' }],\r\n * staleTime: 60000\r\n * });\r\n *\r\n * // With JOIN\r\n * const { data: posts } = useRepositoryQuery('posts', {\r\n * selectFields: ['posts.*', 'c.name as category_name'],\r\n * joins: [{\r\n * type: 'INNER',\r\n * table: 'post_categories',\r\n * alias: 'pc',\r\n * on: { leftField: 'posts.id', rightField: 'pc.post_id' }\r\n * }],\r\n * distinct: true\r\n * });\r\n *\r\n * // With complex WHERE\r\n * const { data: products } = useRepositoryQuery('products', {\r\n * whereAdvanced: {\r\n * type: 'AND',\r\n * conditions: [\r\n * { field: 'published', operator: '=', value: 1 },\r\n * { type: 'OR', conditions: [\r\n * { field: 'name', operator: 'LIKE', value: '%phone%' },\r\n * { field: 'description', operator: 'LIKE', value: '%phone%' }\r\n * ]}\r\n * ]\r\n * }\r\n * });\r\n */\r\nexport function useRepositoryQuery<T = any>(\r\n table: string,\r\n options: RepositoryQueryOptions<T> = {},\r\n queryOptions?: Omit<UseQueryOptions<T[], Error>, \"queryKey\" | \"queryFn\">,\r\n) {\r\n const manager = getManager();\r\n const {\r\n // QueryOptions fields\r\n where,\r\n limit,\r\n offset,\r\n orderBy,\r\n include,\r\n // New complex query fields\r\n selectFields,\r\n distinct,\r\n joins,\r\n whereAdvanced,\r\n groupBy,\r\n having,\r\n // React Query options\r\n select,\r\n enabled,\r\n staleTime,\r\n gcTime,\r\n refetchOnWindowFocus,\r\n refetchInterval,\r\n } = options;\r\n\r\n const queryOpts: QueryOptions = {\r\n where,\r\n limit,\r\n offset,\r\n orderBy,\r\n include,\r\n select: selectFields,\r\n distinct,\r\n joins,\r\n whereAdvanced,\r\n groupBy,\r\n having,\r\n };\r\n\r\n return useQuery<T[], Error>({\r\n queryKey: queryKeys.list(table, queryOpts),\r\n queryFn: () => manager.query<T>(table, queryOpts),\r\n select,\r\n enabled,\r\n staleTime,\r\n gcTime,\r\n refetchOnWindowFocus,\r\n refetchInterval,\r\n ...queryOptions,\r\n });\r\n}\r\n\r\n/**\r\n * Query single record\r\n * @example\r\n * const { data: post } = useRepositoryQueryOne('posts', {\r\n * where: { slug: 'my-post' }\r\n * });\r\n */\r\nexport function useRepositoryQueryOne<T = any>(\r\n table: string,\r\n options: RepositoryQueryOptions<T> = {},\r\n) {\r\n const result = useRepositoryQuery<T>(table, { ...options, limit: 1 });\r\n\r\n return {\r\n ...result,\r\n data: result.data?.[0] || null,\r\n };\r\n}\r\n\r\n/**\r\n * Query by ID - React Query caches by ID automatically\r\n * @example\r\n * const { data: post, isLoading } = useRepositoryQueryById('posts', postId);\r\n */\r\nexport function useRepositoryQueryById<T = any>(\r\n table: string,\r\n id: number | string | null | undefined,\r\n options: Omit<UseQueryOptions<T | null, Error>, \"queryKey\" | \"queryFn\"> = {},\r\n) {\r\n const manager = getManager();\r\n\r\n return useQuery<T | null, Error>({\r\n queryKey: queryKeys.detail(table, id as any),\r\n queryFn: () => manager.queryById<T>(table, id as any),\r\n enabled: options.enabled !== false && id != null,\r\n ...options,\r\n });\r\n}\r\n\r\n/**\r\n * Paginated query - React Query caches each page\r\n * @example\r\n * const { data, totalPages, hasMore } = useRepositoryPagination('products', page, 20);\r\n */\r\nexport function useRepositoryPagination<T = any>(\r\n table: string,\r\n page: number = 1,\r\n limit: number = 10,\r\n options: QueryOptions = {},\r\n) {\r\n const manager = getManager();\r\n\r\n return useQuery({\r\n queryKey: queryKeys.paginated(table, page, limit, options),\r\n queryFn: () => manager.paginate<T>(table, page, limit, options),\r\n });\r\n}\r\n\r\n/**\r\n * Infinite query for infinite scroll / load more\r\n * @example\r\n * const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =\r\n * useRepositoryInfiniteQuery('posts', 20, {\r\n * where: { published: 1 },\r\n * orderBy: [{ field: 'created_at', direction: 'DESC' }]\r\n * });\r\n *\r\n * // data.pages = [page1Data, page2Data, page3Data, ...]\r\n * const allPosts = data?.pages.flatMap(page => page.data) ?? [];\r\n */\r\nexport function useRepositoryInfiniteQuery<T = any>(\r\n table: string,\r\n pageSize: number = 20,\r\n options: QueryOptions = {},\r\n queryOptions?: Omit<\r\n UseInfiniteQueryOptions<\r\n { data: T[]; page: number; totalPages: number; hasMore: boolean },\r\n Error\r\n >,\r\n \"queryKey\" | \"queryFn\" | \"getNextPageParam\" | \"initialPageParam\"\r\n >,\r\n) {\r\n const manager = getManager();\r\n\r\n return useInfiniteQuery({\r\n queryKey: queryKeys.infinite(table, pageSize, options),\r\n queryFn: ({ pageParam }) =>\r\n manager.paginate<T>(table, pageParam as number, pageSize, options),\r\n initialPageParam: 1,\r\n getNextPageParam: (lastPage) => {\r\n if (!lastPage.hasMore) return undefined;\r\n return lastPage.page + 1;\r\n },\r\n ...queryOptions,\r\n });\r\n}\r\n\r\n// ==========================================\r\n// MUTATION HOOKS (Auto-invalidation via React Query)\r\n// ==========================================\r\n\r\n/**\r\n * Create mutation - React Query handles cache invalidation\r\n * @example\r\n * const { mutate: createPost } = useRepositoryCreate('posts', {\r\n * onSuccess: () => toast.success('Created!')\r\n * });\r\n */\r\nexport function useRepositoryCreate<T = any>(\r\n table: string,\r\n options: Omit<UseMutationOptions<T, Error, Partial<T>>, \"mutationFn\"> & {\r\n invalidate?: string[];\r\n } = {},\r\n) {\r\n const manager = getManager();\r\n const queryClient = useQueryClient();\r\n const { invalidate = [table], ...mutationOptions } = options;\r\n\r\n return useMutation<T, Error, Partial<T>>({\r\n mutationFn: (data) => manager.create<T>(table, data),\r\n onSuccess: () => {\r\n // React Query automatically invalidates and refetches\r\n invalidate.forEach((t) => {\r\n queryClient.invalidateQueries({ queryKey: queryKeys.all(t) });\r\n });\r\n },\r\n ...mutationOptions,\r\n });\r\n}\r\n\r\n/**\r\n * Update mutation - Optimistic update via React Query\r\n * @example\r\n * const { mutate: updatePost } = useRepositoryUpdate('posts', {\r\n * onSuccess: () => toast.success('Updated!')\r\n * });\r\n * updatePost({ id: 1, data: { title: 'New Title' } });\r\n */\r\nexport function useRepositoryUpdate<T = any>(\r\n table: string,\r\n options: Omit<\r\n UseMutationOptions<T, Error, { id: number | string; data: Partial<T> }>,\r\n \"mutationFn\"\r\n > = {},\r\n) {\r\n const manager = getManager();\r\n const queryClient = useQueryClient();\r\n\r\n return useMutation<T, Error, { id: number | string; data: Partial<T> }>({\r\n mutationFn: ({ id, data }) => manager.update<T>(table, id, data),\r\n onSuccess: (data, variables) => {\r\n // Invalidate list queries\r\n queryClient.invalidateQueries({ queryKey: queryKeys.all(table) });\r\n\r\n // Update detail cache optimistically\r\n queryClient.setQueryData(queryKeys.detail(table, variables.id), data);\r\n },\r\n ...options,\r\n });\r\n}\r\n\r\n/**\r\n * Delete mutation\r\n * @example\r\n * const { mutate: deletePost } = useRepositoryDelete('posts', {\r\n * onSuccess: () => toast.success('Deleted!')\r\n * });\r\n * deletePost(postId);\r\n */\r\nexport function useRepositoryDelete(\r\n table: string,\r\n options: Omit<\r\n UseMutationOptions<boolean, Error, number | string>,\r\n \"mutationFn\"\r\n > = {},\r\n) {\r\n const manager = getManager();\r\n const queryClient = useQueryClient();\r\n\r\n return useMutation<boolean, Error, number | string>({\r\n mutationFn: (id) => manager.delete(table, id),\r\n onSuccess: (_data, id) => {\r\n // Invalidate and remove from cache\r\n queryClient.invalidateQueries({ queryKey: queryKeys.all(table) });\r\n queryClient.removeQueries({ queryKey: queryKeys.detail(table, id) });\r\n },\r\n ...options,\r\n });\r\n}\r\n\r\n// ==========================================\r\n// RAW SQL QUERY HOOKS\r\n// ==========================================\r\n\r\n/**\r\n * Raw SQL query hook - for complex queries that can't be expressed with QueryOptions\r\n * @example\r\n * // Complex JOIN query\r\n * const { data: posts } = useRawQuery<Post>(\r\n * ['posts-with-categories', categorySlug],\r\n * `SELECT DISTINCT p.*, c.name as category_name\r\n * FROM posts p\r\n * JOIN post_categories pc ON p.id = pc.post_id\r\n * JOIN blog_categories c ON pc.category_id = c.id\r\n * WHERE c.slug = ? AND p.published = 1\r\n * ORDER BY p.published_at DESC`,\r\n * [categorySlug]\r\n * );\r\n *\r\n * // Aggregation query\r\n * const { data: stats } = useRawQuery<{total: number, avg: number}>(\r\n * ['product-stats'],\r\n * `SELECT COUNT(*) as total, AVG(price) as avg FROM products WHERE published = 1`\r\n * );\r\n */\r\nexport function useRawQuery<T = any>(\r\n queryKey: any[],\r\n sql: string,\r\n params?: any[],\r\n options?: Omit<UseQueryOptions<T[], Error>, \"queryKey\" | \"queryFn\">,\r\n) {\r\n const manager = getManager();\r\n\r\n return useQuery<T[], Error>({\r\n queryKey: [\"raw\", ...queryKey],\r\n queryFn: () => manager.raw<T>(sql, params),\r\n ...options,\r\n });\r\n}\r\n\r\n/**\r\n * Raw SQL query hook for single result - aggregations, single lookups\r\n * @example\r\n * // Get price range\r\n * const { data: priceRange } = useRawQueryOne<{min: number, max: number}>(\r\n * ['price-range'],\r\n * `SELECT MIN(price) as min, MAX(price) as max FROM products WHERE published = 1`\r\n * );\r\n *\r\n * // Get single post with category\r\n * const { data: post } = useRawQueryOne<Post>(\r\n * ['post-detail', slug],\r\n * `SELECT p.*, c.name as category_name\r\n * FROM posts p\r\n * LEFT JOIN post_categories pc ON p.id = pc.post_id\r\n * LEFT JOIN blog_categories c ON pc.category_id = c.id\r\n * WHERE p.slug = ?`,\r\n * [slug]\r\n * );\r\n */\r\nexport function useRawQueryOne<T = any>(\r\n queryKey: any[],\r\n sql: string,\r\n params?: any[],\r\n options?: Omit<UseQueryOptions<T | null, Error>, \"queryKey\" | \"queryFn\">,\r\n) {\r\n const manager = getManager();\r\n\r\n return useQuery<T | null, Error>({\r\n queryKey: [\"raw\", ...queryKey],\r\n queryFn: () => manager.rawOne<T>(sql, params),\r\n ...options,\r\n });\r\n}\r\n"
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
"path": "db/utils/parsers.ts",
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
"dependencies": [
|
|
7
7
|
"zustand"
|
|
8
8
|
],
|
|
9
|
-
"registryDependencies": [
|
|
9
|
+
"registryDependencies": [
|
|
10
|
+
"db"
|
|
11
|
+
],
|
|
10
12
|
"usage": "import { useCart, useFavorites, useDbProducts } from '@/modules/ecommerce-core';\n\n// No provider needed - just use the hooks:\nconst { addItem, removeItem, state, itemCount } = useCart();\nconst { addToFavorites, isFavorite } = useFavorites();\nconst { products, loading } = useDbProducts();\n\n// Or use stores directly with selectors:\nconst itemCount = useCartStore((s) => s.itemCount);",
|
|
11
13
|
"files": [
|
|
12
14
|
{
|
package/package.json
CHANGED
package/template/src/lib/api.ts
CHANGED
|
@@ -119,7 +119,7 @@ class ApiService {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// Get site owner email from ENV
|
|
122
|
-
const tenantEmail = import.meta.env.VITE_TENANT_MAIL;
|
|
122
|
+
const tenantEmail = constants.email || import.meta.env.VITE_TENANT_MAIL;
|
|
123
123
|
if (!tenantEmail) {
|
|
124
124
|
throw new Error("VITE_TENANT_MAIL environment variable is not set");
|
|
125
125
|
}
|
|
@@ -196,8 +196,7 @@ class ApiService {
|
|
|
196
196
|
async getOrderById(orderId: number): Promise<any | null> {
|
|
197
197
|
try {
|
|
198
198
|
const response = await this.axiosInstance.get(
|
|
199
|
-
`${this.getEndpoint("orders")}/${orderId}?site_slug=${
|
|
200
|
-
this.settings?.site?.slug
|
|
199
|
+
`${this.getEndpoint("orders")}/${orderId}?site_slug=${this.settings?.site?.slug
|
|
201
200
|
}`,
|
|
202
201
|
);
|
|
203
202
|
return response.data.order || null;
|