startx 0.9.1 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/apps/core-server/package.json +4 -3
  2. package/apps/core-server/src/events/index.ts +19 -19
  3. package/apps/core-server/src/index.ts +1 -3
  4. package/apps/startx-cli/dist/index.mjs +1 -1
  5. package/package.json +1 -1
  6. package/packages/{@repo/db → @db/drizzle}/package.json +1 -1
  7. package/packages/@db/sqlite/package.json +47 -0
  8. package/packages/@db/sqlite/src/index.ts +2 -0
  9. package/packages/@db/sqlite/src/lib/sqlite-client.ts +146 -0
  10. package/packages/@db/sqlite/src/lib/sqlite-convertor.ts +235 -0
  11. package/packages/@db/sqlite/tsconfig.json +13 -0
  12. package/packages/@repo/lib/src/events/i-event.ts +59 -0
  13. package/packages/@repo/lib/src/events/index.ts +1 -0
  14. package/packages/@repo/mail/src/emails/admin/OtpEmail.tsx +1 -1
  15. package/packages/@repo/mail/src/emails/emails.ts +2 -1
  16. package/packages/constants/eslint.config.ts +4 -0
  17. package/packages/{@repo/constants → constants}/package.json +1 -1
  18. package/packages/constants/vitest.config.ts +3 -0
  19. package/pnpm-workspace.yaml +4 -1
  20. /package/packages/{@repo/db → @db/drizzle}/drizzle.config.ts +0 -0
  21. /package/packages/{@repo/constants → @db/drizzle}/eslint.config.ts +0 -0
  22. /package/packages/{@repo/db → @db/drizzle}/src/functions.ts +0 -0
  23. /package/packages/{@repo/db → @db/drizzle}/src/index.ts +0 -0
  24. /package/packages/{@repo/db → @db/drizzle}/src/schema/common.ts +0 -0
  25. /package/packages/{@repo/db → @db/drizzle}/src/schema/index.ts +0 -0
  26. /package/packages/{@repo/db → @db/drizzle}/tsconfig.json +0 -0
  27. /package/packages/{@repo/constants → @db/drizzle}/vitest.config.ts +0 -0
  28. /package/packages/{@repo/db → @db/sqlite}/eslint.config.ts +0 -0
  29. /package/packages/{@repo/db → @db/sqlite}/vitest.config.ts +0 -0
  30. /package/packages/{@repo/constants → constants}/src/api.ts +0 -0
  31. /package/packages/{@repo/constants → constants}/src/index.ts +0 -0
  32. /package/packages/{@repo/constants → constants}/src/time.ts +0 -0
  33. /package/packages/{@repo/constants → constants}/tsconfig.json +0 -0
@@ -20,8 +20,8 @@
20
20
  "test": "vitest run"
21
21
  },
22
22
  "dependencies": {
23
- "@repo/constants": "workspace:^",
24
- "@repo/db": "workspace:^",
23
+ "constants": "workspace:^",
24
+ "@db/drizzle": "workspace:^",
25
25
  "@repo/mail": "workspace:^",
26
26
  "@repo/lib": "workspace:^",
27
27
  "@repo/redis": "workspace:^",
@@ -56,7 +56,8 @@
56
56
  ],
57
57
  "requiredDeps": [
58
58
  "@repo/env",
59
- "@repo/logger"
59
+ "@repo/logger",
60
+ "@repo/lib"
60
61
  ],
61
62
  "requiredDevDeps": [
62
63
  "typescript-config",
@@ -1,37 +1,37 @@
1
- type ServerEvent = {
2
- server: Array<() => void>;
3
- db: Array<() => void>;
4
- redis: Array<() => void>;
1
+ import { IEvent } from "@repo/lib/events";
2
+ import { logger } from "@repo/logger";
3
+ type ServerEventMap = {
4
+ server: string;
5
+ db: string;
6
+ redis: string;
5
7
  };
6
8
 
7
- export class ServerEvents {
8
- static events: ServerEvent = {
9
- server: [],
10
- db: [],
11
- redis: [],
12
- };
9
+ const serverBus = new IEvent<ServerEventMap>();
13
10
 
11
+ export class ServerEvents {
14
12
  static onServerReady(fn: () => void) {
15
- ServerEvents.events.server.push(fn);
13
+ serverBus.on("server", fn);
16
14
  }
17
15
 
18
- static emitServerReady() {
19
- ServerEvents.events.server.forEach(fn => fn());
16
+ static emitServerReady(message?: string) {
17
+ serverBus.emit("server", message ?? "Server ready");
20
18
  }
21
19
 
22
20
  static onDBReady(fn: () => void) {
23
- ServerEvents.events.db.push(fn);
21
+ serverBus.on("db", fn);
24
22
  }
25
23
 
26
- static emitDBReady() {
27
- ServerEvents.events.db.forEach(fn => fn());
24
+ static emitDBReady(message?: string) {
25
+ serverBus.emit("db", message ?? "Database ready");
28
26
  }
29
27
 
30
28
  static onRedisReady(fn: () => void) {
31
- ServerEvents.events.redis.push(fn);
29
+ serverBus.on("redis", fn);
32
30
  }
33
31
 
34
- static emitRedisReady() {
35
- ServerEvents.events.redis.forEach(fn => fn());
32
+ static emitRedisReady(message?: string) {
33
+ serverBus.emit("redis", message ?? "Redis ready");
36
34
  }
37
35
  }
36
+
37
+ serverBus.onEvery(e => logger.info(`${e.event}: ${e.payload}`));
@@ -1,9 +1,7 @@
1
1
  import { ENV } from "@repo/env";
2
- import { logger } from "@repo/logger";
3
2
  import { ServerEvents } from "./events/index.js";
4
3
  import { app } from "./routes/server.js";
5
4
 
6
5
  app.listen(ENV.PORT, () => {
7
- ServerEvents.emitServerReady();
8
- logger.info(`Server started on port ${ENV.PORT}`);
6
+ ServerEvents.emitServerReady(`Server listening on port ${ENV.PORT}`);
9
7
  });
@@ -200,4 +200,4 @@ $&`).replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g,`$1$2`).replace(/\
200
200
  `+t:``),o=Math.floor(n.length/i)-this.cursorPos.rows+(t?Ty(t):0);o>0&&(a+=by(o)),a+=Sy(this.cursorPos.cols),this.write(xy(this.extraLinesUnderPrompt)+wy(this.height)+a),this.extraLinesUnderPrompt=o,this.height=Ty(a)}checkCursorPos(){let e=this.rl.getCursorPos();e.cols!==this.cursorPos.cols&&(this.write(Sy(e.cols)),this.cursorPos=e)}done({clearContent:e}){this.rl.setPrompt(``);let t=xy(this.extraLinesUnderPrompt);t+=e?wy(this.height):`
201
201
  `,t+=`\x1B[?25h`,this.write(t),this.rl.close()}},Oy=class extends Promise{static withResolver(){let e,t;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}}},ky=b(iy(),1);const Ay=globalThis.setImmediate;function jy(){let e=Error.prepareStackTrace,t=[];try{Error.prepareStackTrace=(e,n)=>{let r=n.slice(1);return t=r,r},Error().stack}catch{return t}return Error.prepareStackTrace=e,t}function My(e){let t=jy();return(n,r={})=>{let{input:i=process.stdin,signal:a}=r,o=new Set,c=new ky.default;c.pipe(r.output??process.stdout),c.mute();let l=u.createInterface({terminal:!0,input:i,output:c}),d=new Dy(l),{promise:f,resolve:p,reject:m}=Oy.withResolver(),h=()=>m(new Q_);if(a){let e=()=>m(new Z_({cause:a.reason}));if(a.aborted)return e(),Object.assign(f,{cancel:h});a.addEventListener(`abort`,e),o.add(()=>a.removeEventListener(`abort`,e))}o.add(gy((e,t)=>{m(new $_(`User force closed the prompt with ${e} ${t}`))}));let g=()=>m(new $_(`User force closed the prompt with SIGINT`));return l.on(`SIGINT`,g),o.add(()=>l.removeListener(`SIGINT`,g)),iv(l,a=>{let u=s.bind(()=>uv.clearAll());l.on(`close`,u),o.add(()=>l.removeListener(`close`,u));let g=()=>{let r=()=>d.checkCursorPos();l.input.on(`keypress`,r),o.add(()=>l.input.removeListener(`keypress`,r)),a(()=>{try{let r=e(n,e=>{setImmediate(()=>p(e))});if(r===void 0){let e=t[1]?.getFileName();throw Error(`Prompt functions must return a string.\n at ${e}`)}let[i,a]=typeof r==`string`?[r]:r;d.render(i,a),uv.run()}catch(e){m(e)}})};return`readableFlowing`in i?Ay(g):g(),Object.assign(f.then(e=>(uv.clearAll(),e),e=>{throw uv.clearAll(),e}).finally(()=>{o.forEach(e=>e()),d.done({clearContent:!!r.clearPromptOnDone}),c.end()}).then(()=>f),{cancel:h})})}}var $=class{separator=l(`dim`,Array.from({length:15}).join(vv.line));type=`separator`;constructor(e){e&&(this.separator=e)}static isSeparator(e){return!!(e&&typeof e==`object`&&`type`in e&&e.type===`separator`)}};const Ny={icon:{checked:l(`green`,vv.circleFilled),unchecked:vv.circle,cursor:vv.pointer,disabledChecked:l(`green`,vv.circleDouble),disabledUnchecked:`-`},style:{disabled:e=>l(`dim`,e),renderSelectedChoices:e=>e.map(e=>e.short).join(`, `),description:e=>l(`cyan`,e),keysHelpTip:e=>e.map(([e,t])=>`${l(`bold`,e)} ${l(`dim`,t)}`).join(l(`dim`,` • `))},i18n:{disabledError:`This option is disabled and cannot be toggled.`},keybindings:[]};function Py(e){return!$.isSeparator(e)&&!e.disabled}function Fy(e){return!$.isSeparator(e)}function Iy(e){return!$.isSeparator(e)&&e.checked}function Ly(e){return Py(e)?{...e,checked:!e.checked}:e}function Ry(e){return function(t){return Py(t)?{...t,checked:e}:t}}function zy(e){return e.map(e=>{if($.isSeparator(e))return e;if(typeof e==`string`)return{value:e,name:e,short:e,checkedName:e,disabled:!1,checked:!1};let t=e.name??String(e.value),n={value:e.value,name:t,short:e.short??t,checkedName:e.checkedName??t,disabled:e.disabled??!1,checked:e.checked??!1};return e.description&&(n.description=e.description),n})}var By=My((e,t)=>{let{pageSize:n=7,loop:r=!0,required:i,validate:a=()=>!0}=e,o={all:`a`,invert:`i`,...e.shortcuts},s=Sv(Ny,e.theme),{keybindings:c}=s,[l,u]=Q(`idle`),d=Cv({status:l,theme:s}),[f,p]=Q(zy(e.choices)),m=wv(()=>{let e=f.findIndex(Fy),t=f.findLastIndex(Fy);if(e===-1)throw new tv(`[checkbox prompt] No selectable choices. All choices are disabled.`);return{first:e,last:t}},[f]),[h,g]=Q(m.first),[_,v]=Q();Ev(async e=>{if(X_(e)){let e=f.filter(Iy),n=await a([...e]);i&&!e.length?v(`At least one choice must be selected`):n===!0?(u(`done`),t(e.map(e=>e.value))):v(n||`You must select a valid value`)}else if(W_(e,c)||G_(e,c)){if(_&&v(void 0),r||W_(e,c)&&h!==m.first||G_(e,c)&&h!==m.last){let t=W_(e,c)?-1:1,n=h;do n=(n+t+f.length)%f.length;while(!Fy(f[n]));g(n)}}else if(K_(e)){let e=f[h];e&&!$.isSeparator(e)&&(e.disabled?v(s.i18n.disabledError):(v(void 0),p(f.map((e,t)=>t===h?Ly(e):e))))}else if(e.name===o.all){let e=f.some(e=>Py(e)&&!e.checked);p(f.map(Ry(e)))}else if(e.name===o.invert)p(f.map(Ly));else if(Y_(e)){let t=Number(e.name)-1,n=-1,r=f.findIndex(e=>$.isSeparator(e)?!1:(n++,n===t)),i=f[r];i&&Py(i)&&(g(r),p(f.map((e,t)=>t===r?Ly(e):e)))}});let y=s.style.message(e.message,l),b,x=ry({items:f,active:h,renderItem({item:e,isActive:t}){if($.isSeparator(e))return` ${e.separator}`;let n=t?s.icon.cursor:` `;if(e.disabled){let t=typeof e.disabled==`string`?e.disabled:`(disabled)`,r=e.checked?s.icon.disabledChecked:s.icon.disabledUnchecked;return s.style.disabled(`${n}${r} ${e.name} ${t}`)}t&&(b=e.description);let r=e.checked?s.icon.checked:s.icon.unchecked,i=e.checked?e.checkedName:e.name;return(t?s.style.highlight:e=>e)(`${n}${r} ${i}`)},pageSize:n,loop:r});if(l===`done`){let e=f.filter(Iy);return[d,y,s.style.answer(s.style.renderSelectedChoices(e,f))].filter(Boolean).join(` `)}let S=[[`↑↓`,`navigate`],[`space`,`select`]];o.all&&S.push([o.all,`all`]),o.invert&&S.push([o.invert,`invert`]),S.push([`⏎`,`submit`]);let C=s.style.keysHelpTip(S);return`${[[d,y].filter(Boolean).join(` `),x,` `,b?s.style.description(b):``,_?s.style.error(_):``,C].filter(Boolean).join(`
202
202
  `).trimEnd()}${yy}`});const Vy={validationFailureMode:`keep`};var Hy=My((e,t)=>{let{prefill:n=`tab`}=e,r=Sv(Vy,e.theme),[i,a]=Q(`idle`),[o,s]=Q(String(e.default??``)),[c,l]=Q(),[u,d]=Q(``),f=Cv({status:i,theme:r});async function p(t){let{required:n,pattern:r,patternError:i=`Invalid input`}=e;return n&&!t?`You must provide a value`:r&&!r.test(t)?i:typeof e.validate==`function`?await e.validate(t)||`You must provide a valid value`:!0}Ev(async(e,n)=>{if(i===`idle`)if(X_(e)){let e=u||o;a(`loading`);let i=await p(e);i===!0?(d(e),a(`done`),t(e)):(r.validationFailureMode===`clear`?d(``):n.write(u),l(i),a(`idle`))}else q_(e)&&!u?s(``):J_(e)&&!u?(s(``),n.clearLine(0),n.write(o),d(o)):(d(n.line),l(void 0))}),dv(e=>{n===`editable`&&o&&(e.write(o),d(o))},[]);let m=r.style.message(e.message,i),h=u;typeof e.transformer==`function`?h=e.transformer(u,{isFinal:i===`done`}):i===`done`&&(h=r.style.answer(u));let g;o&&i!==`done`&&!u&&(g=r.style.defaultAnswer(o));let _=``;return c&&(_=r.style.error(c)),[[f,m,g,h].filter(e=>e!==void 0).join(` `),_]});const Uy={icon:{cursor:vv.pointer},style:{disabled:e=>l(`dim`,e),description:e=>l(`cyan`,e),keysHelpTip:e=>e.map(([e,t])=>`${l(`bold`,e)} ${l(`dim`,t)}`).join(l(`dim`,` • `))},i18n:{disabledError:`This option is disabled and cannot be selected.`},indexMode:`hidden`,keybindings:[]};function Wy(e){return!$.isSeparator(e)&&!e.disabled}function Gy(e){return!$.isSeparator(e)}function Ky(e){return e.map(e=>{if($.isSeparator(e))return e;if(typeof e!=`object`||!e||!(`value`in e)){let t=String(e);return{value:e,name:t,short:t,disabled:!1}}let t=e.name??String(e.value),n={value:e.value,name:t,short:e.short??t,disabled:e.disabled??!1};return e.description&&(n.description=e.description),n})}var qy=My((e,t)=>{let{loop:n=!0,pageSize:r=7}=e,i=Sv(Uy,e.theme),{keybindings:a}=i,[o,s]=Q(`idle`),c=Cv({status:o,theme:i}),l=Tv(),u=!a.includes(`vim`),d=wv(()=>Ky(e.choices),[e.choices]),f=wv(()=>{let e=d.findIndex(Gy),t=d.findLastIndex(Gy);if(e===-1)throw new tv(`[select prompt] No selectable choices. All choices are disabled.`);return{first:e,last:t}},[d]),p=wv(()=>`default`in e?d.findIndex(t=>Wy(t)&&t.value===e.default):-1,[e.default,d]),[m,h]=Q(p===-1?f.first:p),g=d[m],[_,v]=Q();Ev((e,r)=>{if(clearTimeout(l.current),_&&v(void 0),X_(e))g.disabled?v(i.i18n.disabledError):(s(`done`),t(g.value));else if(W_(e,a)||G_(e,a)){if(r.clearLine(0),n||W_(e,a)&&m!==f.first||G_(e,a)&&m!==f.last){let t=W_(e,a)?-1:1,n=m;do n=(n+t+d.length)%d.length;while(!Gy(d[n]));h(n)}}else if(Y_(e)&&!Number.isNaN(Number(r.line))){let e=Number(r.line)-1,t=-1,n=d.findIndex(n=>$.isSeparator(n)?!1:(t++,t===e)),i=d[n];i!=null&&Wy(i)&&h(n),l.current=setTimeout(()=>{r.clearLine(0)},700)}else if(q_(e))r.clearLine(0);else if(u){let e=r.line.toLowerCase(),t=d.findIndex(t=>$.isSeparator(t)||!Wy(t)?!1:t.name.toLowerCase().startsWith(e));t!==-1&&h(t),l.current=setTimeout(()=>{r.clearLine(0)},700)}}),dv(()=>()=>{clearTimeout(l.current)},[]);let y=i.style.message(e.message,o),b=i.style.keysHelpTip([[`↑↓`,`navigate`],[`⏎`,`select`]]),x=0,S=ry({items:d,active:m,renderItem({item:e,isActive:t,index:n}){if($.isSeparator(e))return x++,` ${e.separator}`;let r=t?i.icon.cursor:` `,a=i.indexMode===`number`?`${n+1-x}. `:``;if(e.disabled){let n=typeof e.disabled==`string`?e.disabled:`(disabled)`,r=t?i.icon.cursor:`-`;return i.style.disabled(`${r} ${a}${e.name} ${n}`)}return(t?i.style.highlight:e=>e)(`${r} ${a}${e.name}`)},pageSize:r,loop:n});if(o===`done`)return[c,y,i.style.answer(g.short)].filter(Boolean).join(` `);let{description:C}=g;return`${[[c,y].filter(Boolean).join(` `),S,` `,C?i.style.description(C):``,_?i.style.error(_):``,b].filter(Boolean).join(`
203
- `).trimEnd()}${yy}`}),Jy=class{static async getText(e){let{message:t,schema:n,default:r}=e,i;if(n&&r!==void 0){let e=n.safeParse(r);e.success&&(i=e.data)}else !n&&r!==void 0&&(i=r);if(n&&n instanceof Af)return await qy({message:t,choices:n.options.map(e=>({value:e})),default:i});let a=await Hy({message:t,default:i,validate:e=>{if(!n)return!0;let t=e;if(n instanceof Dd){if(e.trim()===``)return`Input cannot be empty`;let n=Number(e);if(Number.isNaN(n))return`Please enter a valid number`;t=n}let r=n.safeParse(t);return r.success?!0:r.error.issues[0]?.message??`Invalid input`}});if(!n)return a;let o=n instanceof Dd?Number(a):a;return n.parse(o)}static async choose(e){let{message:t,options:n,mode:r=`single`,default:i,includeAllOption:a=!1,required:o=!1}=e,s=`__all__`,c=r===`single`?[i]:[...i||[]],l=[...r===`multiple`&&a?[{name:`All`,value:s,checked:c.includes(s)}]:[],...n.map(e=>({name:e,value:e,checked:c.includes(e)}))];if(r===`multiple`){let e=await By({message:t,choices:l,validate:e=>o&&e.length===0?`You must select at least one option.`:!0});return a&&e.includes(s)?n:e}return await qy({message:t,choices:n.map(e=>({name:e,value:e})),default:typeof i==`string`?i:void 0})}},Yy=class e{static command=new hg(`init`).argument(`[projectName]`).option(`-d, --dir <path>`,`workspace directory`).action(e.run.bind(e));static async run(e,t){let n=await z_.getPackageList(),r=n.filter(e=>e.type===`apps`&&e.packageJson?.startx?.mode!==`silent`),i=await this.getPrefs({projectName:e,options:t,projects:r}),a=n.filter(e=>e.type!==`apps`),o=await this.getConfigPrefs({selectedApps:i.selectedApps,packages:a}),s=await this.getPackagesPrefs({selectedPackages:o.selectedConfigs,packages:a,tags:o.gTags}),c=[...s.gTags,`runnable`];await this.installWorkspace({name:i.projectName,tags:[...c,`runnable`],dir:i.directory});let l=[...s.selectedPackages,...i.selectedApps];await Promise.all(l.map(async e=>{let t={},n=new Set(s.gTags);e.packageJson?.startx?.mode===`standalone`&&n.add(`runnable`),e.type===`apps`&&(n.add(`runnable`),s.selectedPackages.filter(t=>t.type!==`packages`||t.packageJson?.startx?.mode===`standalone`?!1:t.packageJson?.startx?.iTags?.every(t=>e.packageJson?.startx?.gTags?.includes(t))).forEach(e=>{let n=e.packageJson?.name||e.name;t[n]=`workspace:^`})),await this.installPackage({pkg:e,directory:i.directory,tags:Array.from(n),dependencies:t})}))}static async getPrefs(e){let n=await Jy.getText({message:`Project name`,name:`projectName`,default:e.projectName,schema:Gp.string().min(1,`Package name is required`).max(214,`Package name too long`).regex(/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/,`Invalid package name`)});if(e.projects.length===0)throw Error(`No apps found to install.`);let r=z_.getDirectory(),i=e.options.dir?t.resolve(r.workspace,e.options.dir):t.join(r.workspace,n),a=await Jy.choose({message:`Select apps to install`,options:e.projects.map(e=>e.name),includeAllOption:!0,mode:`multiple`,required:!0});return{projectName:n,directory:{workspace:i,template:r.template},selectedApps:e.projects.filter(e=>a.includes(e.name))}}static async getConfigPrefs(e){let t=new Set([`common`]),n=new Map;this.getGlobalTags({pkgs:e.selectedApps}).forEach(e=>t.add(e)),this.getPackageDeps({allPkgs:e.packages,pkgs:e.selectedApps}).forEach(e=>n.set(e.name,e));let r=e.packages.filter(e=>e.type!==`configs`||e.packageJson?.startx?.mode===`silent`||n.has(e.name)?!1:e.packageJson?.startx?.iTags?.every(e=>t.has(e))??!0);if(r.length>0){let e=await Jy.choose({message:`Select configs to install`,options:r.map(e=>e.name),includeAllOption:!0,mode:`multiple`,required:!1});r.filter(t=>e.includes(t.name)).forEach(e=>n.set(e.name,e))}return t.has(`node`)&&(await Jy.choose({message:`Select formatter`,options:[`prettier + biome`,`prettier`],mode:`single`,default:`prettier`,required:!0})===`prettier`||t.add(`biome`),t.add(`prettier`)),this.getPackageDeps({allPkgs:e.packages,pkgs:Array.from(n.values())}).forEach(e=>n.set(e.name,e)),this.getGlobalTags({pkgs:Array.from(n.values())}).forEach(e=>t.add(e)),{gTags:Array.from(t),selectedConfigs:Array.from(n.values())}}static async getPackagesPrefs(e){let t=new Set(e.tags),n=new Map(e.selectedPackages.map(e=>[e.name,e])),r=e.packages.filter(e=>e.type!==`packages`||e.packageJson?.startx?.mode===`silent`||n.has(e.name)?!1:e.packageJson?.startx?.iTags?.every(e=>t.has(e))??!1);if(r.length>0){let e=await Jy.choose({message:`Select packages to install`,options:r.map(e=>e.name),includeAllOption:!0,mode:`multiple`,required:!1});r.filter(t=>e.includes(t.name)).forEach(e=>n.set(e.name,e))}return this.getPackageDeps({allPkgs:e.packages,pkgs:Array.from(n.values())}).forEach(e=>n.set(e.name,e)),this.getGlobalTags({pkgs:Array.from(n.values())}).forEach(e=>t.add(e)),{gTags:Array.from(t),selectedPackages:Array.from(n.values())}}static async installPackage(e){if(!e.pkg.packageJson)throw Error(`Missing package.json for ${e.pkg.name}`);let n=new Set([...e.tags,...e.pkg.packageJson.startx?.tags||[]]),r=e.pkg.packageJson.startx?.ignore||[];r.includes(`eslint-config`)&&n.delete(`eslint`),r.includes(`vitest-config`)&&n.delete(`vitest`);let{packageJson:i,isWorkspace:a}=U_.handlePackageJson({app:e.pkg.packageJson,tags:Array.from(n),name:e.pkg.name,dependencies:e.dependencies});if(a)throw Error(`Cannot install workspace as a package: ${e.pkg.name}`);let o=t.join(e.directory.workspace,e.pkg.relativePath),s=t.join(e.pkg.path);await I_.writeJSONFile({dir:o,file:`package`,content:i}),await this.copyValidatedFilesFromFolder(s,o,n),await I_.copyDirectory({from:t.join(s,`src`),to:t.join(o,`src`),exclude:n.has(`vitest`)?void 0:/\.test\.tsx?$/}),tg.info(`Successfully installed ${e.pkg.name}`)}static async installWorkspace(e){let t=await z_.parsePackageJson({dir:e.dir.template}),n=await z_.parsePackageJson({dir:e.dir.template,file:`startx`});if(!t)throw Error(`Failed to parse root package.json`);t.dependencies={...t.dependencies,...n?.dependencies||{}},t.devDependencies={...t.devDependencies,...n?.devDependencies||{}};let{packageJson:r}=U_.handlePackageJson({app:t,tags:[`root`,...e.tags],name:e.name});await I_.writeJSONFile({dir:e.dir.workspace,file:`package`,content:r}),await this.copyValidatedFilesFromFolder(e.dir.template,e.dir.workspace,new Set([`root`,...e.tags]))}static getPackageDeps(e){let t=new Map(e.pkgs.map(e=>[e.name,e]));return Array.from(t.values()).forEach(n=>{[...n.packageJson?.startx?.requiredDeps||[],...n.packageJson?.startx?.requiredDevDeps||[]].forEach(n=>{let r=e.allPkgs.find(e=>e.packageJson?.name===n);r&&t.set(r.name,r)})}),e.pkgs.forEach(e=>t.delete(e.name)),Array.from(t.values())}static getGlobalTags(e){let t=new Set(e.gTags||[]);return e.pkgs.forEach(e=>{e.packageJson?.startx?.gTags?.forEach(e=>t.add(e))}),Array.from(t)}static async copyValidatedFilesFromFolder(e,n,r){let i=await I_.listFiles({dir:e}).catch(()=>[]);for(let a of i){let i=L_[a];if(!(i&&!i.tags.every(e=>r.has(e))))try{await I_.copyFile({from:t.join(e,a),to:t.join(n,a)})}catch(e){tg.error(`Failed to copy file ${a}:`,e)}}}},Xy=`0.9.1`;const Zy=new hg;Zy.name(`startx`).description(`StartX CLI - Your all in one monorepo startup tool.`).version(Xy),Zy.command(`ping`).action(()=>{tg.info(`pong`)}),Zy.addCommand(Yy.command),Zy.parse(process.argv);export{};
203
+ `).trimEnd()}${yy}`}),Jy=class{static async getText(e){let{message:t,schema:n,default:r}=e,i;if(n&&r!==void 0){let e=n.safeParse(r);e.success&&(i=e.data)}else !n&&r!==void 0&&(i=r);if(n&&n instanceof Af)return await qy({message:t,choices:n.options.map(e=>({value:e})),default:i});let a=await Hy({message:t,default:i,validate:e=>{if(!n)return!0;let t=e;if(n instanceof Dd){if(e.trim()===``)return`Input cannot be empty`;let n=Number(e);if(Number.isNaN(n))return`Please enter a valid number`;t=n}let r=n.safeParse(t);return r.success?!0:r.error.issues[0]?.message??`Invalid input`}});if(!n)return a;let o=n instanceof Dd?Number(a):a;return n.parse(o)}static async choose(e){let{message:t,options:n,mode:r=`single`,default:i,includeAllOption:a=!1,required:o=!1}=e,s=`__all__`,c=r===`single`?[i]:[...i||[]],l=[...r===`multiple`&&a?[{name:`All`,value:s,checked:c.includes(s)}]:[],...n.map(e=>({name:e,value:e,checked:c.includes(e)}))];if(r===`multiple`){let e=await By({message:t,choices:l,validate:e=>o&&e.length===0?`You must select at least one option.`:!0});return a&&e.includes(s)?n:e}return await qy({message:t,choices:n.map(e=>({name:e,value:e})),default:typeof i==`string`?i:void 0})}},Yy=class e{static command=new hg(`init`).argument(`[projectName]`).option(`-d, --dir <path>`,`workspace directory`).action(e.run.bind(e));static async run(e,t){let n=await z_.getPackageList(),r=n.filter(e=>e.type===`apps`&&e.packageJson?.startx?.mode!==`silent`),i=await this.getPrefs({projectName:e,options:t,projects:r}),a=n.filter(e=>e.type!==`apps`),o=await this.getConfigPrefs({selectedApps:i.selectedApps,packages:a}),s=await this.getPackagesPrefs({selectedPackages:o.selectedConfigs,packages:a,tags:o.gTags}),c=[...s.gTags,`runnable`];await this.installWorkspace({name:i.projectName,tags:[...c,`runnable`],dir:i.directory});let l=[...s.selectedPackages,...i.selectedApps];await Promise.all(l.map(async e=>{let t={},n=new Set(s.gTags);e.packageJson?.startx?.mode===`standalone`&&n.add(`runnable`),e.type===`apps`&&(n.add(`runnable`),s.selectedPackages.filter(t=>t.type!==`packages`||t.packageJson?.startx?.mode===`standalone`?!1:t.packageJson?.startx?.iTags?.every(t=>e.packageJson?.startx?.gTags?.includes(t))).forEach(e=>{let n=e.packageJson?.name||e.name;t[n]=`workspace:^`})),await this.installPackage({pkg:e,directory:i.directory,tags:Array.from(n),dependencies:t})}))}static async getPrefs(e){let n=await Jy.getText({message:`Project name`,name:`projectName`,default:e.projectName,schema:Gp.string().min(1,`Package name is required`).max(214,`Package name too long`).regex(/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/,`Invalid package name`)});if(e.projects.length===0)throw Error(`No apps found to install.`);let r=z_.getDirectory(),i=e.options.dir?t.resolve(r.workspace,e.options.dir):t.join(r.workspace,n),a=await Jy.choose({message:`Select apps to install`,options:e.projects.map(e=>e.name),includeAllOption:!0,mode:`multiple`,required:!0});return{projectName:n,directory:{workspace:i,template:r.template},selectedApps:e.projects.filter(e=>a.includes(e.name))}}static async getConfigPrefs(e){let t=new Set([`common`]),n=new Map;this.getGlobalTags({pkgs:e.selectedApps}).forEach(e=>t.add(e)),this.getPackageDeps({allPkgs:e.packages,pkgs:e.selectedApps}).forEach(e=>n.set(e.name,e));let r=e.packages.filter(e=>e.type!==`configs`||e.packageJson?.startx?.mode===`silent`||n.has(e.name)?!1:e.packageJson?.startx?.iTags?.every(e=>t.has(e))??!0);if(r.length>0){let e=await Jy.choose({message:`Select configs to install`,options:r.map(e=>e.name),includeAllOption:!0,mode:`multiple`,required:!1});r.filter(t=>e.includes(t.name)).forEach(e=>n.set(e.name,e))}return t.has(`node`)&&(await Jy.choose({message:`Select formatter`,options:[`prettier + biome`,`prettier`],mode:`single`,default:`prettier`,required:!0})===`prettier`||t.add(`biome`),t.add(`prettier`)),this.getPackageDeps({allPkgs:e.packages,pkgs:Array.from(n.values())}).forEach(e=>n.set(e.name,e)),this.getGlobalTags({pkgs:Array.from(n.values())}).forEach(e=>t.add(e)),{gTags:Array.from(t),selectedConfigs:Array.from(n.values())}}static async getPackagesPrefs(e){let t=new Set(e.tags),n=new Map(e.selectedPackages.map(e=>[e.name,e])),r=e.packages.filter(e=>e.type!==`packages`||e.packageJson?.startx?.mode===`silent`||n.has(e.name)?!1:e.packageJson?.startx?.iTags?.every(e=>t.has(e))??!1);if(r.length>0){let e=await Jy.choose({message:`Select packages to install`,options:r.map(e=>e.name),includeAllOption:!0,mode:`multiple`,required:!1});r.filter(t=>e.includes(t.name)).forEach(e=>n.set(e.name,e))}return this.getPackageDeps({allPkgs:e.packages,pkgs:Array.from(n.values())}).forEach(e=>n.set(e.name,e)),this.getGlobalTags({pkgs:Array.from(n.values())}).forEach(e=>t.add(e)),{gTags:Array.from(t),selectedPackages:Array.from(n.values())}}static async installPackage(e){if(!e.pkg.packageJson)throw Error(`Missing package.json for ${e.pkg.name}`);let n=new Set([...e.tags,...e.pkg.packageJson.startx?.tags||[]]),r=e.pkg.packageJson.startx?.ignore||[];r.includes(`eslint-config`)&&n.delete(`eslint`),r.includes(`vitest-config`)&&n.delete(`vitest`);let{packageJson:i,isWorkspace:a}=U_.handlePackageJson({app:e.pkg.packageJson,tags:Array.from(n),name:e.pkg.name,dependencies:e.dependencies});if(a)throw Error(`Cannot install workspace as a package: ${e.pkg.name}`);let o=t.join(e.directory.workspace,e.pkg.relativePath),s=t.join(e.pkg.path);await I_.writeJSONFile({dir:o,file:`package`,content:i}),await this.copyValidatedFilesFromFolder(s,o,n),await I_.copyDirectory({from:t.join(s,`src`),to:t.join(o,`src`),exclude:n.has(`vitest`)?void 0:/\.test\.tsx?$/}),tg.info(`Successfully installed ${e.pkg.name}`)}static async installWorkspace(e){let t=await z_.parsePackageJson({dir:e.dir.template}),n=await z_.parsePackageJson({dir:e.dir.template,file:`startx`});if(!t)throw Error(`Failed to parse root package.json`);t.dependencies={...t.dependencies,...n?.dependencies||{}},t.devDependencies={...t.devDependencies,...n?.devDependencies||{}};let{packageJson:r}=U_.handlePackageJson({app:t,tags:[`root`,...e.tags],name:e.name});await I_.writeJSONFile({dir:e.dir.workspace,file:`package`,content:r}),await this.copyValidatedFilesFromFolder(e.dir.template,e.dir.workspace,new Set([`root`,...e.tags]))}static getPackageDeps(e){let t=new Map(e.pkgs.map(e=>[e.name,e]));return Array.from(t.values()).forEach(n=>{[...n.packageJson?.startx?.requiredDeps||[],...n.packageJson?.startx?.requiredDevDeps||[]].forEach(n=>{let r=e.allPkgs.find(e=>e.packageJson?.name===n);r&&t.set(r.name,r)})}),e.pkgs.forEach(e=>t.delete(e.name)),Array.from(t.values())}static getGlobalTags(e){let t=new Set(e.gTags||[]);return e.pkgs.forEach(e=>{e.packageJson?.startx?.gTags?.forEach(e=>t.add(e))}),Array.from(t)}static async copyValidatedFilesFromFolder(e,n,r){let i=await I_.listFiles({dir:e}).catch(()=>[]);for(let a of i){let i=L_[a];if(!(i&&!i.tags.every(e=>r.has(e))))try{await I_.copyFile({from:t.join(e,a),to:t.join(n,a)})}catch(e){tg.error(`Failed to copy file ${a}:`,e)}}}},Xy=`0.9.4`;const Zy=new hg;Zy.name(`startx`).description(`StartX CLI - Your all in one monorepo startup tool.`).version(Xy),Zy.command(`ping`).action(()=>{tg.info(`pong`)}),Zy.addCommand(Yy.command),Zy.parse(process.argv);export{};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "startx",
3
3
  "description": "",
4
- "version": "0.9.1",
4
+ "version": "0.9.4",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/avinashid/startx.git"
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "@repo/db",
2
+ "name": "@db/drizzle",
3
3
  "version": "0.0.0",
4
4
  "private": true,
5
5
  "type": "module",
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@db/sqlite",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": "./src/index.ts",
7
+ "scripts": {
8
+ "clean": "rimraf dist .turbo",
9
+ "watch:dev": "pnpm watch",
10
+ "typecheck": "tsc --noEmit",
11
+ "format": "biome format --write .",
12
+ "format:check": "biome ci .",
13
+ "lint": "eslint .",
14
+ "lint:fix": "eslint . --fix",
15
+ "watch": "tsc -p tsconfig.build.json --watch"
16
+ },
17
+ "devDependencies": {
18
+ "typescript-config": "workspace:*",
19
+ "vitest-config": "workspace:*",
20
+ "@types/better-sqlite3": "catalog:",
21
+ "eslint-config": "workspace:*"
22
+ },
23
+ "dependencies": {
24
+ "better-sqlite3": "catalog:",
25
+ "xlsx": "catalog:",
26
+ "@repo/env": "workspace:*",
27
+ "@repo/logger": "workspace:*"
28
+ },
29
+ "startx": {
30
+ "iTags": [
31
+ "node",
32
+ "backend"
33
+ ],
34
+ "gTags": [
35
+ "db"
36
+ ],
37
+ "tags": [
38
+ "sqlite"
39
+ ],
40
+ "requiredDeps": [
41
+ "@repo/logger"
42
+ ],
43
+ "requiredDevDeps": [
44
+ "typescript-config"
45
+ ]
46
+ }
47
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./lib/sqlite-client.js";
2
+ export * from "./lib/sqlite-convertor.js";
@@ -0,0 +1,146 @@
1
+ import { logger } from "@repo/logger";
2
+ import Database from "better-sqlite3";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ class SqliteManager {
11
+ private static connections: Map<string, Database.Database> = new Map();
12
+
13
+ static getDb(dbPath: string): Database.Database {
14
+ const resolvedPath = path.resolve(dbPath);
15
+
16
+ if (!this.connections.has(resolvedPath)) {
17
+ const dir = path.dirname(resolvedPath);
18
+ if (!fs.existsSync(dir)) {
19
+ fs.mkdirSync(dir, { recursive: true });
20
+ }
21
+
22
+ const db = new Database(resolvedPath);
23
+ db.pragma("journal_mode = WAL");
24
+ db.pragma("synchronous = NORMAL");
25
+ logger.info(`Connected to SQLite database at ${resolvedPath}`);
26
+ this.connections.set(resolvedPath, db);
27
+ }
28
+ return this.connections.get(resolvedPath)!;
29
+ }
30
+
31
+ static closeDb(dbPath: string): void {
32
+ const resolvedPath = path.resolve(dbPath);
33
+ const db = this.connections.get(resolvedPath);
34
+
35
+ if (db) {
36
+ db.close();
37
+ this.connections.delete(resolvedPath);
38
+ logger.warn(`SQLite connection for ${resolvedPath} closed.`);
39
+ }
40
+ }
41
+ }
42
+
43
+ interface TableInfoRow {
44
+ cid: number;
45
+ name: string;
46
+ type: string;
47
+ notnull: number;
48
+ dflt_value: string | null;
49
+ pk: number;
50
+ }
51
+
52
+ export class SqliteModule {
53
+ public db: Database.Database;
54
+ private dbPath: string;
55
+
56
+ constructor(dbPath: string = path.resolve(__dirname, "db-files", "app.db")) {
57
+ this.dbPath = path.resolve(dbPath);
58
+ this.db = SqliteManager.getDb(this.dbPath);
59
+ }
60
+
61
+ private escapeId(identifier: string): string {
62
+ return `"${identifier.replace(/"/g, '""')}"`;
63
+ }
64
+
65
+ createTable(tableName: string, schema: Record<string, string>): void {
66
+ const safeTable = this.escapeId(tableName);
67
+ const columns = Object.entries(schema)
68
+ .map(([col, type]) => `${this.escapeId(col)} ${type}`)
69
+ .join(", ");
70
+
71
+ const sql = `CREATE TABLE IF NOT EXISTS ${safeTable} (${columns});`;
72
+ this.db.prepare(sql).run();
73
+ logger.info(`Table '${tableName}' is ready.`);
74
+ }
75
+
76
+ insert(tableName: string, row: Record<string, any>): void {
77
+ const safeTable = this.escapeId(tableName);
78
+ const cols = Object.keys(row)
79
+ .map(c => this.escapeId(c))
80
+ .join(", ");
81
+ const placeholders = Object.keys(row)
82
+ .map(() => "?")
83
+ .join(", ");
84
+ const values = Object.values(row);
85
+
86
+ const sql = `INSERT INTO ${safeTable} (${cols}) VALUES (${placeholders});`;
87
+ this.db.prepare(sql).run(...values);
88
+ }
89
+
90
+ readAll<T = unknown>(tableName: string): T[] {
91
+ const safeTable = this.escapeId(tableName);
92
+ return this.db.prepare(`SELECT * FROM ${safeTable};`).all() as T[];
93
+ }
94
+
95
+ readOne<T = unknown>(tableName: string, where: string, params: any[] = []): T | undefined {
96
+ const safeTable = this.escapeId(tableName);
97
+ return this.db.prepare(`SELECT * FROM ${safeTable} WHERE ${where} LIMIT 1;`).get(...params) as T | undefined;
98
+ }
99
+
100
+ update(tableName: string, updates: Record<string, any>, where: string, params: any[] = []): void {
101
+ const safeTable = this.escapeId(tableName);
102
+ const setClause = Object.keys(updates)
103
+ .map(k => `${this.escapeId(k)} = ?`)
104
+ .join(", ");
105
+ const values = [...Object.values(updates), ...params];
106
+
107
+ const sql = `UPDATE ${safeTable} SET ${setClause} WHERE ${where};`;
108
+ this.db.prepare(sql).run(...values);
109
+ }
110
+
111
+ delete(tableName: string, where: string, params: any[] = []): void {
112
+ const safeTable = this.escapeId(tableName);
113
+ const sql = `DELETE FROM ${safeTable} WHERE ${where};`;
114
+ this.db.prepare(sql).run(...params);
115
+ }
116
+
117
+ listTables(): string[] {
118
+ const rows = this.db
119
+ .prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';`)
120
+ .all() as Array<{ name: string }>;
121
+ return rows.map(r => r.name);
122
+ }
123
+
124
+ getTableSchema(tableName: string): string | string[] {
125
+ const exists = this.db
126
+ .prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1;`)
127
+ .get(tableName);
128
+
129
+ if (!exists) {
130
+ logger.warn(`Table not found when fetching schema.`);
131
+ return [];
132
+ }
133
+
134
+ const safeName = tableName.replace(/"/g, '""');
135
+ const rows = this.db.prepare(`PRAGMA table_info("${safeName}");`).all() as TableInfoRow[];
136
+
137
+ const header = ["cid", "name", "type", "notnull", "default_value", "primary_key"];
138
+ const content = rows.map(r => `${r.cid} ${r.name} ${r.type} ${!!r.notnull} ${r.dflt_value} ${!!r.pk}`);
139
+
140
+ return [header.join(" "), ...content].join("\n");
141
+ }
142
+
143
+ close(): void {
144
+ SqliteManager.closeDb(this.dbPath);
145
+ }
146
+ }
@@ -0,0 +1,235 @@
1
+ import fs from "node:fs";
2
+ import * as XLSX from "xlsx";
3
+ import { SqliteModule } from "./sqlite-client.js";
4
+
5
+ export type InferredKind = "INTEGER" | "REAL" | "BOOLEAN" | "DATE" | "DATETIME" | "TEXT";
6
+
7
+ export interface ColumnDef {
8
+ name: string;
9
+ original: string;
10
+ kind: InferredKind;
11
+ }
12
+
13
+ const snake = (s: string) =>
14
+ s
15
+ .trim()
16
+ .replace(/\s+/g, "_")
17
+ .replace(/[^A-Za-z0-9_]/g, "_")
18
+ .replace(/__+/g, "_")
19
+ .replace(/^_+|_+$/g, "")
20
+ .toLowerCase();
21
+
22
+ function uniqueNames(headers: string[]): string[] {
23
+ const used = new Map<string, number>();
24
+ return headers.map(h => {
25
+ let base = snake(h || "col");
26
+ if (!base) base = "col";
27
+ let name = base;
28
+ let i = 1;
29
+ while (used.has(name)) {
30
+ name = `${base}_${++i}`;
31
+ }
32
+ used.set(name, 1);
33
+ return name;
34
+ });
35
+ }
36
+
37
+ function isBooleanish(v: unknown): boolean {
38
+ if (typeof v === "boolean") return true;
39
+ if (typeof v === "number") return v === 0 || v === 1;
40
+ if (typeof v === "string") {
41
+ const s = v.trim().toLowerCase();
42
+ return ["true", "false", "yes", "no", "y", "n", "0", "1"].includes(s);
43
+ }
44
+ return false;
45
+ }
46
+
47
+ function toBoolean(v: unknown): boolean | null {
48
+ if (v == null || v === "") return null;
49
+ if (typeof v === "boolean") return v;
50
+ if (typeof v === "number") return v !== 0;
51
+ if (typeof v === "string") {
52
+ const s = v.trim().toLowerCase();
53
+ return ["true", "yes", "y", "1"].includes(s) ? true : ["false", "no", "n", "0"].includes(s) ? false : null;
54
+ }
55
+ return null;
56
+ }
57
+
58
+ function isExcelDate(d: unknown): d is Date {
59
+ return d instanceof Date && !isNaN(d.getTime());
60
+ }
61
+
62
+ function looksLikeDateString(s: string): boolean {
63
+ return (
64
+ /^(\d{4}-\d{2}-\d{2})(?:[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?)?$/.test(s) ||
65
+ /^(\d{1,2})[\/-](\d{1,2})[\/-](\d{2,4})(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(s)
66
+ );
67
+ }
68
+
69
+ function parseDateFlexible(v: unknown): Date | null {
70
+ if (v == null || v === "") return null;
71
+ if (isExcelDate(v)) return v;
72
+ if (typeof v === "number") {
73
+ if (v > 1e12) return new Date(v);
74
+ if (v > 1e9) return new Date(v * 1000);
75
+ const excelEpoch = new Date(Date.UTC(1899, 11, 30));
76
+ const ms = excelEpoch.getTime() + v * 24 * 60 * 60 * 1000;
77
+ const d = new Date(ms);
78
+ return isNaN(d.getTime()) ? null : d;
79
+ }
80
+ if (typeof v === "string" && looksLikeDateString(v.trim())) {
81
+ const d = new Date(v);
82
+ return isNaN(d.getTime()) ? null : d;
83
+ }
84
+ return null;
85
+ }
86
+
87
+ function inferKind(values: unknown[]): InferredKind {
88
+ const nonNull = values.filter(v => v !== undefined && v !== null && v !== "");
89
+ if (nonNull.length === 0) return "TEXT";
90
+
91
+ const allDates = nonNull.every(v => isExcelDate(v) || parseDateFlexible(v) !== null);
92
+ if (allDates) {
93
+ const anyHasTime = nonNull.some(v => {
94
+ const d = isExcelDate(v) ? v : parseDateFlexible(v);
95
+ if (!d) return false;
96
+ return d.getUTCHours() !== 0 || d.getUTCMinutes() !== 0 || d.getUTCSeconds() !== 0;
97
+ });
98
+ return anyHasTime ? "DATETIME" : "DATE";
99
+ }
100
+
101
+ const allBool = nonNull.every(isBooleanish);
102
+ if (allBool) return "BOOLEAN";
103
+
104
+ const allNumbers = nonNull.every(v => typeof v === "number" || (!isNaN(Number(v)) && String(v).trim() !== ""));
105
+ if (allNumbers) {
106
+ const anyFloat = nonNull.some(v => String(v).includes("."));
107
+ return anyFloat ? "REAL" : "INTEGER";
108
+ }
109
+
110
+ return "TEXT";
111
+ }
112
+
113
+ function coerceValue(kind: InferredKind, v: unknown, dateAs: "TEXT" | "INTEGER" | "REAL"): unknown {
114
+ if (v === undefined || v === null || v === "") return null;
115
+ switch (kind) {
116
+ case "BOOLEAN": {
117
+ const b = toBoolean(v);
118
+ return b === null ? null : b ? 1 : 0;
119
+ }
120
+ case "INTEGER": {
121
+ const n = Number(v);
122
+ return isFinite(n) ? Math.trunc(n) : null;
123
+ }
124
+ case "REAL": {
125
+ const n = Number(v);
126
+ return isFinite(n) ? n : null;
127
+ }
128
+ case "DATE":
129
+ case "DATETIME": {
130
+ const d = isExcelDate(v) ? v : parseDateFlexible(v);
131
+ if (!d) return null;
132
+ if (dateAs === "TEXT") return kind === "DATE" ? d.toISOString().slice(0, 10) : d.toISOString();
133
+ if (dateAs === "INTEGER") return Math.floor(d.getTime() / 1000);
134
+ return d.getTime();
135
+ }
136
+ default:
137
+ return JSON.stringify(v);
138
+ }
139
+ }
140
+
141
+ export class ExcelToSqlite {
142
+ static importSheetFromFile(
143
+ filePath: string,
144
+ dbPath: string,
145
+ tableName: string,
146
+ sheetName?: string,
147
+ drop = false,
148
+ headerRow = 1,
149
+ dateAs: "TEXT" | "INTEGER" | "REAL" = "TEXT"
150
+ ) {
151
+ const workbook = XLSX.read(fs.readFileSync(filePath), {
152
+ type: "buffer",
153
+ cellDates: true,
154
+ raw: false,
155
+ });
156
+ return this.importSheetFromWorkbook(workbook, dbPath, tableName, sheetName, drop, headerRow, dateAs);
157
+ }
158
+
159
+ static importSheetFromBuffer(
160
+ buffer: Buffer,
161
+ dbPath: string,
162
+ tableName: string,
163
+ sheetName?: string,
164
+ drop = false,
165
+ headerRow = 1,
166
+ dateAs: "TEXT" | "INTEGER" | "REAL" = "TEXT"
167
+ ) {
168
+ const workbook = XLSX.read(buffer, {
169
+ type: "buffer",
170
+ cellDates: true,
171
+ raw: false,
172
+ });
173
+ return this.importSheetFromWorkbook(workbook, dbPath, tableName, sheetName, drop, headerRow, dateAs);
174
+ }
175
+
176
+ private static importSheetFromWorkbook(
177
+ workbook: XLSX.WorkBook,
178
+ dbPath: string,
179
+ tableName: string,
180
+ sheetName?: string,
181
+ drop = false,
182
+ headerRow = 1,
183
+ dateAs: "TEXT" | "INTEGER" | "REAL" = "TEXT"
184
+ ) {
185
+ const sheet = sheetName || workbook.SheetNames[0];
186
+ const worksheet = workbook.Sheets[sheet];
187
+ if (!worksheet) throw new Error(`Sheet not found: ${sheet}`);
188
+
189
+ const rows: any[] = XLSX.utils.sheet_to_json(worksheet, {
190
+ header: 1,
191
+ defval: "",
192
+ blankrows: false,
193
+ raw: false,
194
+ });
195
+ if (rows.length < headerRow) throw new Error(`Sheet has no header at row ${headerRow}`);
196
+
197
+ const header = (rows[headerRow - 1] as unknown[]).map(v => JSON.stringify(v ?? "").trim());
198
+ const dataRows = rows
199
+ .slice(headerRow)
200
+ .filter(r => Array.isArray(r) && r.some(c => c !== "" && c !== null && c !== undefined));
201
+
202
+ const sanitizedNames = uniqueNames(header);
203
+ const columns: ColumnDef[] = sanitizedNames.map((name, idx) => {
204
+ const colValues = dataRows.map(r => r[idx]);
205
+ return {
206
+ name,
207
+ original: header[idx] || name,
208
+ kind: inferKind(colValues),
209
+ };
210
+ });
211
+
212
+ const tblName = snake(tableName || sheet);
213
+ const columnDecls = columns.map(c => `"${c.name}" ${c.kind}`);
214
+ const ddl = `CREATE TABLE IF NOT EXISTS "${tblName}" ( ${columnDecls.join(", ")} );`;
215
+
216
+ const db = new SqliteModule(dbPath);
217
+
218
+ const tx = db.db.transaction(() => {
219
+ if (drop) db.db.prepare(`DROP TABLE IF EXISTS "${tblName}";`).run();
220
+ db.db.prepare(ddl).run();
221
+ if (dataRows.length === 0) return;
222
+
223
+ const placeholders = columns.map(() => "?").join(",");
224
+ const insertSql = `INSERT INTO "${tblName}" (${columns.map(c => `"${c.name}"`).join(", ")}) VALUES (${placeholders});`;
225
+ const stmt = db.db.prepare(insertSql);
226
+ for (const row of dataRows) {
227
+ const values = columns.map((c, idx) => coerceValue(c.kind, row[idx], dateAs));
228
+ stmt.run(values);
229
+ }
230
+ });
231
+
232
+ tx();
233
+ return { tableName: tblName, columns };
234
+ }
235
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "typescript-config/tsconfig.common.json",
3
+ "compilerOptions": {
4
+ "rootDir": ".",
5
+ "types": ["node"],
6
+ "baseUrl": "src",
7
+ "tsBuildInfoFile": "dist/typecheck.tsbuildinfo",
8
+ "experimentalDecorators": true,
9
+ "emitDecoratorMetadata": true,
10
+ "allowSyntheticDefaultImports": true
11
+ },
12
+ "include": ["src/**/*.ts"]
13
+ }
@@ -0,0 +1,59 @@
1
+ type IEventWithPayload<T extends Record<string, unknown>> = {
2
+ [K in keyof T]: { event: K; payload: T[K] };
3
+ }[keyof T];
4
+
5
+ export class IEvent<T extends Record<string, unknown>> {
6
+ private listeners: { [K in keyof T]?: Array<(payload: T[K]) => void> } = {};
7
+ private onceListeners: { [K in keyof T]?: Array<(payload: T[K]) => void> } = {};
8
+ private everyListeners: Array<(e: IEventWithPayload<T>) => void> = [];
9
+
10
+ on<K extends keyof T>(event: K, handler: (payload: T[K]) => void) {
11
+ (this.listeners[event] ||= []).push(handler);
12
+ return () => this.off(event, handler);
13
+ }
14
+
15
+ once<K extends keyof T>(event: K, handler: (payload: T[K]) => void) {
16
+ (this.onceListeners[event] ||= []).push(handler);
17
+ return () => this.off(event, handler);
18
+ }
19
+
20
+ off<K extends keyof T>(event: K, handler: (payload: T[K]) => void) {
21
+ if (this.listeners[event]) {
22
+ this.listeners[event] = this.listeners[event].filter(fn => fn !== handler);
23
+ }
24
+ if (this.onceListeners[event]) {
25
+ this.onceListeners[event] = this.onceListeners[event].filter(fn => fn !== handler);
26
+ }
27
+ }
28
+
29
+ onEvery(handler: (e: IEventWithPayload<T>) => void) {
30
+ this.everyListeners.push(handler);
31
+ return () => {
32
+ this.everyListeners = this.everyListeners.filter(h => h !== handler);
33
+ };
34
+ }
35
+
36
+ emit<K extends keyof T>(event: K, payload: T[K]) {
37
+ const everyList = [...this.everyListeners];
38
+ for (const h of everyList) {
39
+ h({ event, payload } as IEventWithPayload<T>);
40
+ }
41
+
42
+ const list = this.listeners[event];
43
+ if (list) {
44
+ for (const h of [...list]) h(payload);
45
+ }
46
+
47
+ const onceList = this.onceListeners[event];
48
+ if (onceList && onceList.length > 0) {
49
+ this.onceListeners[event] = [];
50
+ for (const h of [...onceList]) h(payload);
51
+ }
52
+ }
53
+
54
+ emitOnce<K extends keyof T>(event: K, payload: T[K]) {
55
+ this.emit(event, payload);
56
+ this.listeners[event] = [];
57
+ this.onceListeners[event] = [];
58
+ }
59
+ }
@@ -0,0 +1 @@
1
+ export * from "./i-event.js";
@@ -13,7 +13,7 @@ import {
13
13
  Text,
14
14
  } from "@react-email/components";
15
15
  import React from "react";
16
- export function VerifyEmailOtp({ verificationCode = "596853" }: { verificationCode: string }) {
16
+ export default function VerifyEmailOtp({ verificationCode = "596853" }: { verificationCode: string }) {
17
17
  return (
18
18
  <Html>
19
19
  <Head>
@@ -1 +1,2 @@
1
- export * from "./admin/OtpEmail.js";
1
+ import VerifyEmailOtp from "./admin/OtpEmail.js";
2
+ export { VerifyEmailOtp };
@@ -0,0 +1,4 @@
1
+ import { baseConfig } from "eslint-config/base";
2
+ import { extend } from "eslint-config/extend";
3
+
4
+ export default extend(baseConfig);
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "@repo/constants",
2
+ "name": "constants",
3
3
  "version": "0.12.0",
4
4
  "type": "module",
5
5
 
@@ -0,0 +1,3 @@
1
+ import vitestConfig from "vitest-config/node";
2
+
3
+ export default vitestConfig;
@@ -1,7 +1,7 @@
1
1
  packages:
2
2
  - "apps/*"
3
3
  - "packages/*"
4
- - "packages/@repo/*"
4
+ - "packages/*/*"
5
5
  - "configs/*"
6
6
 
7
7
  catalog:
@@ -23,6 +23,7 @@ catalog:
23
23
  axios: ^1.13.6
24
24
  date-fns: ^4.1.0
25
25
  nanoid: ^5.1.6
26
+ xlsx: ^0.18.5
26
27
 
27
28
  # testing
28
29
  vitest: ^4.0.18
@@ -66,9 +67,11 @@ catalog:
66
67
  drizzle-orm: ^0.45.1
67
68
  drizzle-kit: ^0.31.10
68
69
  pg: ^8.19.0
70
+ better-sqlite3: ^12.9.0
69
71
 
70
72
  # db types
71
73
  "@types/pg": ^8.10.0
74
+ "@types/better-sqlite3": ^7.6.13
72
75
 
73
76
  # lib
74
77
  "@aws-sdk/client-s3": ^3.39.0
File without changes