arkormx 0.1.7 → 0.1.9

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.mjs CHANGED
@@ -1,32 +1,1773 @@
1
- import{copyFileSync as e,existsSync as t,mkdirSync as n,readFileSync as r,readdirSync as i,rmSync as a,writeFileSync as o}from"fs";import s,{dirname as c,extname as l,join as u,relative as d}from"path";import{createRequire as f}from"module";import{existsSync as p,mkdirSync as m,readFileSync as h,readdirSync as g,writeFileSync as _}from"node:fs";import{join as v,resolve as y}from"node:path";import{spawnSync as ee}from"node:child_process";import{str as b}from"@h3ravel/support";import{fileURLToPath as te,pathToFileURL as ne}from"url";import{Logger as re}from"@h3ravel/shared";import{Command as x}from"@h3ravel/musket";import{pathToFileURL as ie}from"node:url";import{Collection as ae}from"@h3ravel/collect.js";const oe={string:{get:e=>e==null?e:String(e),set:e=>e==null?e:String(e)},number:{get:e=>e==null?e:Number(e),set:e=>e==null?e:Number(e)},boolean:{get:e=>e==null?e:!!e,set:e=>e==null?e:!!e},date:{get:e=>e==null||e instanceof Date?e:new Date(String(e)),set:e=>e==null||e instanceof Date?e:new Date(String(e))},json:{get:e=>{if(e==null||typeof e!=`string`)return e;try{return JSON.parse(e)}catch{return e}},set:e=>e==null||typeof e==`string`?e:JSON.stringify(e)},array:{get:e=>{if(Array.isArray(e))return e;if(typeof e==`string`)try{let t=JSON.parse(e);return Array.isArray(t)?t:[t]}catch{return[e]}return e==null?e:[e]},set:e=>e==null||Array.isArray(e)?e:[e]}};function S(e){return typeof e==`string`?oe[e]:e}var C=class extends Error{constructor(e){super(e),this.name=`ArkormException`}},w=class{},T=class{columns=[];dropColumnNames=[];indexes=[];latestColumnName;primary(e,t){let n=typeof e==`string`?{columnName:e,...t??{}}:e??{},r=this.resolveColumn(n.columnName);return r.primary=!0,typeof n.autoIncrement==`boolean`&&(r.autoIncrement=n.autoIncrement),Object.prototype.hasOwnProperty.call(n,`default`)&&(r.default=n.default),this}id(e=`id`,t=`id`){return this.column(e,t,{primary:!0})}uuid(e,t={}){return this.column(e,`uuid`,t)}string(e,t={}){return this.column(e,`string`,t)}text(e,t={}){return this.column(e,`text`,t)}integer(e,t={}){return this.column(e,`integer`,t)}bigInteger(e,t={}){return this.column(e,`bigInteger`,t)}float(e,t={}){return this.column(e,`float`,t)}boolean(e,t={}){return this.column(e,`boolean`,t)}json(e,t={}){return this.column(e,`json`,t)}date(e,t={}){return this.column(e,`date`,t)}morphs(e,t=!1){return this.string(`${e}Type`,{nullable:t}),this.integer(`${e}Id`,{nullable:t}),this}nullableMorphs(e){return this.morphs(e,!0)}timestamp(e,t={}){return this.column(e,`timestamp`,t)}timestamps(){return this.timestamp(`createdAt`,{nullable:!1}),this.timestamp(`updatedAt`,{nullable:!1}),this}softDeletes(e=`deletedAt`){return this.timestamp(e,{nullable:!0}),this}dropColumn(e){return this.dropColumnNames.push(e),this}nullable(e){let t=this.resolveColumn(e);return t.nullable=!0,this}after(e,t){let n=this.resolveColumn(t);return n.after=e,this}map(e,t){let n=this.resolveColumn(t);return n.map=e,this}index(e,t){let n=Array.isArray(e)?e:typeof e==`string`?[e]:[this.resolveColumn().name];return this.indexes.push({columns:[...n],name:t}),this}getColumns(){return this.columns.map(e=>({...e}))}getDropColumns(){return[...this.dropColumnNames]}getIndexes(){return this.indexes.map(e=>({...e,columns:[...e.columns]}))}column(e,t,n){return this.columns.push({name:e,type:t,map:n.map,nullable:n.nullable,unique:n.unique,primary:n.primary,autoIncrement:n.autoIncrement,after:n.after,default:n.default}),this.latestColumnName=e,this}resolveColumn(e){let t=e??this.latestColumnName;if(!t)throw Error(`No column available for this operation.`);let n=this.columns.find(e=>e.name===t);if(!n)throw Error(`Column [${t}] was not found in the table definition.`);return n}},se=class{operations=[];createTable(e,t){let n=new T;return t(n),this.operations.push({type:`createTable`,table:e,columns:n.getColumns(),indexes:n.getIndexes()}),this}alterTable(e,t){let n=new T;return t(n),this.operations.push({type:`alterTable`,table:e,addColumns:n.getColumns(),dropColumns:n.getDropColumns(),addIndexes:n.getIndexes()}),this}dropTable(e){return this.operations.push({type:`dropTable`,table:e}),this}getOperations(){return this.operations.map(e=>e.type===`createTable`?{...e,columns:e.columns.map(e=>({...e})),indexes:e.indexes.map(e=>({...e,columns:[...e.columns]}))}:e.type===`alterTable`?{...e,addColumns:e.addColumns.map(e=>({...e})),dropColumns:[...e.dropColumns],addIndexes:e.addIndexes.map(e=>({...e,columns:[...e.columns]}))}:{...e})}};const ce=/model\s+(\w+)\s*\{[\s\S]*?\n\}/g,E=e=>{let t=e.replace(/[^a-zA-Z0-9]+/g,` `).trim(),n=(t.endsWith(`s`)&&t.length>1?t.slice(0,-1):t).split(/\s+/g).filter(Boolean);return n.length===0?`GeneratedModel`:n.map(e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}`).join(``)},D=e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),le=e=>e.type===`id`?`Int`:e.type===`uuid`||e.type===`string`||e.type===`text`?`String`:e.type===`integer`?`Int`:e.type===`bigInteger`?`BigInt`:e.type===`float`?`Float`:e.type===`boolean`?`Boolean`:e.type===`json`?`Json`:`DateTime`,O=e=>{if(e!=null){if(typeof e==`string`)return`@default("${e.replace(/"/g,`\\"`)}")`;if(typeof e==`number`||typeof e==`bigint`)return`@default(${e})`;if(typeof e==`boolean`)return`@default(${e?`true`:`false`})`}},k=e=>{if(e.type===`id`){let t=e.primary===!1?``:` @id`,n=typeof e.map==`string`&&e.map.trim().length>0?` @map("${e.map.replace(/"/g,`\\"`)}")`:``,r=O(e.default),i=e.autoIncrement??e.primary!==!1,a=r?` ${r}`:i&&t?` @default(autoincrement())`:``;return` ${e.name} Int${t}${a}${n}`}let t=le(e),n=e.nullable?`?`:``,r=e.unique?` @unique`:``,i=e.primary?` @id`:``,a=typeof e.map==`string`&&e.map.trim().length>0?` @map("${e.map.replace(/"/g,`\\"`)}")`:``,o=O(e.default)??(e.type===`uuid`&&e.primary?`@default(uuid())`:void 0),s=o?` ${o}`:``;return` ${e.name} ${t}${n}${i}${r}${s}${a}`},A=e=>` @@index([${e.columns.join(`, `)}]${typeof e.name==`string`&&e.name.trim().length>0?`, name: "${e.name.replace(/"/g,`\\"`)}"`:``})`,ue=e=>{let t=E(e.table),n=e.table!==t.toLowerCase(),r=e.columns.map(k),i=[...(e.indexes??[]).map(A),...n?[` @@map("${b(e.table).snake()}")`]:[]];return`model ${t} {\n${(i.length>0?[...r,``,...i]:r).join(`
2
- `)}\n}`},j=(e,t)=>{let n=[...e.matchAll(ce)],r=RegExp(`@@map\\("${D(t)}"\\)`);for(let e of n){let n=e[0],i=e[1],a=e.index??0,o=a+n.length;if(r.test(n)||i.toLowerCase()===t.toLowerCase()||i.toLowerCase()===E(t).toLowerCase())return{modelName:i,block:n,start:a,end:o}}return null},M=(e,t)=>{if(j(e,t.table))throw new C(`Prisma model for table [${t.table}] already exists.`);let n=ue(t);return`${e.trimEnd()}\n\n${n}\n`},de=(e,t)=>{let n=j(e,t.table);if(!n)throw new C(`Prisma model for table [${t.table}] was not found.`);let r=n.block,i=r.split(`
3
- `);return t.dropColumns.forEach(e=>{let t=RegExp(`^\\s*${D(e)}\\s+`);for(let e=0;e<i.length;e+=1)if(t.test(i[e])){i.splice(e,1);return}}),t.addColumns.forEach(e=>{let t=k(e),n=RegExp(`^\\s*${D(e.name)}\\s+`);if(i.some(e=>n.test(e)))return;let r=Math.max(1,i.length-1),a=typeof e.after==`string`&&e.after.length>0?i.findIndex(t=>RegExp(`^\\s*${D(e.after)}\\s+`).test(t)):-1,o=a>0?Math.min(a+1,r):r;i.splice(o,0,t)}),(t.addIndexes??[]).forEach(e=>{let t=A(e);if(i.some(e=>e.trim()===t.trim()))return;let n=Math.max(1,i.length-1);i.splice(n,0,t)}),r=i.join(`
4
- `),`${e.slice(0,n.start)}${r}${e.slice(n.end)}`},fe=(e,t)=>{let n=j(e,t.table);if(!n)return e;let r=e.slice(0,n.start).trimEnd(),i=e.slice(n.end).trimStart();return`${r}${r&&i?`
5
-
6
- `:``}${i}`},pe=(e,t)=>t.reduce((e,t)=>t.type===`createTable`?M(e,t):t.type===`alterTable`?de(e,t):fe(e,t),e),N=(e,t)=>{let n=ee(`npx`,[`prisma`,...e],{cwd:t,encoding:`utf-8`});if(n.status===0)return;let r=[n.stdout,n.stderr].filter(Boolean).join(`
7
- `).trim();throw new C(r?`Prisma command failed: prisma ${e.join(` `)}\n${r}`:`Prisma command failed: prisma ${e.join(` `)}`)},me=e=>{let t=e.replace(/[^a-zA-Z0-9]+/g,` `).trim();return t?`${t.split(/\s+/g).map(e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}`).join(``)}Migration`:`GeneratedMigration`},P=e=>String(e).padStart(2,`0`),F=(e=new Date)=>`${e.getFullYear()}${P(e.getMonth()+1)}${P(e.getDate())}${P(e.getHours())}${P(e.getMinutes())}${P(e.getSeconds())}`,he=e=>e.trim().toLowerCase().replace(/[^a-z0-9]+/g,`_`).replace(/^_+|_+$/g,``)||`migration`,ge=(e,t=`ts`)=>t===`js`?[`import { Migration } from 'arkormx'`,``,`export default class ${e} extends Migration {`,` /**`,` * @param {import('arkormx').SchemaBuilder} schema`,` * @returns {Promise<void>}`,` */`,` async up (schema) {`,` }`,``,` /**`,` * @param {import('arkormx').SchemaBuilder} schema`,` * @returns {Promise<void>}`,` */`,` async down (schema) {`,` }`,`}`,``].join(`
8
- `):[`import { Migration, SchemaBuilder } from 'arkormx'`,``,`export default class ${e} extends Migration {`,` public async up (schema: SchemaBuilder): Promise<void> {`,` }`,``,` public async down (schema: SchemaBuilder): Promise<void> {`,` }`,`}`,``].join(`
9
- `),_e=(e,t={})=>{let n=F(new Date),r=he(e),i=me(e),a=t.extension??`ts`,o=t.directory??v(process.cwd(),`database`,`migrations`),s=`${n}_${r}.${a}`,c=v(o,s),l=ge(i,a);if(t.write??!0){if(p(o)||m(o,{recursive:!0}),p(c))throw new C(`Migration file already exists: ${c}`);_(c,l)}return{fileName:s,filePath:c,className:i,content:l}},ve=async(e,t=`up`)=>{let n=e instanceof w?e:new e,r=new se;return t===`up`?await n.up(r):await n.down(r),r.getOperations()},I=async(e,t={})=>{let n=t.schemaPath??v(process.cwd(),`prisma`,`schema.prisma`);if(!p(n))throw new C(`Prisma schema file not found: ${n}`);let r=h(n,`utf-8`),i=await ve(e,`up`),a=pe(r,i);return(t.write??!0)&&_(n,a),{schema:a,schemaPath:n,operations:i}},ye=async(e,t={})=>{let n=t.cwd??process.cwd(),r=await I(e,{schemaPath:t.schemaPath??v(n,`prisma`,`schema.prisma`),write:t.write}),i=t.runGenerate??!0,a=t.runMigrate??!0,o=t.migrateMode??`dev`;return i&&N([`generate`],n),a&&N(o===`deploy`?[`migrate`,`deploy`]:[`migrate`,`dev`,`--name`,t.migrationName??`arkorm_${F()}`],n),{schemaPath:r.schemaPath,operations:r.operations}},be=()=>{let e=s.dirname(te(import.meta.url));for(;;){let n=s.join(e,`package.json`),r=s.join(e,`stubs`);if(t(n)&&t(r))return r;let i=s.dirname(e);if(i===e)break;e=i}return s.join(process.cwd(),`stubs`)},L={paths:{stubs:be(),seeders:s.join(process.cwd(),`database`,`seeders`),models:s.join(process.cwd(),`src`,`models`),migrations:s.join(process.cwd(),`database`,`migrations`),factories:s.join(process.cwd(),`database`,`factories`),buildOutput:s.join(process.cwd(),`dist`)},outputExt:`ts`},R={...L,paths:{...L.paths??{}}};let z=!1,B,V,H;const xe=e=>{let t=L.paths??{},n=R.paths??{},r=Object.entries(e??{}).reduce((e,[t,n])=>(typeof n==`string`&&n.trim().length>0&&(e[t]=n),e),{});return{...t,...n,...r}},Se=e=>e,U=e=>e?R[e]:R,Ce=(e,t={})=>{let n={...R,prisma:e,paths:xe(t.paths)};t.pagination!==void 0&&(n.pagination=t.pagination),t.outputExt!==void 0&&(n.outputExt=t.outputExt),Object.assign(R,{...n}),V=e,H=n.pagination?.urlDriver},we=()=>{Object.assign(R,{...L,paths:{...L.paths??{}}}),z=!1,B=void 0,V=void 0,H=void 0},Te=e=>{if(!e)return;let t=typeof e==`function`?e():e;if(!(!t||typeof t!=`object`))return t},Ee=e=>{let t=e?.default??e;!t||typeof t!=`object`||!t.prisma||(Ce(t.prisma,{pagination:t.pagination,paths:t.paths,outputExt:t.outputExt}),z=!0)},De=e=>import(`${ne(e).href}?arkorm_runtime=${Date.now()}`),W=()=>{let e=f(import.meta.url),n=[s.join(process.cwd(),`arkormx.config.cjs`)];for(let r of n)if(t(r))try{return Ee(e(r)),!0}catch{continue}return!1},G=async()=>{if(!z){if(B)return await B;W()||(B=(async()=>{let e=[s.join(process.cwd(),`arkormx.config.js`),s.join(process.cwd(),`arkormx.config.ts`)];for(let n of e)if(t(n))try{Ee(await De(n));return}catch{continue}z=!0})(),await B)}},Oe=()=>{z||B||G()},K=()=>be(),ke=()=>(z||W(),Te(V)),q=()=>(z||W(),H),J=e=>{if(!e||typeof e!=`object`)return!1;let t=e;return[`findMany`,`findFirst`,`create`,`update`,`delete`,`count`].every(e=>typeof t[e]==`function`)};G();var Ae=class{command;config={};constructor(){this.config=U()}getConfig=U;ensureDirectory(e){let r=c(e);t(r)||n(r,{recursive:!0})}formatPathForLog(e){let t=d(process.cwd(),e);return t?t.startsWith(`..`)?e:t:`.`}splitLogger(e,t){return t=t.includes(process.cwd())?this.formatPathForLog(t):t,re.twoColumnDetail(e+` `,` `+t,!1).join(``)}hasTypeScriptInstalled(){try{return f(import.meta.url).resolve(`typescript`,{paths:[process.cwd()]}),!0}catch{return!1}}resolveOutputExt(){let e=this.getConfig(`outputExt`)===`js`?`js`:`ts`;return e===`ts`&&!this.hasTypeScriptInstalled()?`js`:e}stripKnownSourceExtension(e){return e.replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i,``)}resolveRuntimeDirectoryPath(e){if(t(e))return e;let{buildOutput:n}=this.getConfig(`paths`)||{};if(typeof n!=`string`||n.trim().length===0)return e;let r=d(process.cwd(),e);if(!r||r.startsWith(`..`))return e;let i=u(n,r);return t(i)?i:e}resolveRuntimeScriptPath(e){let n=l(e).toLowerCase(),r=n===`.ts`||n===`.mts`||n===`.cts`,i=[];if(r){let t=e.slice(0,-n.length);i.push(`${t}.js`,`${t}.cjs`,`${t}.mjs`)}let{buildOutput:a}=this.getConfig(`paths`)??{};if(typeof a==`string`&&a.trim().length>0){let t=d(process.cwd(),e);if(t&&!t.startsWith(`..`)){let e=u(a,t),n=l(e).toLowerCase();if(n===`.ts`||n===`.mts`||n===`.cts`){let t=e.slice(0,-n.length);i.push(`${t}.js`,`${t}.cjs`,`${t}.mjs`)}else i.push(e)}}return i.find(e=>t(e))||e}generateFile(e,n,i,s){t(n)&&!s?.force?(this.command.error(`Error: ${this.formatPathForLog(n)} already exists.`),process.exit(1)):t(n)&&s?.force&&a(n);let c=r(e,`utf-8`);for(let[e,t]of Object.entries(i))c=c.replace(RegExp(`{{${e}}}`,`g`),t);return this.ensureDirectory(n),o(n,c),n}resolveConfigPath(e,t){let{[e]:n}=this.getConfig(`paths`)??{};return typeof n==`string`&&n.trim().length>0?n:t}resolveStubPath(e){return u(this.resolveConfigPath(`stubs`,K()),e)}makeFactory(e,t={}){let n=b(e.replace(/Factory$/,``)).pascal(),r=`${n}Factory`,i=t.modelName?b(t.modelName).pascal():n,a=this.resolveOutputExt(),o=u(this.resolveConfigPath(`factories`,u(process.cwd(),`database`,`factories`)),`${r}.${a}`),s=u(this.resolveConfigPath(`models`,u(process.cwd(),`src`,`models`)),`${i}.${a}`),l=t.modelImportPath??`./${this.stripKnownSourceExtension(d(c(o),s).replace(/\\/g,`/`))}${a===`js`?`.js`:``}`,f=this.resolveStubPath(a===`js`?`factory.js.stub`:`factory.stub`);return{name:r,path:this.generateFile(f,o,{FactoryName:r,ModelName:i.toString(),ModelImportPath:l.startsWith(`.`)?l:`./${l}`},t)}}makeSeeder(e,t={}){let n=`${b(e.replace(/Seeder$/,``)).pascal()}Seeder`,r=this.resolveOutputExt(),i=u(this.resolveConfigPath(`seeders`,u(process.cwd(),`database`,`seeders`)),`${n}.${r}`),a=this.resolveStubPath(r===`js`?`seeder.js.stub`:`seeder.stub`);return{name:n,path:this.generateFile(a,i,{SeederName:n},t)}}makeMigration(e){let t=_e(e,{directory:this.resolveConfigPath(`migrations`,u(process.cwd(),`database`,`migrations`)),extension:this.resolveOutputExt()});return{name:t.className,path:t.filePath}}makeModel(e,t={}){let n=b(e.replace(/Model$/,``)).pascal().toString(),r=`${n}`,i=b(n).camel().plural().toString(),a=this.resolveOutputExt(),o=u(this.resolveConfigPath(`models`,u(process.cwd(),`src`,`models`)),`${r}.${a}`),s=t.all||t.factory,l=t.all||t.seeder,f=t.all||t.migration,p=`${n}Factory`,m=u(this.resolveConfigPath(`factories`,u(process.cwd(),`database`,`factories`)),`${p}.${a}`),h=`./${d(c(o),m).replace(/\\/g,`/`).replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i,``)}${a===`js`?`.js`:``}`,g=this.resolveStubPath(a===`js`?`model.js.stub`:`model.stub`),_=this.generateFile(g,o,{ModelName:r,DelegateName:i,FactoryImport:s?`import { ${p} } from '${h}'\n`:``,FactoryLink:s?a===`js`?`\n static factoryClass = ${p}`:`\n protected static override factoryClass = ${p}`:``},t),v=this.ensurePrismaModelEntry(r,i),y={model:{name:r,path:_},prisma:v,factory:void 0,seeder:void 0,migration:void 0};return s&&(y.factory=this.makeFactory(n,{force:t.force,modelName:r,modelImportPath:`./${d(c(m),o).replace(/\\/g,`/`).replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i,``)}${a===`js`?`.js`:``}`})),l&&(y.seeder=this.makeSeeder(n,{force:t.force})),f&&(y.migration=this.makeMigration(`create ${i} table`)),y}ensurePrismaModelEntry(e,n){let i=u(process.cwd(),`prisma`,`schema.prisma`);if(!t(i))return{path:i,updated:!1};let a=r(i,`utf-8`),s=j(a,n),c=RegExp(`model\\s+${e}\\s*\\{`,`m`).test(a);return s||c?{path:i,updated:!1}:(o(i,M(a,{type:`createTable`,table:n,columns:[{name:`id`,type:`id`,primary:!0}]})),{path:i,updated:!0})}prismaTypeToTs(e){return e===`Int`||e===`Float`||e===`Decimal`?`number`:e===`BigInt`?`bigint`:e===`String`?`string`:e===`Boolean`?`boolean`:e===`DateTime`?`Date`:e===`Json`?`Record<string, unknown>`:e===`Bytes`?`Buffer`:`unknown`}parsePrismaModels(e){let t=[],n=/model\s+(\w+)\s*\{([\s\S]*?)\n\}/g,r=new Set([`Int`,`Float`,`Decimal`,`BigInt`,`String`,`Boolean`,`DateTime`,`Json`,`Bytes`]);for(let i of e.matchAll(n)){let e=i[1],n=i[2],a=n.match(/@@map\("([^"]+)"\)/)?.[1]??`${e.charAt(0).toLowerCase()}${e.slice(1)}s`,o=[];n.split(`
10
- `).forEach(e=>{let t=e.trim();if(!t||t.startsWith(`@@`)||t.startsWith(`//`))return;let n=t.match(/^(\w+)\s+([A-Za-z]+)(\?)?\b/);if(!n)return;let i=n[2];r.has(i)&&o.push({name:n[1],type:this.prismaTypeToTs(i),optional:!!n[3]})}),t.push({name:e,table:a,fields:o})}return t}syncModelDeclarations(e,t){let n=e.split(`
11
- `),r=n.findIndex(e=>/export\s+class\s+\w+\s+extends\s+Model<.+>\s*\{/.test(e));if(r<0)return{content:e,updated:!1};let i=-1,a=0;for(let e=r;e<n.length;e+=1){let t=n[e];if(a+=(t.match(/\{/g)||[]).length,a-=(t.match(/\}/g)||[]).length,a===0){i=e;break}}if(i<0)return{content:e,updated:!1};let o=n.slice(r+1,i).filter(e=>!/^\s*declare\s+\w+\??:\s*[^\n]+$/.test(e)),s=[...t.map(e=>` ${e}`),...o],c=[...n.slice(0,r+1),...s,...n.slice(i)].join(`
12
- `);return{content:c,updated:c!==e}}syncModelsFromPrisma(e={}){let n=e.schemaPath??u(process.cwd(),`prisma`,`schema.prisma`),a=e.modelsDir??this.resolveConfigPath(`models`,u(process.cwd(),`src`,`models`));if(!t(n))throw Error(`Prisma schema file not found: ${n}`);if(!t(a))throw Error(`Models directory not found: ${a}`);let s=r(n,`utf-8`),c=this.parsePrismaModels(s),l=i(a).filter(e=>e.endsWith(`.ts`)),d=[],f=[];return l.forEach(e=>{let t=u(a,e),n=r(t,`utf-8`),i=n.match(/export\s+class\s+(\w+)\s+extends\s+Model<'([^']+)'>/);if(!i){f.push(t);return}let s=i[1],l=i[2],p=c.find(e=>e.table===l)??c.find(e=>e.name===s);if(!p||p.fields.length===0){f.push(t);return}let m=p.fields.map(e=>`declare ${e.name}${e.optional?`?`:``}: ${e.type}`),h=this.syncModelDeclarations(n,m);if(!h.updated){f.push(t);return}o(t,h.content),d.push(t)}),{schemaPath:n,modelsDir:a,total:l.length,updated:d,skipped:f}}},je=class extends x{signature=`init
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
2
+ import path, { dirname, extname, join, relative } from "path";
3
+ import { createRequire } from "module";
4
+ import { existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, writeFileSync as writeFileSync$1 } from "node:fs";
5
+ import { join as join$1, resolve } from "node:path";
6
+ import { spawnSync } from "node:child_process";
7
+ import { str } from "@h3ravel/support";
8
+ import { fileURLToPath, pathToFileURL } from "url";
9
+ import { Logger } from "@h3ravel/shared";
10
+ import { Command } from "@h3ravel/musket";
11
+ import { pathToFileURL as pathToFileURL$1 } from "node:url";
12
+ import { Collection } from "@h3ravel/collect.js";
13
+
14
+ //#region src/casts.ts
15
+ const builtinCasts = {
16
+ string: {
17
+ get: (value) => value == null ? value : String(value),
18
+ set: (value) => value == null ? value : String(value)
19
+ },
20
+ number: {
21
+ get: (value) => value == null ? value : Number(value),
22
+ set: (value) => value == null ? value : Number(value)
23
+ },
24
+ boolean: {
25
+ get: (value) => value == null ? value : Boolean(value),
26
+ set: (value) => value == null ? value : Boolean(value)
27
+ },
28
+ date: {
29
+ get: (value) => {
30
+ if (value == null || value instanceof Date) return value;
31
+ return new Date(String(value));
32
+ },
33
+ set: (value) => {
34
+ if (value == null || value instanceof Date) return value;
35
+ return new Date(String(value));
36
+ }
37
+ },
38
+ json: {
39
+ get: (value) => {
40
+ if (value == null || typeof value !== "string") return value;
41
+ try {
42
+ return JSON.parse(value);
43
+ } catch {
44
+ return value;
45
+ }
46
+ },
47
+ set: (value) => {
48
+ if (value == null || typeof value === "string") return value;
49
+ return JSON.stringify(value);
50
+ }
51
+ },
52
+ array: {
53
+ get: (value) => {
54
+ if (Array.isArray(value)) return value;
55
+ if (typeof value === "string") try {
56
+ const parsed = JSON.parse(value);
57
+ return Array.isArray(parsed) ? parsed : [parsed];
58
+ } catch {
59
+ return [value];
60
+ }
61
+ if (value == null) return value;
62
+ return [value];
63
+ },
64
+ set: (value) => {
65
+ if (value == null) return value;
66
+ return Array.isArray(value) ? value : [value];
67
+ }
68
+ }
69
+ };
70
+ function resolveCast(definition) {
71
+ if (typeof definition === "string") return builtinCasts[definition];
72
+ return definition;
73
+ }
74
+
75
+ //#endregion
76
+ //#region src/Exceptions/ArkormException.ts
77
+ /**
78
+ * The ArkormException class is a custom error type for handling
79
+ * exceptions specific to the Arkormˣ.
80
+ *
81
+ * @author Legacy (3m1n3nc3)
82
+ * @since 0.1.0
83
+ */
84
+ var ArkormException = class extends Error {
85
+ constructor(message) {
86
+ super(message);
87
+ this.name = "ArkormException";
88
+ }
89
+ };
90
+
91
+ //#endregion
92
+ //#region src/database/Migration.ts
93
+ /**
94
+ * The Migration class serves as a base for defining database migrations, requiring
95
+ * the implementation of `up` and `down` methods to specify the changes to be
96
+ * applied or reverted in the database schema.
97
+ *
98
+ * @author Legacy (3m1n3nc3)
99
+ * @since 0.1.0
100
+ */
101
+ var Migration = class {};
102
+
103
+ //#endregion
104
+ //#region src/database/TableBuilder.ts
105
+ /**
106
+ * The TableBuilder class provides a fluent interface for defining
107
+ * the structure of a database table in a migration, including columns to add or drop.
108
+ *
109
+ * @author Legacy (3m1n3nc3)
110
+ * @since 0.1.0
111
+ */
112
+ var TableBuilder = class {
113
+ columns = [];
114
+ dropColumnNames = [];
115
+ indexes = [];
116
+ latestColumnName;
117
+ /**
118
+ * Defines a primary key column in the table.
119
+ *
120
+ * @param columnNameOrOptions
121
+ * @param options
122
+ * @returns
123
+ */
124
+ primary(columnNameOrOptions, options) {
125
+ const config = typeof columnNameOrOptions === "string" ? {
126
+ columnName: columnNameOrOptions,
127
+ ...options ?? {}
128
+ } : columnNameOrOptions ?? {};
129
+ const column = this.resolveColumn(config.columnName);
130
+ column.primary = true;
131
+ if (typeof config.autoIncrement === "boolean") column.autoIncrement = config.autoIncrement;
132
+ if (Object.prototype.hasOwnProperty.call(config, "default")) column.default = config.default;
133
+ return this;
134
+ }
135
+ /**
136
+ * Defines an auto-incrementing primary key column.
137
+ *
138
+ * @param name The name of the primary key column.
139
+ * @default 'id'
140
+ * @returns The current TableBuilder instance for chaining.
141
+ */
142
+ id(name = "id", type = "id") {
143
+ return this.column(name, type, { primary: true });
144
+ }
145
+ /**
146
+ * Defines a UUID column in the table.
147
+ *
148
+ * @param name The name of the UUID column.
149
+ * @param options Additional options for the UUID column.
150
+ * @returns The current TableBuilder instance for chaining.
151
+ */
152
+ uuid(name, options = {}) {
153
+ return this.column(name, "uuid", options);
154
+ }
155
+ /**
156
+ * Defines a string column in the table.
157
+ *
158
+ * @param name The name of the string column.
159
+ * @param options Additional options for the string column.
160
+ * @returns The current TableBuilder instance for chaining.
161
+ */
162
+ string(name, options = {}) {
163
+ return this.column(name, "string", options);
164
+ }
165
+ /**
166
+ * Defines a text column in the table.
167
+ *
168
+ * @param name The name of the text column.
169
+ * @param options Additional options for the text column.
170
+ * @returns The current TableBuilder instance for chaining.
171
+ */
172
+ text(name, options = {}) {
173
+ return this.column(name, "text", options);
174
+ }
175
+ /**
176
+ * Defines an integer column in the table.
177
+ *
178
+ * @param name The name of the integer column.
179
+ * @param options Additional options for the integer column.
180
+ * @returns
181
+ */
182
+ integer(name, options = {}) {
183
+ return this.column(name, "integer", options);
184
+ }
185
+ /**
186
+ * Defines a big integer column in the table.
187
+ *
188
+ * @param name The name of the big integer column.
189
+ * @param options Additional options for the big integer column.
190
+ * @returns
191
+ */
192
+ bigInteger(name, options = {}) {
193
+ return this.column(name, "bigInteger", options);
194
+ }
195
+ /**
196
+ * Defines a float column in the table.
197
+ *
198
+ * @param name The name of the float column.
199
+ * @param options Additional options for the float column.
200
+ * @returns
201
+ */
202
+ float(name, options = {}) {
203
+ return this.column(name, "float", options);
204
+ }
205
+ /**
206
+ * Defines a boolean column in the table.
207
+ *
208
+ * @param name The name of the boolean column.
209
+ * @param options Additional options for the boolean column.
210
+ * @returns
211
+ */
212
+ boolean(name, options = {}) {
213
+ return this.column(name, "boolean", options);
214
+ }
215
+ /**
216
+ * Defines a JSON column in the table.
217
+ *
218
+ * @param name The name of the JSON column.
219
+ * @param options Additional options for the JSON column.
220
+ * @returns
221
+ */
222
+ json(name, options = {}) {
223
+ return this.column(name, "json", options);
224
+ }
225
+ /**
226
+ * Defines a date column in the table.
227
+ *
228
+ * @param name The name of the date column.
229
+ * @param options Additional options for the date column.
230
+ * @returns
231
+ */
232
+ date(name, options = {}) {
233
+ return this.column(name, "date", options);
234
+ }
235
+ /**
236
+ * Defines colonns for a polymorphic relationship in the table.
237
+ *
238
+ * @param name The base name for the polymorphic relationship columns.
239
+ * @returns
240
+ */
241
+ morphs(name, nullable = false) {
242
+ this.string(`${name}Type`, { nullable });
243
+ this.integer(`${name}Id`, { nullable });
244
+ return this;
245
+ }
246
+ /**
247
+ * Defines nullable columns for a polymorphic relationship in the table.
248
+ *
249
+ * @param name The base name for the polymorphic relationship columns.
250
+ * @returns
251
+ */
252
+ nullableMorphs(name) {
253
+ return this.morphs(name, true);
254
+ }
255
+ /**
256
+ * Defines a timestamp column in the table.
257
+ *
258
+ * @param name The name of the timestamp column.
259
+ * @param options Additional options for the timestamp column.
260
+ * @returns
261
+ */
262
+ timestamp(name, options = {}) {
263
+ return this.column(name, "timestamp", options);
264
+ }
265
+ /**
266
+ * Defines both createdAt and updatedAt timestamp columns in the table.
267
+ *
268
+ * @returns
269
+ */
270
+ timestamps() {
271
+ this.timestamp("createdAt", { nullable: false });
272
+ this.timestamp("updatedAt", { nullable: false });
273
+ return this;
274
+ }
275
+ /**
276
+ * Defines a soft delete timestamp column in the table.
277
+ *
278
+ * @param column The name of the soft delete column.
279
+ * @returns
280
+ */
281
+ softDeletes(column = "deletedAt") {
282
+ this.timestamp(column, { nullable: true });
283
+ return this;
284
+ }
285
+ /**
286
+ * Defines a column to be dropped from the table in an alterTable operation.
287
+ *
288
+ * @param name The name of the column to drop.
289
+ * @returns
290
+ */
291
+ dropColumn(name) {
292
+ this.dropColumnNames.push(name);
293
+ return this;
294
+ }
295
+ /**
296
+ * Marks a column as nullable.
297
+ *
298
+ * @param columnName Optional explicit column name. When omitted, applies to the latest defined column.
299
+ * @returns The current TableBuilder instance for chaining.
300
+ */
301
+ nullable(columnName) {
302
+ const column = this.resolveColumn(columnName);
303
+ column.nullable = true;
304
+ return this;
305
+ }
306
+ /**
307
+ * Sets the column position to appear after another column when possible.
308
+ *
309
+ * @param referenceColumn The column that the target column should be placed after.
310
+ * @param columnName Optional explicit target column name. When omitted, applies to the latest defined column.
311
+ * @returns The current TableBuilder instance for chaining.
312
+ */
313
+ after(referenceColumn, columnName) {
314
+ const column = this.resolveColumn(columnName);
315
+ column.after = referenceColumn;
316
+ return this;
317
+ }
318
+ /**
319
+ * Maps the column to a custom database column name.
320
+ *
321
+ * @param name The custom database column name.
322
+ * @param columnName Optional explicit target column name. When omitted, applies to the latest defined column.
323
+ * @returns The current TableBuilder instance for chaining.
324
+ */
325
+ map(name, columnName) {
326
+ const column = this.resolveColumn(columnName);
327
+ column.map = name;
328
+ return this;
329
+ }
330
+ /**
331
+ * Defines an index on one or more columns.
332
+ *
333
+ * @param columns Optional target columns. When omitted, applies to the latest defined column.
334
+ * @param name Optional index name.
335
+ * @returns The current TableBuilder instance for chaining.
336
+ */
337
+ index(columns, name) {
338
+ const columnList = Array.isArray(columns) ? columns : typeof columns === "string" ? [columns] : [this.resolveColumn().name];
339
+ this.indexes.push({
340
+ columns: [...columnList],
341
+ name
342
+ });
343
+ return this;
344
+ }
345
+ /**
346
+ * Returns a deep copy of the defined columns for the table.
347
+ *
348
+ * @returns
349
+ */
350
+ getColumns() {
351
+ return this.columns.map((column) => ({ ...column }));
352
+ }
353
+ /**
354
+ * Returns a copy of the defined column names to be dropped from the table.
355
+ *
356
+ * @returns
357
+ */
358
+ getDropColumns() {
359
+ return [...this.dropColumnNames];
360
+ }
361
+ /**
362
+ * Returns a deep copy of the defined indexes for the table.
363
+ *
364
+ * @returns
365
+ */
366
+ getIndexes() {
367
+ return this.indexes.map((index) => ({
368
+ ...index,
369
+ columns: [...index.columns]
370
+ }));
371
+ }
372
+ /**
373
+ * Defines a column in the table with the given name.
374
+ *
375
+ * @param name The name of the column.
376
+ * @param type The type of the column.
377
+ * @param options Additional options for the column.
378
+ * @returns
379
+ */
380
+ column(name, type, options) {
381
+ this.columns.push({
382
+ name,
383
+ type,
384
+ map: options.map,
385
+ nullable: options.nullable,
386
+ unique: options.unique,
387
+ primary: options.primary,
388
+ autoIncrement: options.autoIncrement,
389
+ after: options.after,
390
+ default: options.default
391
+ });
392
+ this.latestColumnName = name;
393
+ return this;
394
+ }
395
+ /**
396
+ * Resolve a target column by name or fallback to the latest defined column.
397
+ *
398
+ * @param columnName
399
+ * @returns
400
+ */
401
+ resolveColumn(columnName) {
402
+ const targetName = columnName ?? this.latestColumnName;
403
+ if (!targetName) throw new Error("No column available for this operation.");
404
+ const column = this.columns.find((item) => item.name === targetName);
405
+ if (!column) throw new Error(`Column [${targetName}] was not found in the table definition.`);
406
+ return column;
407
+ }
408
+ };
409
+
410
+ //#endregion
411
+ //#region src/database/SchemaBuilder.ts
412
+ /**
413
+ * The SchemaBuilder class provides methods for defining the operations to be
414
+ * performed in a migration, such as creating, altering, or dropping tables.
415
+ *
416
+ * @author Legacy (3m1n3nc3)
417
+ * @since 0.1.0
418
+ */
419
+ var SchemaBuilder = class {
420
+ operations = [];
421
+ /**
422
+ * Defines a new table to be created in the migration.
423
+ *
424
+ * @param table The name of the table to create.
425
+ * @param callback A callback function to define the table's columns and structure.
426
+ * @returns The current SchemaBuilder instance for chaining.
427
+ */
428
+ createTable(table, callback) {
429
+ const builder = new TableBuilder();
430
+ callback(builder);
431
+ this.operations.push({
432
+ type: "createTable",
433
+ table,
434
+ columns: builder.getColumns(),
435
+ indexes: builder.getIndexes()
436
+ });
437
+ return this;
438
+ }
439
+ /**
440
+ * Defines alterations to an existing table in the migration.
441
+ *
442
+ * @param table The name of the table to alter.
443
+ * @param callback A callback function to define the alterations to the table's columns and structure.
444
+ * @returns The current SchemaBuilder instance for chaining.
445
+ */
446
+ alterTable(table, callback) {
447
+ const builder = new TableBuilder();
448
+ callback(builder);
449
+ this.operations.push({
450
+ type: "alterTable",
451
+ table,
452
+ addColumns: builder.getColumns(),
453
+ dropColumns: builder.getDropColumns(),
454
+ addIndexes: builder.getIndexes()
455
+ });
456
+ return this;
457
+ }
458
+ /**
459
+ * Defines a table to be dropped in the migration.
460
+ *
461
+ * @param table The name of the table to drop.
462
+ * @returns The current SchemaBuilder instance for chaining.
463
+ */
464
+ dropTable(table) {
465
+ this.operations.push({
466
+ type: "dropTable",
467
+ table
468
+ });
469
+ return this;
470
+ }
471
+ /**
472
+ * Returns a deep copy of the defined schema operations for the migration/
473
+ *
474
+ * @returns An array of schema operations for the migration.
475
+ */
476
+ getOperations() {
477
+ return this.operations.map((operation) => {
478
+ if (operation.type === "createTable") return {
479
+ ...operation,
480
+ columns: operation.columns.map((column) => ({ ...column })),
481
+ indexes: operation.indexes.map((index) => ({
482
+ ...index,
483
+ columns: [...index.columns]
484
+ }))
485
+ };
486
+ if (operation.type === "alterTable") return {
487
+ ...operation,
488
+ addColumns: operation.addColumns.map((column) => ({ ...column })),
489
+ dropColumns: [...operation.dropColumns],
490
+ addIndexes: operation.addIndexes.map((index) => ({
491
+ ...index,
492
+ columns: [...index.columns]
493
+ }))
494
+ };
495
+ return { ...operation };
496
+ });
497
+ }
498
+ };
499
+
500
+ //#endregion
501
+ //#region src/helpers/migrations.ts
502
+ const PRISMA_MODEL_REGEX = /model\s+(\w+)\s*\{[\s\S]*?\n\}/g;
503
+ /**
504
+ * Convert a table name to a PascalCase model name, with basic singularization.
505
+ *
506
+ * @param tableName The name of the table to convert.
507
+ * @returns The corresponding PascalCase model name.
508
+ */
509
+ const toModelName = (tableName) => {
510
+ const normalized = tableName.replace(/[^a-zA-Z0-9]+/g, " ").trim();
511
+ const parts = (normalized.endsWith("s") && normalized.length > 1 ? normalized.slice(0, -1) : normalized).split(/\s+/g).filter(Boolean);
512
+ if (parts.length === 0) return "GeneratedModel";
513
+ return parts.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
514
+ };
515
+ /**
516
+ * Escape special characters in a string for use in a regular expression.
517
+ *
518
+ * @param value
519
+ * @returns
520
+ */
521
+ const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
522
+ /**
523
+ * Convert a SchemaColumn definition to a Prisma field type string, including modifiers.
524
+ *
525
+ * @param column
526
+ * @returns
527
+ */
528
+ const resolvePrismaType = (column) => {
529
+ if (column.type === "id") return "Int";
530
+ if (column.type === "uuid") return "String";
531
+ if (column.type === "string" || column.type === "text") return "String";
532
+ if (column.type === "integer") return "Int";
533
+ if (column.type === "bigInteger") return "BigInt";
534
+ if (column.type === "float") return "Float";
535
+ if (column.type === "boolean") return "Boolean";
536
+ if (column.type === "json") return "Json";
537
+ return "DateTime";
538
+ };
539
+ /**
540
+ * Format a default value for inclusion in a Prisma schema field definition, based on its type.
541
+ *
542
+ * @param value
543
+ * @returns
544
+ */
545
+ const formatDefaultValue = (value) => {
546
+ if (value == null) return void 0;
547
+ if (typeof value === "string") return `@default("${value.replace(/"/g, "\\\"")}")`;
548
+ if (typeof value === "number" || typeof value === "bigint") return `@default(${value})`;
549
+ if (typeof value === "boolean") return `@default(${value ? "true" : "false"})`;
550
+ };
551
+ /**
552
+ * Build a single line of a Prisma model field definition based on a SchemaColumn, including type and modifiers.
553
+ *
554
+ * @param column
555
+ * @returns
556
+ */
557
+ const buildFieldLine = (column) => {
558
+ if (column.type === "id") {
559
+ const primary = column.primary === false ? "" : " @id";
560
+ const mapped = typeof column.map === "string" && column.map.trim().length > 0 ? ` @map("${column.map.replace(/"/g, "\\\"")}")` : "";
561
+ const configuredDefault = formatDefaultValue(column.default);
562
+ const shouldAutoIncrement = column.autoIncrement ?? column.primary !== false;
563
+ const defaultSuffix = configuredDefault ? ` ${configuredDefault}` : shouldAutoIncrement && primary ? " @default(autoincrement())" : "";
564
+ return ` ${column.name} Int${primary}${defaultSuffix}${mapped}`;
565
+ }
566
+ const scalar = resolvePrismaType(column);
567
+ const nullable = column.nullable ? "?" : "";
568
+ const unique = column.unique ? " @unique" : "";
569
+ const primary = column.primary ? " @id" : "";
570
+ const mapped = typeof column.map === "string" && column.map.trim().length > 0 ? ` @map("${column.map.replace(/"/g, "\\\"")}")` : "";
571
+ const defaultValue = formatDefaultValue(column.default) ?? (column.type === "uuid" && column.primary ? "@default(uuid())" : void 0);
572
+ const defaultSuffix = defaultValue ? ` ${defaultValue}` : "";
573
+ return ` ${column.name} ${scalar}${nullable}${primary}${unique}${defaultSuffix}${mapped}`;
574
+ };
575
+ /**
576
+ * Build a Prisma model-level @@index definition line.
577
+ *
578
+ * @param index
579
+ * @returns
580
+ */
581
+ const buildIndexLine = (index) => {
582
+ return ` @@index([${index.columns.join(", ")}]${typeof index.name === "string" && index.name.trim().length > 0 ? `, name: "${index.name.replace(/"/g, "\\\"")}"` : ""})`;
583
+ };
584
+ /**
585
+ * Build a Prisma model block string based on a SchemaTableCreateOperation, including
586
+ * all fields and any necessary mapping.
587
+ *
588
+ * @param operation The schema table create operation to convert.
589
+ * @returns The corresponding Prisma model block string.
590
+ */
591
+ const buildModelBlock = (operation) => {
592
+ const modelName = toModelName(operation.table);
593
+ const mapped = operation.table !== modelName.toLowerCase();
594
+ const fields = operation.columns.map(buildFieldLine);
595
+ const metadata = [...(operation.indexes ?? []).map(buildIndexLine), ...mapped ? [` @@map("${str(operation.table).snake()}")`] : []];
596
+ return `model ${modelName} {\n${(metadata.length > 0 ? [
597
+ ...fields,
598
+ "",
599
+ ...metadata
600
+ ] : fields).join("\n")}\n}`;
601
+ };
602
+ /**
603
+ * Find the Prisma model block in a schema string that corresponds to a given
604
+ * table name, using both explicit mapping and naming conventions.
605
+ *
606
+ * @param schema
607
+ * @param table
608
+ * @returns
609
+ */
610
+ const findModelBlock = (schema, table) => {
611
+ const candidates = [...schema.matchAll(PRISMA_MODEL_REGEX)];
612
+ const explicitMapRegex = new RegExp(`@@map\\("${escapeRegex(table)}"\\)`);
613
+ for (const match of candidates) {
614
+ const block = match[0];
615
+ const modelName = match[1];
616
+ const start = match.index ?? 0;
617
+ const end = start + block.length;
618
+ if (explicitMapRegex.test(block)) return {
619
+ modelName,
620
+ block,
621
+ start,
622
+ end
623
+ };
624
+ if (modelName.toLowerCase() === table.toLowerCase()) return {
625
+ modelName,
626
+ block,
627
+ start,
628
+ end
629
+ };
630
+ if (modelName.toLowerCase() === toModelName(table).toLowerCase()) return {
631
+ modelName,
632
+ block,
633
+ start,
634
+ end
635
+ };
636
+ }
637
+ return null;
638
+ };
639
+ /**
640
+ * Apply a create table operation to a Prisma schema string, adding a new model
641
+ * block for the specified table and fields.
642
+ *
643
+ * @param schema The current Prisma schema string.
644
+ * @param operation The schema table create operation to apply.
645
+ * @returns The updated Prisma schema string with the new model block.
646
+ */
647
+ const applyCreateTableOperation = (schema, operation) => {
648
+ if (findModelBlock(schema, operation.table)) throw new ArkormException(`Prisma model for table [${operation.table}] already exists.`);
649
+ const block = buildModelBlock(operation);
650
+ return `${schema.trimEnd()}\n\n${block}\n`;
651
+ };
652
+ /**
653
+ * Apply an alter table operation to a Prisma schema string, modifying the model
654
+ * block for the specified table by adding and removing fields as needed.
655
+ *
656
+ * @param schema The current Prisma schema string.
657
+ * @param operation The schema table alter operation to apply.
658
+ * @returns The updated Prisma schema string with the modified model block.
659
+ */
660
+ const applyAlterTableOperation = (schema, operation) => {
661
+ const model = findModelBlock(schema, operation.table);
662
+ if (!model) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
663
+ let block = model.block;
664
+ const bodyLines = block.split("\n");
665
+ operation.dropColumns.forEach((column) => {
666
+ const columnRegex = new RegExp(`^\\s*${escapeRegex(column)}\\s+`);
667
+ for (let index = 0; index < bodyLines.length; index += 1) if (columnRegex.test(bodyLines[index])) {
668
+ bodyLines.splice(index, 1);
669
+ return;
670
+ }
671
+ });
672
+ operation.addColumns.forEach((column) => {
673
+ const fieldLine = buildFieldLine(column);
674
+ const columnRegex = new RegExp(`^\\s*${escapeRegex(column.name)}\\s+`);
675
+ if (bodyLines.some((line) => columnRegex.test(line))) return;
676
+ const defaultInsertIndex = Math.max(1, bodyLines.length - 1);
677
+ const afterInsertIndex = typeof column.after === "string" && column.after.length > 0 ? bodyLines.findIndex((line) => new RegExp(`^\\s*${escapeRegex(column.after)}\\s+`).test(line)) : -1;
678
+ const insertIndex = afterInsertIndex > 0 ? Math.min(afterInsertIndex + 1, defaultInsertIndex) : defaultInsertIndex;
679
+ bodyLines.splice(insertIndex, 0, fieldLine);
680
+ });
681
+ (operation.addIndexes ?? []).forEach((index) => {
682
+ const indexLine = buildIndexLine(index);
683
+ if (bodyLines.some((line) => line.trim() === indexLine.trim())) return;
684
+ const insertIndex = Math.max(1, bodyLines.length - 1);
685
+ bodyLines.splice(insertIndex, 0, indexLine);
686
+ });
687
+ block = bodyLines.join("\n");
688
+ return `${schema.slice(0, model.start)}${block}${schema.slice(model.end)}`;
689
+ };
690
+ /**
691
+ * Apply a drop table operation to a Prisma schema string, removing the model block
692
+ * for the specified table.
693
+ */
694
+ const applyDropTableOperation = (schema, operation) => {
695
+ const model = findModelBlock(schema, operation.table);
696
+ if (!model) return schema;
697
+ const before = schema.slice(0, model.start).trimEnd();
698
+ const after = schema.slice(model.end).trimStart();
699
+ return `${before}${before && after ? "\n\n" : ""}${after}`;
700
+ };
701
+ /**
702
+ * The SchemaBuilder class provides a fluent interface for defining
703
+ * database schema operations in a migration, such as creating, altering, and
704
+ * dropping tables.
705
+ *
706
+ * @param schema The current Prisma schema string.
707
+ * @param operations The list of schema operations to apply.
708
+ * @returns The updated Prisma schema string after applying all operations.
709
+ */
710
+ const applyOperationsToPrismaSchema = (schema, operations) => {
711
+ return operations.reduce((current, operation) => {
712
+ if (operation.type === "createTable") return applyCreateTableOperation(current, operation);
713
+ if (operation.type === "alterTable") return applyAlterTableOperation(current, operation);
714
+ return applyDropTableOperation(current, operation);
715
+ }, schema);
716
+ };
717
+ /**
718
+ * Run a Prisma CLI command using npx, capturing and throwing any errors that occur.
719
+ *
720
+ * @param args The arguments to pass to the Prisma CLI command.
721
+ * @param cwd The current working directory to run the command in.
722
+ * @returns void
723
+ */
724
+ const runPrismaCommand = (args, cwd) => {
725
+ const command = spawnSync("npx", ["prisma", ...args], {
726
+ cwd,
727
+ encoding: "utf-8"
728
+ });
729
+ if (command.status === 0) return;
730
+ const errorOutput = [command.stdout, command.stderr].filter(Boolean).join("\n").trim();
731
+ throw new ArkormException(errorOutput ? `Prisma command failed: prisma ${args.join(" ")}\n${errorOutput}` : `Prisma command failed: prisma ${args.join(" ")}`);
732
+ };
733
+ /**
734
+ * Generate a new migration file with a given name and options, including
735
+ * writing the file to disk if specified.
736
+ *
737
+ * @param name
738
+ * @returns
739
+ */
740
+ const resolveMigrationClassName = (name) => {
741
+ const cleaned = name.replace(/[^a-zA-Z0-9]+/g, " ").trim();
742
+ if (!cleaned) return "GeneratedMigration";
743
+ return `${cleaned.split(/\s+/g).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("")}Migration`;
744
+ };
745
+ /**
746
+ * Pad a number with leading zeros to ensure it is at least two digits, for
747
+ * use in migration timestamps.
748
+ *
749
+ * @param value
750
+ * @returns
751
+ */
752
+ const pad = (value) => String(value).padStart(2, "0");
753
+ /**
754
+ * Create a timestamp string in the format YYYYMMDDHHMMSS for use in migration
755
+ * file names, based on the current date and time or a provided date.
756
+ *
757
+ * @param date
758
+ * @returns
759
+ */
760
+ const createMigrationTimestamp = (date = /* @__PURE__ */ new Date()) => {
761
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
762
+ };
763
+ /**
764
+ * Convert a migration name to a slug suitable for use in a file name, by
765
+ * lowercasing and replacing non-alphanumeric characters with underscores.
766
+ *
767
+ * @param name
768
+ * @returns
769
+ */
770
+ const toMigrationFileSlug = (name) => {
771
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration";
772
+ };
773
+ /**
774
+ * Build the source code for a new migration file based on a given class
775
+ * name, using a template with empty up and down methods.
776
+ *
777
+ * @param className
778
+ * @returns
779
+ */
780
+ const buildMigrationSource = (className, extension = "ts") => {
781
+ if (extension === "js") return [
782
+ "import { Migration } from 'arkormx'",
783
+ "",
784
+ `export default class ${className} extends Migration {`,
785
+ " /**",
786
+ " * @param {import('arkormx').SchemaBuilder} schema",
787
+ " * @returns {Promise<void>}",
788
+ " */",
789
+ " async up (schema) {",
790
+ " }",
791
+ "",
792
+ " /**",
793
+ " * @param {import('arkormx').SchemaBuilder} schema",
794
+ " * @returns {Promise<void>}",
795
+ " */",
796
+ " async down (schema) {",
797
+ " }",
798
+ "}",
799
+ ""
800
+ ].join("\n");
801
+ return [
802
+ "import { Migration, SchemaBuilder } from 'arkormx'",
803
+ "",
804
+ `export default class ${className} extends Migration {`,
805
+ " public async up (schema: SchemaBuilder): Promise<void> {",
806
+ " }",
807
+ "",
808
+ " public async down (schema: SchemaBuilder): Promise<void> {",
809
+ " }",
810
+ "}",
811
+ ""
812
+ ].join("\n");
813
+ };
814
+ /**
815
+ * Generate a new migration file with a given name and options, including
816
+ * writing the file to disk if specified, and return the details of the generated file.
817
+ *
818
+ * @param name
819
+ * @param options
820
+ * @returns
821
+ */
822
+ const generateMigrationFile = (name, options = {}) => {
823
+ const timestamp = createMigrationTimestamp(/* @__PURE__ */ new Date());
824
+ const fileSlug = toMigrationFileSlug(name);
825
+ const className = resolveMigrationClassName(name);
826
+ const extension = options.extension ?? "ts";
827
+ const directory = options.directory ?? join$1(process.cwd(), "database", "migrations");
828
+ const fileName = `${timestamp}_${fileSlug}.${extension}`;
829
+ const filePath = join$1(directory, fileName);
830
+ const content = buildMigrationSource(className, extension);
831
+ if (options.write ?? true) {
832
+ if (!existsSync$1(directory)) mkdirSync$1(directory, { recursive: true });
833
+ if (existsSync$1(filePath)) throw new ArkormException(`Migration file already exists: ${filePath}`);
834
+ writeFileSync$1(filePath, content);
835
+ }
836
+ return {
837
+ fileName,
838
+ filePath,
839
+ className,
840
+ content
841
+ };
842
+ };
843
+ /**
844
+ * Get the list of schema operations that would be performed by a given migration class when run in a specified direction (up or down), without actually applying them.
845
+ *
846
+ * @param migration The migration class or instance to analyze.
847
+ * @param direction The direction of the migration to plan for ('up' or 'down').
848
+ * @returns A promise that resolves to an array of schema operations that would be performed.
849
+ */
850
+ const getMigrationPlan = async (migration, direction = "up") => {
851
+ const instance = migration instanceof Migration ? migration : new migration();
852
+ const schema = new SchemaBuilder();
853
+ if (direction === "up") await instance.up(schema);
854
+ else await instance.down(schema);
855
+ return schema.getOperations();
856
+ };
857
+ /**
858
+ * Apply the schema operations defined in a migration to a Prisma schema
859
+ * file, updating the file on disk if specified, and return the updated
860
+ * schema and list of operations applied.
861
+ *
862
+ * @param migration The migration class or instance to apply.
863
+ * @param options Options for applying the migration, including schema path and write flag.
864
+ * @returns A promise that resolves to an object containing the updated schema, schema path, and list of operations applied.
865
+ */
866
+ const applyMigrationToPrismaSchema = async (migration, options = {}) => {
867
+ const schemaPath = options.schemaPath ?? join$1(process.cwd(), "prisma", "schema.prisma");
868
+ if (!existsSync$1(schemaPath)) throw new ArkormException(`Prisma schema file not found: ${schemaPath}`);
869
+ const source = readFileSync$1(schemaPath, "utf-8");
870
+ const operations = await getMigrationPlan(migration, "up");
871
+ const schema = applyOperationsToPrismaSchema(source, operations);
872
+ if (options.write ?? true) writeFileSync$1(schemaPath, schema);
873
+ return {
874
+ schema,
875
+ schemaPath,
876
+ operations
877
+ };
878
+ };
879
+ /**
880
+ * Run a migration by applying its schema operations to a Prisma schema
881
+ * file, optionally generating Prisma client code and running migrations after
882
+ * applying the schema changes.
883
+ *
884
+ * @param migration The migration class or instance to run.
885
+ * @param options Options for running the migration, including schema path, write flag, and Prisma commands.
886
+ * @returns A promise that resolves to an object containing the schema path and list of operations applied.
887
+ */
888
+ const runMigrationWithPrisma = async (migration, options = {}) => {
889
+ const cwd = options.cwd ?? process.cwd();
890
+ const applied = await applyMigrationToPrismaSchema(migration, {
891
+ schemaPath: options.schemaPath ?? join$1(cwd, "prisma", "schema.prisma"),
892
+ write: options.write
893
+ });
894
+ const shouldGenerate = options.runGenerate ?? true;
895
+ const shouldMigrate = options.runMigrate ?? true;
896
+ const mode = options.migrateMode ?? "dev";
897
+ if (shouldGenerate) runPrismaCommand(["generate"], cwd);
898
+ if (shouldMigrate) if (mode === "deploy") runPrismaCommand(["migrate", "deploy"], cwd);
899
+ else runPrismaCommand([
900
+ "migrate",
901
+ "dev",
902
+ "--name",
903
+ options.migrationName ?? `arkorm_${createMigrationTimestamp()}`
904
+ ], cwd);
905
+ return {
906
+ schemaPath: applied.schemaPath,
907
+ operations: applied.operations
908
+ };
909
+ };
910
+
911
+ //#endregion
912
+ //#region src/helpers/runtime-config.ts
913
+ const resolveDefaultStubsPath = () => {
914
+ let current = path.dirname(fileURLToPath(import.meta.url));
915
+ while (true) {
916
+ const packageJsonPath = path.join(current, "package.json");
917
+ const stubsPath = path.join(current, "stubs");
918
+ if (existsSync(packageJsonPath) && existsSync(stubsPath)) return stubsPath;
919
+ const parent = path.dirname(current);
920
+ if (parent === current) break;
921
+ current = parent;
922
+ }
923
+ return path.join(process.cwd(), "stubs");
924
+ };
925
+ const baseConfig = {
926
+ paths: {
927
+ stubs: resolveDefaultStubsPath(),
928
+ seeders: path.join(process.cwd(), "database", "seeders"),
929
+ models: path.join(process.cwd(), "src", "models"),
930
+ migrations: path.join(process.cwd(), "database", "migrations"),
931
+ factories: path.join(process.cwd(), "database", "factories"),
932
+ buildOutput: path.join(process.cwd(), "dist")
933
+ },
934
+ outputExt: "ts"
935
+ };
936
+ const userConfig = {
937
+ ...baseConfig,
938
+ paths: { ...baseConfig.paths ?? {} }
939
+ };
940
+ let runtimeConfigLoaded = false;
941
+ let runtimeConfigLoadingPromise;
942
+ let runtimeClientResolver;
943
+ let runtimePaginationURLDriverFactory;
944
+ const mergePathConfig = (paths) => {
945
+ const defaults = baseConfig.paths ?? {};
946
+ const current = userConfig.paths ?? {};
947
+ const incoming = Object.entries(paths ?? {}).reduce((all, [key, value]) => {
948
+ if (typeof value === "string" && value.trim().length > 0) all[key] = path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
949
+ return all;
950
+ }, {});
951
+ return {
952
+ ...defaults,
953
+ ...current,
954
+ ...incoming
955
+ };
956
+ };
957
+ /**
958
+ * Define the ArkORM runtime configuration. This function can be used to provide.
959
+ *
960
+ * @param config The ArkORM configuration object.
961
+ * @returns The same configuration object.
962
+ */
963
+ const defineConfig = (config) => {
964
+ return config;
965
+ };
966
+ /**
967
+ * Get the user-provided ArkORM configuration.
968
+ *
969
+ * @returns The user-provided ArkORM configuration object.
970
+ */
971
+ const getUserConfig = (key) => {
972
+ if (key) return userConfig[key];
973
+ return userConfig;
974
+ };
975
+ /**
976
+ * Configure the ArkORM runtime with the provided Prisma client resolver and
977
+ * delegate mapping resolver.
978
+ *
979
+ * @param prisma
980
+ * @param mapping
981
+ */
982
+ const configureArkormRuntime = (prisma, options = {}) => {
983
+ const nextConfig = {
984
+ ...userConfig,
985
+ prisma,
986
+ paths: mergePathConfig(options.paths)
987
+ };
988
+ if (options.pagination !== void 0) nextConfig.pagination = options.pagination;
989
+ if (options.outputExt !== void 0) nextConfig.outputExt = options.outputExt;
990
+ Object.assign(userConfig, { ...nextConfig });
991
+ runtimeClientResolver = prisma;
992
+ runtimePaginationURLDriverFactory = nextConfig.pagination?.urlDriver;
993
+ };
994
+ /**
995
+ * Reset the ArkORM runtime configuration.
996
+ * This is primarily intended for testing purposes.
997
+ */
998
+ const resetArkormRuntimeForTests = () => {
999
+ Object.assign(userConfig, {
1000
+ ...baseConfig,
1001
+ paths: { ...baseConfig.paths ?? {} }
1002
+ });
1003
+ runtimeConfigLoaded = false;
1004
+ runtimeConfigLoadingPromise = void 0;
1005
+ runtimeClientResolver = void 0;
1006
+ runtimePaginationURLDriverFactory = void 0;
1007
+ };
1008
+ /**
1009
+ * Resolve a Prisma client instance from the provided resolver, which can be either
1010
+ * a direct client instance or a function that returns a client instance.
1011
+ *
1012
+ * @param resolver
1013
+ * @returns
1014
+ */
1015
+ const resolveClient = (resolver) => {
1016
+ if (!resolver) return void 0;
1017
+ const client = typeof resolver === "function" ? resolver() : resolver;
1018
+ if (!client || typeof client !== "object") return void 0;
1019
+ return client;
1020
+ };
1021
+ /**
1022
+ * Resolve and apply the ArkORM configuration from an imported module.
1023
+ * This function checks for a default export and falls back to the module itself, then validates
1024
+ * the configuration object and applies it to the runtime if valid.
1025
+ *
1026
+ * @param imported
1027
+ * @returns
1028
+ */
1029
+ const resolveAndApplyConfig = (imported) => {
1030
+ const config = imported?.default ?? imported;
1031
+ if (!config || typeof config !== "object" || !config.prisma) return;
1032
+ configureArkormRuntime(config.prisma, {
1033
+ pagination: config.pagination,
1034
+ paths: config.paths,
1035
+ outputExt: config.outputExt
1036
+ });
1037
+ runtimeConfigLoaded = true;
1038
+ };
1039
+ /**
1040
+ * Dynamically import a configuration file.
1041
+ * A cache-busting query parameter is appended to ensure the latest version is loaded.
1042
+ *
1043
+ * @param configPath
1044
+ * @returns A promise that resolves to the imported configuration module.
1045
+ */
1046
+ const importConfigFile = (configPath) => {
1047
+ return import(`${pathToFileURL(configPath).href}?arkorm_runtime=${Date.now()}`);
1048
+ };
1049
+ const loadRuntimeConfigSync = () => {
1050
+ const require = createRequire(import.meta.url);
1051
+ const syncConfigPaths = [path.join(process.cwd(), "arkormx.config.cjs")];
1052
+ for (const configPath of syncConfigPaths) {
1053
+ if (!existsSync(configPath)) continue;
1054
+ try {
1055
+ resolveAndApplyConfig(require(configPath));
1056
+ return true;
1057
+ } catch {
1058
+ continue;
1059
+ }
1060
+ }
1061
+ return false;
1062
+ };
1063
+ /**
1064
+ * Load the ArkORM configuration by searching for configuration files in the
1065
+ * current working directory.
1066
+ * @returns
1067
+ */
1068
+ const loadArkormConfig = async () => {
1069
+ if (runtimeConfigLoaded) return;
1070
+ if (runtimeConfigLoadingPromise) return await runtimeConfigLoadingPromise;
1071
+ if (loadRuntimeConfigSync()) return;
1072
+ runtimeConfigLoadingPromise = (async () => {
1073
+ const configPaths = [path.join(process.cwd(), "arkormx.config.js"), path.join(process.cwd(), "arkormx.config.ts")];
1074
+ for (const configPath of configPaths) {
1075
+ if (!existsSync(configPath)) continue;
1076
+ try {
1077
+ resolveAndApplyConfig(await importConfigFile(configPath));
1078
+ return;
1079
+ } catch {
1080
+ continue;
1081
+ }
1082
+ }
1083
+ runtimeConfigLoaded = true;
1084
+ })();
1085
+ await runtimeConfigLoadingPromise;
1086
+ };
1087
+ /**
1088
+ * Ensure that the ArkORM configuration is loaded.
1089
+ * This function can be called to trigger the loading process if it hasn't already been initiated.
1090
+ * If the configuration is already loaded, it will return immediately.
1091
+ *
1092
+ * @returns
1093
+ */
1094
+ const ensureArkormConfigLoading = () => {
1095
+ if (runtimeConfigLoaded) return;
1096
+ if (!runtimeConfigLoadingPromise) loadArkormConfig();
1097
+ };
1098
+ const getDefaultStubsPath = () => {
1099
+ return resolveDefaultStubsPath();
1100
+ };
1101
+ /**
1102
+ * Get the runtime Prisma client.
1103
+ * This function will trigger the loading of the ArkORM configuration if
1104
+ * it hasn't already been loaded.
1105
+ *
1106
+ * @returns
1107
+ */
1108
+ const getRuntimePrismaClient = () => {
1109
+ if (!runtimeConfigLoaded) loadRuntimeConfigSync();
1110
+ return resolveClient(runtimeClientResolver);
1111
+ };
1112
+ /**
1113
+ * Get the configured pagination URL driver factory from runtime config.
1114
+ *
1115
+ * @returns
1116
+ */
1117
+ const getRuntimePaginationURLDriverFactory = () => {
1118
+ if (!runtimeConfigLoaded) loadRuntimeConfigSync();
1119
+ return runtimePaginationURLDriverFactory;
1120
+ };
1121
+ /**
1122
+ * Check if a given value is a Prisma delegate-like object
1123
+ * by verifying the presence of common delegate methods.
1124
+ *
1125
+ * @param value The value to check.
1126
+ * @returns True if the value is a Prisma delegate-like object, false otherwise.
1127
+ */
1128
+ const isDelegateLike = (value) => {
1129
+ if (!value || typeof value !== "object") return false;
1130
+ const candidate = value;
1131
+ return [
1132
+ "findMany",
1133
+ "findFirst",
1134
+ "create",
1135
+ "update",
1136
+ "delete",
1137
+ "count"
1138
+ ].every((method) => typeof candidate[method] === "function");
1139
+ };
1140
+ loadArkormConfig();
1141
+
1142
+ //#endregion
1143
+ //#region src/cli/CliApp.ts
1144
+ /**
1145
+ * Main application class for the Arkormˣ CLI.
1146
+ *
1147
+ * @author Legacy (3m1n3nc3)
1148
+ * @since 0.1.0
1149
+ */
1150
+ var CliApp = class {
1151
+ command;
1152
+ config = {};
1153
+ constructor() {
1154
+ this.config = getUserConfig();
1155
+ }
1156
+ /**
1157
+ * Get the current configuration object or a specific configuration value.
1158
+ *
1159
+ * @param key Optional specific configuration key to retrieve
1160
+ * @returns The entire configuration object or the value of the specified key
1161
+ */
1162
+ getConfig = getUserConfig;
1163
+ /**
1164
+ * Utility to ensure directory exists
1165
+ *
1166
+ * @param filePath
1167
+ */
1168
+ ensureDirectory(filePath) {
1169
+ const dir = dirname(filePath);
1170
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
1171
+ }
1172
+ /**
1173
+ * Convert absolute paths under current working directory into relative display paths.
1174
+ *
1175
+ * @param filePath
1176
+ * @returns
1177
+ */
1178
+ formatPathForLog(filePath) {
1179
+ const relPath = relative(process.cwd(), filePath);
1180
+ if (!relPath) return ".";
1181
+ if (relPath.startsWith("..")) return filePath;
1182
+ return relPath;
1183
+ }
1184
+ /**
1185
+ * Utility to format a value for logging, converting absolute paths under current
1186
+ * working directory into relative display paths.
1187
+ *
1188
+ * @param name
1189
+ * @param value
1190
+ * @returns
1191
+ */
1192
+ splitLogger(name, value) {
1193
+ value = value.includes(process.cwd()) ? this.formatPathForLog(value) : value;
1194
+ return Logger.twoColumnDetail(name + " ", " " + value, false).join("");
1195
+ }
1196
+ hasTypeScriptInstalled() {
1197
+ try {
1198
+ createRequire(import.meta.url).resolve("typescript", { paths: [process.cwd()] });
1199
+ return true;
1200
+ } catch {
1201
+ return false;
1202
+ }
1203
+ }
1204
+ resolveOutputExt() {
1205
+ const preferred = this.getConfig("outputExt") === "js" ? "js" : "ts";
1206
+ if (preferred === "ts" && !this.hasTypeScriptInstalled()) return "js";
1207
+ return preferred;
1208
+ }
1209
+ stripKnownSourceExtension(value) {
1210
+ return value.replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "");
1211
+ }
1212
+ /**
1213
+ * Resolve a directory path to runtime output when the source path is unavailable.
1214
+ *
1215
+ * @param directoryPath
1216
+ * @returns
1217
+ */
1218
+ resolveRuntimeDirectoryPath(directoryPath) {
1219
+ if (existsSync(directoryPath)) return directoryPath;
1220
+ const { buildOutput } = this.getConfig("paths") || {};
1221
+ if (typeof buildOutput !== "string" || buildOutput.trim().length === 0) return directoryPath;
1222
+ const relativeSource = relative(process.cwd(), directoryPath);
1223
+ if (!relativeSource || relativeSource.startsWith("..")) return directoryPath;
1224
+ const mappedDirectory = join(buildOutput, relativeSource);
1225
+ return existsSync(mappedDirectory) ? mappedDirectory : directoryPath;
1226
+ }
1227
+ /**
1228
+ * Resolve a script file path for runtime execution.
1229
+ * If a .ts file is provided, tries equivalent .js/.cjs/.mjs files first.
1230
+ * Also attempts mapped paths inside paths.buildOutput preserving structure.
1231
+ *
1232
+ * @param filePath
1233
+ * @returns
1234
+ */
1235
+ resolveRuntimeScriptPath(filePath) {
1236
+ const extension = extname(filePath).toLowerCase();
1237
+ const isTsFile = extension === ".ts" || extension === ".mts" || extension === ".cts";
1238
+ const candidates = [];
1239
+ if (isTsFile) {
1240
+ const base = filePath.slice(0, -extension.length);
1241
+ candidates.push(`${base}.js`, `${base}.cjs`, `${base}.mjs`);
1242
+ }
1243
+ const { buildOutput } = this.getConfig("paths") ?? {};
1244
+ if (typeof buildOutput === "string" && buildOutput.trim().length > 0) {
1245
+ const relativeSource = relative(process.cwd(), filePath);
1246
+ if (relativeSource && !relativeSource.startsWith("..")) {
1247
+ const mappedFile = join(buildOutput, relativeSource);
1248
+ const mappedExtension = extname(mappedFile).toLowerCase();
1249
+ if (mappedExtension === ".ts" || mappedExtension === ".mts" || mappedExtension === ".cts") {
1250
+ const mappedBase = mappedFile.slice(0, -mappedExtension.length);
1251
+ candidates.push(`${mappedBase}.js`, `${mappedBase}.cjs`, `${mappedBase}.mjs`);
1252
+ } else candidates.push(mappedFile);
1253
+ }
1254
+ }
1255
+ const runtimeMatch = candidates.find((path) => existsSync(path));
1256
+ if (runtimeMatch) return runtimeMatch;
1257
+ return filePath;
1258
+ }
1259
+ /**
1260
+ * Utility to generate file from stub
1261
+ *
1262
+ * @param stubPath
1263
+ * @param outputPath
1264
+ * @param replacements
1265
+ */
1266
+ generateFile(stubPath, outputPath, replacements, options) {
1267
+ if (existsSync(outputPath) && !options?.force) {
1268
+ this.command.error(`Error: ${this.formatPathForLog(outputPath)} already exists.`);
1269
+ process.exit(1);
1270
+ } else if (existsSync(outputPath) && options?.force) rmSync(outputPath);
1271
+ let content = readFileSync(stubPath, "utf-8");
1272
+ for (const [key, value] of Object.entries(replacements)) content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
1273
+ this.ensureDirectory(outputPath);
1274
+ writeFileSync(outputPath, content);
1275
+ return outputPath;
1276
+ }
1277
+ /**
1278
+ * Resolve a configuration path with a fallback default
1279
+ *
1280
+ * @param key The configuration key to resolve
1281
+ * @param fallback The fallback value if the configuration key is not set
1282
+ * @returns The resolved configuration path
1283
+ */
1284
+ resolveConfigPath(key, fallback) {
1285
+ const { [key]: configured } = this.getConfig("paths") ?? {};
1286
+ if (typeof configured === "string" && configured.trim().length > 0) return configured;
1287
+ return fallback;
1288
+ }
1289
+ /**
1290
+ * Resolve the path to a stub file based on configuration
1291
+ *
1292
+ * @param stubName
1293
+ * @returns
1294
+ */
1295
+ resolveStubPath(stubName) {
1296
+ return join(this.resolveConfigPath("stubs", getDefaultStubsPath()), stubName);
1297
+ }
1298
+ /**
1299
+ * Generate a factory file for a given model name.
1300
+ *
1301
+ * @param name
1302
+ * @param options
1303
+ * @returns
1304
+ */
1305
+ makeFactory(name, options = {}) {
1306
+ const baseName = str(name.replace(/Factory$/, "")).pascal();
1307
+ const factoryName = `${baseName}Factory`;
1308
+ const modelName = options.modelName ? str(options.modelName).pascal() : baseName;
1309
+ const outputExt = this.resolveOutputExt();
1310
+ const outputPath = join(this.resolveConfigPath("factories", join(process.cwd(), "database", "factories")), `${factoryName}.${outputExt}`);
1311
+ const modelPath = join(this.resolveConfigPath("models", join(process.cwd(), "src", "models")), `${modelName}.${outputExt}`);
1312
+ const relativeImport = options.modelImportPath ?? `./${this.stripKnownSourceExtension(relative(dirname(outputPath), modelPath).replace(/\\/g, "/"))}${outputExt === "js" ? ".js" : ""}`;
1313
+ const stubPath = this.resolveStubPath(outputExt === "js" ? "factory.js.stub" : "factory.stub");
1314
+ return {
1315
+ name: factoryName,
1316
+ path: this.generateFile(stubPath, outputPath, {
1317
+ FactoryName: factoryName,
1318
+ ModelName: modelName.toString(),
1319
+ ModelImportPath: relativeImport.startsWith(".") ? relativeImport : `./${relativeImport}`
1320
+ }, options)
1321
+ };
1322
+ }
1323
+ /**
1324
+ * Generate a seeder file for a given name.
1325
+ *
1326
+ * @param name
1327
+ * @param options
1328
+ * @returns
1329
+ */
1330
+ makeSeeder(name, options = {}) {
1331
+ const seederName = `${str(name.replace(/Seeder$/, "")).pascal()}Seeder`;
1332
+ const outputExt = this.resolveOutputExt();
1333
+ const outputPath = join(this.resolveConfigPath("seeders", join(process.cwd(), "database", "seeders")), `${seederName}.${outputExt}`);
1334
+ const stubPath = this.resolveStubPath(outputExt === "js" ? "seeder.js.stub" : "seeder.stub");
1335
+ return {
1336
+ name: seederName,
1337
+ path: this.generateFile(stubPath, outputPath, { SeederName: seederName }, options)
1338
+ };
1339
+ }
1340
+ /**
1341
+ * Generate a migration file for a given name.
1342
+ *
1343
+ * @param name The name of the migration.
1344
+ * @returns An object containing the name and path of the generated migration file.
1345
+ */
1346
+ makeMigration(name) {
1347
+ const generated = generateMigrationFile(name, {
1348
+ directory: this.resolveConfigPath("migrations", join(process.cwd(), "database", "migrations")),
1349
+ extension: this.resolveOutputExt()
1350
+ });
1351
+ return {
1352
+ name: generated.className,
1353
+ path: generated.filePath
1354
+ };
1355
+ }
1356
+ /**
1357
+ * Generate a model file along with optional factory, seeder, and migration files.
1358
+ *
1359
+ * @param name
1360
+ * @param options
1361
+ * @returns
1362
+ */
1363
+ makeModel(name, options = {}) {
1364
+ const baseName = str(name.replace(/Model$/, "")).pascal().toString();
1365
+ const modelName = `${baseName}`;
1366
+ const delegateName = str(baseName).camel().plural().toString();
1367
+ const outputExt = this.resolveOutputExt();
1368
+ const outputPath = join(this.resolveConfigPath("models", join(process.cwd(), "src", "models")), `${modelName}.${outputExt}`);
1369
+ const shouldBuildFactory = options.all || options.factory;
1370
+ const shouldBuildSeeder = options.all || options.seeder;
1371
+ const shouldBuildMigration = options.all || options.migration;
1372
+ const factoryName = `${baseName}Factory`;
1373
+ const factoryPath = join(this.resolveConfigPath("factories", join(process.cwd(), "database", "factories")), `${factoryName}.${outputExt}`);
1374
+ const factoryImportPath = `./${relative(dirname(outputPath), factoryPath).replace(/\\/g, "/").replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "")}${outputExt === "js" ? ".js" : ""}`;
1375
+ const stubPath = this.resolveStubPath(outputExt === "js" ? "model.js.stub" : "model.stub");
1376
+ const modelPath = this.generateFile(stubPath, outputPath, {
1377
+ ModelName: modelName,
1378
+ DelegateName: delegateName,
1379
+ FactoryImport: shouldBuildFactory ? `import { ${factoryName} } from '${factoryImportPath}'\n` : "",
1380
+ FactoryLink: shouldBuildFactory ? outputExt === "js" ? `\n static factoryClass = ${factoryName}` : `\n protected static override factoryClass = ${factoryName}` : ""
1381
+ }, options);
1382
+ const prisma = this.ensurePrismaModelEntry(modelName, delegateName);
1383
+ const created = {
1384
+ model: {
1385
+ name: modelName,
1386
+ path: modelPath
1387
+ },
1388
+ prisma,
1389
+ factory: void 0,
1390
+ seeder: void 0,
1391
+ migration: void 0
1392
+ };
1393
+ if (shouldBuildFactory) created.factory = this.makeFactory(baseName, {
1394
+ force: options.force,
1395
+ modelName,
1396
+ modelImportPath: `./${relative(dirname(factoryPath), outputPath).replace(/\\/g, "/").replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "")}${outputExt === "js" ? ".js" : ""}`
1397
+ });
1398
+ if (shouldBuildSeeder) created.seeder = this.makeSeeder(baseName, { force: options.force });
1399
+ if (shouldBuildMigration) created.migration = this.makeMigration(`create ${delegateName} table`);
1400
+ return created;
1401
+ }
1402
+ /**
1403
+ * Ensure that the Prisma schema has a model entry for the given model
1404
+ * and delegate names.
1405
+ * If the entry does not exist, it will be created with a default `id` field.
1406
+ *
1407
+ * @param modelName The name of the model to ensure in the Prisma schema.
1408
+ * @param delegateName The name of the delegate (table) to ensure in the Prisma schema.
1409
+ */
1410
+ ensurePrismaModelEntry(modelName, delegateName) {
1411
+ const schemaPath = join(process.cwd(), "prisma", "schema.prisma");
1412
+ if (!existsSync(schemaPath)) return {
1413
+ path: schemaPath,
1414
+ updated: false
1415
+ };
1416
+ const source = readFileSync(schemaPath, "utf-8");
1417
+ const existingByTable = findModelBlock(source, delegateName);
1418
+ const existingByName = new RegExp(`model\\s+${modelName}\\s*\\{`, "m").test(source);
1419
+ if (existingByTable || existingByName) return {
1420
+ path: schemaPath,
1421
+ updated: false
1422
+ };
1423
+ writeFileSync(schemaPath, applyCreateTableOperation(source, {
1424
+ type: "createTable",
1425
+ table: delegateName,
1426
+ columns: [{
1427
+ name: "id",
1428
+ type: "id",
1429
+ primary: true
1430
+ }]
1431
+ }));
1432
+ return {
1433
+ path: schemaPath,
1434
+ updated: true
1435
+ };
1436
+ }
1437
+ /**
1438
+ * Convert a Prisma scalar type to its corresponding TypeScript type.
1439
+ *
1440
+ * @param value The Prisma scalar type.
1441
+ * @returns The corresponding TypeScript type.
1442
+ */
1443
+ prismaTypeToTs(value) {
1444
+ if (value === "Int" || value === "Float" || value === "Decimal") return "number";
1445
+ if (value === "BigInt") return "bigint";
1446
+ if (value === "String") return "string";
1447
+ if (value === "Boolean") return "boolean";
1448
+ if (value === "DateTime") return "Date";
1449
+ if (value === "Json") return "Record<string, unknown>";
1450
+ if (value === "Bytes") return "Buffer";
1451
+ return "unknown";
1452
+ }
1453
+ /**
1454
+ * Parse the Prisma schema to extract model definitions and their fields, focusing
1455
+ * on scalar types.
1456
+ *
1457
+ * @param schema The Prisma schema as a string.
1458
+ * @returns An array of model definitions with their fields.
1459
+ */
1460
+ parsePrismaModels(schema) {
1461
+ const models = [];
1462
+ const modelRegex = /model\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
1463
+ const scalarTypes = new Set([
1464
+ "Int",
1465
+ "Float",
1466
+ "Decimal",
1467
+ "BigInt",
1468
+ "String",
1469
+ "Boolean",
1470
+ "DateTime",
1471
+ "Json",
1472
+ "Bytes"
1473
+ ]);
1474
+ for (const match of schema.matchAll(modelRegex)) {
1475
+ const name = match[1];
1476
+ const body = match[2];
1477
+ const table = body.match(/@@map\("([^"]+)"\)/)?.[1] ?? `${name.charAt(0).toLowerCase()}${name.slice(1)}s`;
1478
+ const fields = [];
1479
+ body.split("\n").forEach((rawLine) => {
1480
+ const line = rawLine.trim();
1481
+ if (!line || line.startsWith("@@") || line.startsWith("//")) return;
1482
+ const fieldMatch = line.match(/^(\w+)\s+([A-Za-z]+)(\?)?\b/);
1483
+ if (!fieldMatch) return;
1484
+ const fieldType = fieldMatch[2];
1485
+ if (!scalarTypes.has(fieldType)) return;
1486
+ fields.push({
1487
+ name: fieldMatch[1],
1488
+ type: this.prismaTypeToTs(fieldType),
1489
+ optional: Boolean(fieldMatch[3])
1490
+ });
1491
+ });
1492
+ models.push({
1493
+ name,
1494
+ table,
1495
+ fields
1496
+ });
1497
+ }
1498
+ return models;
1499
+ }
1500
+ /**
1501
+ * Sync model attribute declarations in a model file based on the
1502
+ * provided declarations.
1503
+ * This method takes the source code of a model file and a list of
1504
+ * attribute declarations,
1505
+ *
1506
+ * @param modelSource The source code of the model file.
1507
+ * @param declarations A list of attribute declarations to sync.
1508
+ * @returns An object containing the updated content and a flag indicating if it was updated.
1509
+ */
1510
+ syncModelDeclarations(modelSource, declarations) {
1511
+ const lines = modelSource.split("\n");
1512
+ const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model<.+>\s*\{/.test(line));
1513
+ if (classIndex < 0) return {
1514
+ content: modelSource,
1515
+ updated: false
1516
+ };
1517
+ let classEndIndex = -1;
1518
+ let depth = 0;
1519
+ for (let index = classIndex; index < lines.length; index += 1) {
1520
+ const line = lines[index];
1521
+ depth += (line.match(/\{/g) || []).length;
1522
+ depth -= (line.match(/\}/g) || []).length;
1523
+ if (depth === 0) {
1524
+ classEndIndex = index;
1525
+ break;
1526
+ }
1527
+ }
1528
+ if (classEndIndex < 0) return {
1529
+ content: modelSource,
1530
+ updated: false
1531
+ };
1532
+ const withoutDeclares = lines.slice(classIndex + 1, classEndIndex).filter((line) => !/^\s*declare\s+\w+\??:\s*[^\n]+$/.test(line));
1533
+ const rebuiltClass = [...declarations.map((declaration) => ` ${declaration}`), ...withoutDeclares];
1534
+ const content = [
1535
+ ...lines.slice(0, classIndex + 1),
1536
+ ...rebuiltClass,
1537
+ ...lines.slice(classEndIndex)
1538
+ ].join("\n");
1539
+ return {
1540
+ content,
1541
+ updated: content !== modelSource
1542
+ };
1543
+ }
1544
+ /**
1545
+ * Sync model attribute declarations in model files based on the Prisma schema.
1546
+ * This method reads the Prisma schema to extract model definitions and their
1547
+ * scalar fields, then updates the corresponding model files to include `declare`
1548
+ * statements for these fields. It returns an object containing the paths of the
1549
+ * schema and models, the total number of model files processed, and lists of
1550
+ * updated and skipped files.
1551
+ *
1552
+ * @param options Optional parameters to specify custom paths for the Prisma schema and models directory.
1553
+ * @returns An object with details about the synchronization process, including updated and skipped files.
1554
+ */
1555
+ syncModelsFromPrisma(options = {}) {
1556
+ const schemaPath = options.schemaPath ?? join(process.cwd(), "prisma", "schema.prisma");
1557
+ const modelsDir = options.modelsDir ?? this.resolveConfigPath("models", join(process.cwd(), "src", "models"));
1558
+ if (!existsSync(schemaPath)) throw new Error(`Prisma schema file not found: ${schemaPath}`);
1559
+ if (!existsSync(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
1560
+ const schema = readFileSync(schemaPath, "utf-8");
1561
+ const prismaModels = this.parsePrismaModels(schema);
1562
+ const modelFiles = readdirSync(modelsDir).filter((file) => file.endsWith(".ts"));
1563
+ const updated = [];
1564
+ const skipped = [];
1565
+ modelFiles.forEach((file) => {
1566
+ const filePath = join(modelsDir, file);
1567
+ const source = readFileSync(filePath, "utf-8");
1568
+ const classMatch = source.match(/export\s+class\s+(\w+)\s+extends\s+Model<'([^']+)'>/);
1569
+ if (!classMatch) {
1570
+ skipped.push(filePath);
1571
+ return;
1572
+ }
1573
+ const className = classMatch[1];
1574
+ const delegate = classMatch[2];
1575
+ const prismaModel = prismaModels.find((model) => model.table === delegate) ?? prismaModels.find((model) => model.name === className);
1576
+ if (!prismaModel || prismaModel.fields.length === 0) {
1577
+ skipped.push(filePath);
1578
+ return;
1579
+ }
1580
+ const declarations = prismaModel.fields.map((field) => `declare ${field.name}${field.optional ? "?" : ""}: ${field.type}`);
1581
+ const synced = this.syncModelDeclarations(source, declarations);
1582
+ if (!synced.updated) {
1583
+ skipped.push(filePath);
1584
+ return;
1585
+ }
1586
+ writeFileSync(filePath, synced.content);
1587
+ updated.push(filePath);
1588
+ });
1589
+ return {
1590
+ schemaPath,
1591
+ modelsDir,
1592
+ total: modelFiles.length,
1593
+ updated,
1594
+ skipped
1595
+ };
1596
+ }
1597
+ };
1598
+
1599
+ //#endregion
1600
+ //#region src/cli/commands/InitCommand.ts
1601
+ /**
1602
+ * The InitCommand class implements the CLI command for initializing Arkormˣ by creating
1603
+ * a default config file in the current directory.
1604
+ *
1605
+ * @author Legacy (3m1n3nc3)
1606
+ * @since 0.1.0
1607
+ */
1608
+ var InitCommand = class extends Command {
1609
+ signature = `init
13
1610
  {--force : Force overwrite if config file already exists (existing file will be backed up) }
14
- `;description=`Initialize Arkormˣ by creating a default config file in the current directory`;async handle(){this.app.command=this;let n=v(process.cwd(),`arkormx.config.js`),{stubs:i}=U(`paths`)??{},a=typeof i==`string`&&i.trim().length>0?i:K(),s=v(a,`arkormx.config.stub`),c=v(a,`arkorm.config.stub`),l=t(s)?s:c;t(n)&&!this.option(`force`)&&(this.error(`Error: Arkormˣ has already been initialized. Use --force to reinitialize.`),process.exit(1)),this.app.ensureDirectory(n),t(n)&&this.option(`force`)&&e(n,n.replace(/\.js$/,`.backup.${Date.now()}.js`)),t(l)||(this.error(`Error: Missing config stub at ${s} (or ${c})`),process.exit(1)),o(n,r(l,`utf-8`)),this.success(`Arkormˣ initialized successfully!`)}},Me=class extends x{signature=`make:factory
1611
+ `;
1612
+ description = "Initialize Arkormˣ by creating a default config file in the current directory";
1613
+ /**
1614
+ * Command handler for the init command.
1615
+ */
1616
+ async handle() {
1617
+ this.app.command = this;
1618
+ const outputDir = join$1(process.cwd(), "arkormx.config.js");
1619
+ const { stubs } = getUserConfig("paths") ?? {};
1620
+ const stubsDir = typeof stubs === "string" && stubs.trim().length > 0 ? stubs : getDefaultStubsPath();
1621
+ const preferredStubPath = join$1(stubsDir, "arkormx.config.stub");
1622
+ const legacyStubPath = join$1(stubsDir, "arkorm.config.stub");
1623
+ const stubPath = existsSync(preferredStubPath) ? preferredStubPath : legacyStubPath;
1624
+ if (existsSync(outputDir) && !this.option("force")) {
1625
+ this.error("Error: Arkormˣ has already been initialized. Use --force to reinitialize.");
1626
+ process.exit(1);
1627
+ }
1628
+ this.app.ensureDirectory(outputDir);
1629
+ if (existsSync(outputDir) && this.option("force")) copyFileSync(outputDir, outputDir.replace(/\.js$/, `.backup.${Date.now()}.js`));
1630
+ if (!existsSync(stubPath)) {
1631
+ this.error(`Error: Missing config stub at ${preferredStubPath} (or ${legacyStubPath})`);
1632
+ process.exit(1);
1633
+ }
1634
+ writeFileSync(outputDir, readFileSync(stubPath, "utf-8"));
1635
+ this.success("Arkormˣ initialized successfully!");
1636
+ }
1637
+ };
1638
+
1639
+ //#endregion
1640
+ //#region src/cli/commands/MakeFactoryCommand.ts
1641
+ /**
1642
+ * The MakeFactoryCommand class implements the CLI command for creating new factory classes.
1643
+ *
1644
+ * @author Legacy (3m1n3nc3)
1645
+ * @since 0.1.0
1646
+ */
1647
+ var MakeFactoryCommand = class extends Command {
1648
+ signature = `make:factory
15
1649
  {name : Name of the factory to create}
16
1650
  {--f|force : Overwrite existing file}
17
- `;description=`Create a new model factory class`;async handle(){this.app.command=this;let e=this.argument(`name`);if(!e)return void this.error(`Error: Name argument is required.`);let t=this.app.makeFactory(e,{force:this.option(`force`)});this.success(`Created factory: ${this.app.formatPathForLog(t.path)}`)}},Ne=class extends x{signature=`make:migration
1651
+ `;
1652
+ description = "Create a new model factory class";
1653
+ /**
1654
+ * Command handler for the make:factory command.
1655
+ *
1656
+ * @returns
1657
+ */
1658
+ async handle() {
1659
+ this.app.command = this;
1660
+ const name = this.argument("name");
1661
+ if (!name) return void this.error("Error: Name argument is required.");
1662
+ const created = this.app.makeFactory(name, { force: this.option("force") });
1663
+ this.success(`Created factory: ${this.app.formatPathForLog(created.path)}`);
1664
+ }
1665
+ };
1666
+
1667
+ //#endregion
1668
+ //#region src/cli/commands/MakeMigrationCommand.ts
1669
+ /**
1670
+ * The MakeMigrationCommand class implements the CLI command for creating new migration classes.
1671
+ *
1672
+ * @author Legacy (3m1n3nc3)
1673
+ * @since 0.1.0
1674
+ */
1675
+ var MakeMigrationCommand = class extends Command {
1676
+ signature = `make:migration
18
1677
  {name : Name of the migration to create}
19
- `;description=`Create a new migration class file`;async handle(){this.app.command=this;let e=this.argument(`name`);if(!e)return void this.error(`Error: Name argument is required.`);let t=this.app.makeMigration(e);this.success(`Created migration: ${this.app.formatPathForLog(t.path)}`)}},Pe=class extends x{signature=`make:model
1678
+ `;
1679
+ description = "Create a new migration class file";
1680
+ /**
1681
+ * Command handler for the make:migration command.
1682
+ *
1683
+ * @returns
1684
+ */
1685
+ async handle() {
1686
+ this.app.command = this;
1687
+ const name = this.argument("name");
1688
+ if (!name) return void this.error("Error: Name argument is required.");
1689
+ const created = this.app.makeMigration(name);
1690
+ this.success(`Created migration: ${this.app.formatPathForLog(created.path)}`);
1691
+ }
1692
+ };
1693
+
1694
+ //#endregion
1695
+ //#region src/cli/commands/MakeModelCommand.ts
1696
+ /**
1697
+ * The MakeModelCommand class implements the CLI command for creating new model
1698
+ * classes along with optional linked resources such as factories, seeders, and migrations.
1699
+ *
1700
+ * @author Legacy (3m1n3nc3)
1701
+ * @since 0.1.0
1702
+ */
1703
+ var MakeModelCommand = class extends Command {
1704
+ signature = `make:model
20
1705
  {name : Name of the model to create}
21
1706
  {--f|force : Overwrite existing files}
22
1707
  {--factory : Create and link a factory}
23
1708
  {--seeder : Create a seeder}
24
1709
  {--migration : Create a migration}
25
1710
  {--all : Create and link factory, seeder, and migration}
26
- `;description=`Create a new model and optional linked resources`;async handle(){this.app.command=this;let e=this.argument(`name`);if(!e)return void this.error(`Error: Name argument is required.`);let t=this.app.makeModel(e,this.options());this.success(`Created files:`),[[`Model`,t.model.path],[`Prisma schema ${t.prisma.updated?`(updated)`:`(already up to date)`}`,t.prisma.path],t.factory?[`Factory`,t.factory.path]:``,t.seeder?[`Seeder`,t.seeder.path]:``,t.migration?[`Migration`,t.migration.path]:``].filter(Boolean).map(([e,t])=>this.success(this.app.splitLogger(e,t)))}},Fe=class extends x{signature=`make:seeder
1711
+ `;
1712
+ description = "Create a new model and optional linked resources";
1713
+ /**
1714
+ * Command handler for the make:model command.
1715
+ *
1716
+ * @returns
1717
+ */
1718
+ async handle() {
1719
+ this.app.command = this;
1720
+ const name = this.argument("name");
1721
+ if (!name) return void this.error("Error: Name argument is required.");
1722
+ const created = this.app.makeModel(name, this.options());
1723
+ this.success("Created files:");
1724
+ [
1725
+ ["Model", created.model.path],
1726
+ [`Prisma schema ${created.prisma.updated ? "(updated)" : "(already up to date)"}`, created.prisma.path],
1727
+ created.factory ? ["Factory", created.factory.path] : "",
1728
+ created.seeder ? ["Seeder", created.seeder.path] : "",
1729
+ created.migration ? ["Migration", created.migration.path] : ""
1730
+ ].filter(Boolean).map(([name, path]) => this.success(this.app.splitLogger(name, path)));
1731
+ }
1732
+ };
1733
+
1734
+ //#endregion
1735
+ //#region src/cli/commands/MakeSeederCommand.ts
1736
+ /**
1737
+ * The MakeSeederCommand class implements the CLI command for creating new seeder classes.
1738
+ *
1739
+ * @author Legacy (3m1n3nc3)
1740
+ * @since 0.1.0
1741
+ */
1742
+ var MakeSeederCommand = class extends Command {
1743
+ signature = `make:seeder
27
1744
  {name : Name of the seeder to create}
28
1745
  {--f|force : Overwrite existing file}
29
- `;description=`Create a new seeder class`;async handle(){this.app.command=this;let e=this.argument(`name`);if(!e)return void this.error(`Error: Name argument is required.`);let t=this.app.makeSeeder(e,this.options());this.success(`Created seeder: ${this.app.formatPathForLog(t.path)}`)}},Ie=class extends x{signature=`migrate
1746
+ `;
1747
+ description = "Create a new seeder class";
1748
+ /**
1749
+ * Command handler for the make:seeder command.
1750
+ */
1751
+ async handle() {
1752
+ this.app.command = this;
1753
+ const name = this.argument("name");
1754
+ if (!name) return void this.error("Error: Name argument is required.");
1755
+ const created = this.app.makeSeeder(name, this.options());
1756
+ this.success(`Created seeder: ${this.app.formatPathForLog(created.path)}`);
1757
+ }
1758
+ };
1759
+
1760
+ //#endregion
1761
+ //#region src/cli/commands/MigrateCommand.ts
1762
+ /**
1763
+ * The MigrateCommand class implements the CLI command for applying migration
1764
+ * classes to the Prisma schema and running the Prisma workflow.
1765
+ *
1766
+ * @author Legacy (3m1n3nc3)
1767
+ * @since 0.1.0
1768
+ */
1769
+ var MigrateCommand = class extends Command {
1770
+ signature = `migrate
30
1771
  {name? : Migration class or file name}
31
1772
  {--all : Run all migrations from the configured migrations directory}
32
1773
  {--deploy : Use prisma migrate deploy instead of migrate dev}
@@ -34,17 +1775,2847 @@ import{copyFileSync as e,existsSync as t,mkdirSync as n,readFileSync as r,readdi
34
1775
  {--skip-migrate : Skip prisma migrate command}
35
1776
  {--schema= : Explicit prisma schema path}
36
1777
  {--migration-name= : Name for prisma migrate dev}
37
- `;description=`Apply migration classes to schema.prisma and run Prisma workflow`;async handle(){this.app.command=this;let e=this.app.getConfig(`paths`)?.migrations??v(process.cwd(),`database`,`migrations`),t=this.app.resolveRuntimeDirectoryPath(e);if(!p(t))return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(e)}`);let n=this.option(`schema`)?y(String(this.option(`schema`))):v(process.cwd(),`prisma`,`schema.prisma`),r=this.option(`all`)?await this.loadAllMigrations(t):(await this.loadNamedMigration(t,this.argument(`name`))).filter(([e])=>e!==void 0);if(r.length===0)return void this.error(`Error: No migration classes found to run.`);for(let[e]of r)await I(e,{schemaPath:n,write:!0});this.option(`skip-generate`)||N([`generate`],process.cwd()),this.option(`skip-migrate`)||(this.option(`deploy`)?N([`migrate`,`deploy`],process.cwd()):N([`migrate`,`dev`,`--name`,this.option(`migration-name`)?String(this.option(`migration-name`)):`arkorm_cli_${Date.now()}`],process.cwd())),this.success(`Applied ${r.length} migration(s).`),r.forEach(([e,t])=>this.success(this.app.splitLogger(`Migrated`,t)))}async loadAllMigrations(e){let t=g(e).filter(e=>/\.(ts|js|mjs|cjs)$/i.test(e)).sort((e,t)=>e.localeCompare(t)).map(t=>this.app.resolveRuntimeScriptPath(v(e,t)));return(await Promise.all(t.map(async e=>(await this.loadMigrationClassesFromFile(e)).map(t=>[t,e])))).flat()}async loadNamedMigration(e,t){if(!t)return[[void 0,``]];let n=t.replace(/Migration$/,``),r=[`${t}.ts`,`${t}.js`,`${t}.mjs`,`${t}.cjs`,`${n}Migration.ts`,`${n}Migration.js`,`${n}Migration.mjs`,`${n}Migration.cjs`].map(t=>v(e,t)).find(e=>p(e));if(!r)return[[void 0,t]];let i=this.app.resolveRuntimeScriptPath(r);return(await this.loadMigrationClassesFromFile(i)).map(e=>[e,i])}async loadMigrationClassesFromFile(e){let t=await import(`${ie(y(e)).href}?arkorm_migrate=${Date.now()}`);return Object.values(t).filter(e=>typeof e==`function`?e.prototype instanceof w:!1)}},Le=class extends x{signature=`models:sync
1778
+ `;
1779
+ description = "Apply migration classes to schema.prisma and run Prisma workflow";
1780
+ /**
1781
+ * Command handler for the migrate command.
1782
+ * This method is responsible for orchestrating the migration
1783
+ * process, including loading migration classes, applying them to
1784
+ * the Prisma schema, and running the appropriate Prisma commands
1785
+ * based on the provided options.
1786
+ *
1787
+ * @returns
1788
+ */
1789
+ async handle() {
1790
+ this.app.command = this;
1791
+ const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join$1(process.cwd(), "database", "migrations");
1792
+ const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
1793
+ if (!existsSync$1(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
1794
+ const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join$1(process.cwd(), "prisma", "schema.prisma");
1795
+ const classes = this.option("all") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
1796
+ if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
1797
+ for (const [MigrationClassItem] of classes) await applyMigrationToPrismaSchema(MigrationClassItem, {
1798
+ schemaPath,
1799
+ write: true
1800
+ });
1801
+ if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
1802
+ if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
1803
+ else runPrismaCommand([
1804
+ "migrate",
1805
+ "dev",
1806
+ "--name",
1807
+ this.option("migration-name") ? String(this.option("migration-name")) : `arkorm_cli_${Date.now()}`
1808
+ ], process.cwd());
1809
+ this.success(`Applied ${classes.length} migration(s).`);
1810
+ classes.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
1811
+ }
1812
+ /**
1813
+ * Load all migration classes from the specified directory.
1814
+ *
1815
+ * @param migrationsDir The directory to load migration classes from.
1816
+ */
1817
+ async loadAllMigrations(migrationsDir) {
1818
+ const files = readdirSync$1(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join$1(migrationsDir, file)));
1819
+ return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
1820
+ }
1821
+ /**
1822
+ * Load migration classes from a specific file or by class name.
1823
+ *
1824
+ * @param migrationsDir
1825
+ * @param name
1826
+ * @returns
1827
+ */
1828
+ async loadNamedMigration(migrationsDir, name) {
1829
+ if (!name) return [[void 0, ""]];
1830
+ const base = name.replace(/Migration$/, "");
1831
+ const target = [
1832
+ `${name}.ts`,
1833
+ `${name}.js`,
1834
+ `${name}.mjs`,
1835
+ `${name}.cjs`,
1836
+ `${base}Migration.ts`,
1837
+ `${base}Migration.js`,
1838
+ `${base}Migration.mjs`,
1839
+ `${base}Migration.cjs`
1840
+ ].map((file) => join$1(migrationsDir, file)).find((file) => existsSync$1(file));
1841
+ if (!target) return [[void 0, name]];
1842
+ const runtimeTarget = this.app.resolveRuntimeScriptPath(target);
1843
+ return (await this.loadMigrationClassesFromFile(runtimeTarget)).map((cls) => [cls, runtimeTarget]);
1844
+ }
1845
+ /**
1846
+ * Load migration classes from a given file path.
1847
+ *
1848
+ * @param filePath
1849
+ * @returns
1850
+ */
1851
+ async loadMigrationClassesFromFile(filePath) {
1852
+ const imported = await import(`${pathToFileURL$1(resolve(filePath)).href}?arkorm_migrate=${Date.now()}`);
1853
+ return Object.values(imported).filter((value) => {
1854
+ if (typeof value !== "function") return false;
1855
+ return value.prototype instanceof Migration;
1856
+ });
1857
+ }
1858
+ };
1859
+
1860
+ //#endregion
1861
+ //#region src/cli/commands/ModelsSyncCommand.ts
1862
+ var ModelsSyncCommand = class extends Command {
1863
+ signature = `models:sync
38
1864
  {--schema= : Path to prisma schema file}
39
1865
  {--models= : Path to models directory}
40
- `;description=`Sync model declare attributes from prisma schema for all model files`;async handle(){this.app.command=this;let e=this.app.syncModelsFromPrisma({schemaPath:this.option(`schema`)?y(String(this.option(`schema`))):void 0,modelsDir:this.option(`models`)?y(String(this.option(`models`))):void 0}),t=e.updated.length===0?[this.app.splitLogger(`Updated`,`none`)]:e.updated.map(e=>this.app.splitLogger(`Updated`,e));this.success(`SUCCESS: Model sync completed with the following results:`),[this.app.splitLogger(`Schema`,e.schemaPath),this.app.splitLogger(`Models`,e.modelsDir),this.app.splitLogger(`Processed`,String(e.total)),...t,this.app.splitLogger(`Skipped`,String(e.skipped.length))].map(e=>this.success(e))}},Re=class e{async call(...t){await e.runSeeders(...t)}static toSeederInstance(t){return t instanceof e?t:new t}static async runSeeders(...e){let t=e.reduce((e,t)=>Array.isArray(t)?[...e,...t]:(e.push(t),e),[]);for(let e of t)await this.toSeederInstance(e).run()}},ze=class extends x{signature=`seed
1866
+ `;
1867
+ description = "Sync model declare attributes from prisma schema for all model files";
1868
+ async handle() {
1869
+ this.app.command = this;
1870
+ const result = this.app.syncModelsFromPrisma({
1871
+ schemaPath: this.option("schema") ? resolve(String(this.option("schema"))) : void 0,
1872
+ modelsDir: this.option("models") ? resolve(String(this.option("models"))) : void 0
1873
+ });
1874
+ const updatedLines = result.updated.length === 0 ? [this.app.splitLogger("Updated", "none")] : result.updated.map((path) => this.app.splitLogger("Updated", path));
1875
+ this.success("SUCCESS: Model sync completed with the following results:");
1876
+ [
1877
+ this.app.splitLogger("Schema", result.schemaPath),
1878
+ this.app.splitLogger("Models", result.modelsDir),
1879
+ this.app.splitLogger("Processed", String(result.total)),
1880
+ ...updatedLines,
1881
+ this.app.splitLogger("Skipped", String(result.skipped.length))
1882
+ ].map((line) => this.success(line));
1883
+ }
1884
+ };
1885
+
1886
+ //#endregion
1887
+ //#region src/database/Seeder.ts
1888
+ /**
1889
+ * The Seeder class serves as a base for defining database seeders, which are
1890
+ * used to populate the database with initial or test data.
1891
+ *
1892
+ * @author Legacy (3m1n3nc3)
1893
+ * @since 0.1.0
1894
+ */
1895
+ var Seeder = class Seeder {
1896
+ /**
1897
+ * Runs one or more seeders.
1898
+ *
1899
+ * @param seeders The seeders to be run.
1900
+ */
1901
+ async call(...seeders) {
1902
+ await Seeder.runSeeders(...seeders);
1903
+ }
1904
+ /**
1905
+ * Converts a SeederInput into a Seeder instance.
1906
+ *
1907
+ * @param input The SeederInput to convert.
1908
+ * @returns A Seeder instance.
1909
+ */
1910
+ static toSeederInstance(input) {
1911
+ if (input instanceof Seeder) return input;
1912
+ return new input();
1913
+ }
1914
+ /**
1915
+ * Runs the given seeders in sequence.
1916
+ *
1917
+ * @param seeders The seeders to be run.
1918
+ */
1919
+ static async runSeeders(...seeders) {
1920
+ const queue = seeders.reduce((all, current) => {
1921
+ if (Array.isArray(current)) return [...all, ...current];
1922
+ all.push(current);
1923
+ return all;
1924
+ }, []);
1925
+ for (const seeder of queue) await this.toSeederInstance(seeder).run();
1926
+ }
1927
+ };
1928
+
1929
+ //#endregion
1930
+ //#region src/cli/commands/SeedCommand.ts
1931
+ /**
1932
+ * The SeedCommand class implements the CLI command for running seeder classes.
1933
+ *
1934
+ * @author Legacy (3m1n3nc3)
1935
+ * @since 0.1.0
1936
+ */
1937
+ var SeedCommand = class extends Command {
1938
+ signature = `seed
41
1939
  {name? : Seeder class or file name}
42
1940
  {--all : Run all seeders in the configured seeders directory}
43
- `;description=`Run one or more seeders`;async handle(){this.app.command=this;let e=this.app.getConfig(`paths`)?.seeders??v(process.cwd(),`database`,`seeders`),t=this.app.resolveRuntimeDirectoryPath(e);if(!p(t))return void this.error(`ERROR: Seeders directory not found: ${this.app.formatPathForLog(e)}`);let n=this.option(`all`)?await this.loadAllSeeders(t):await this.loadNamedSeeder(t,this.argument(`name`)??`DatabaseSeeder`);if(n.length===0)return void this.error(`ERROR: No seeder classes found to run.`);for(let e of n)await new e().run();this.success(`Database seeding completed`),n.forEach(e=>this.success(this.app.splitLogger(`Seeded`,e.name)))}async loadAllSeeders(e){let t=g(e).filter(e=>/\.(ts|js|mjs|cjs)$/i.test(e)).map(t=>this.app.resolveRuntimeScriptPath(v(e,t)));return(await Promise.all(t.map(async e=>await this.loadSeederClassesFromFile(e)))).flat()}async loadNamedSeeder(e,t){let n=t.replace(/Seeder$/,``),r=[`${t}.ts`,`${t}.js`,`${t}.mjs`,`${t}.cjs`,`${n}Seeder.ts`,`${n}Seeder.js`,`${n}Seeder.mjs`,`${n}Seeder.cjs`].map(t=>v(e,t)).find(e=>p(e));if(!r)return[];let i=this.app.resolveRuntimeScriptPath(r);return await this.loadSeederClassesFromFile(i)}async loadSeederClassesFromFile(e){let t=await import(`${ie(y(e)).href}?arkorm_seed=${Date.now()}`);return Object.values(t).filter(e=>typeof e==`function`?e.prototype instanceof Re:!1)}};String.raw`
1941
+ `;
1942
+ description = "Run one or more seeders";
1943
+ /**
1944
+ * Command handler for the seed command.
1945
+ *
1946
+ * @returns
1947
+ */
1948
+ async handle() {
1949
+ this.app.command = this;
1950
+ const configuredSeedersDir = this.app.getConfig("paths")?.seeders ?? join$1(process.cwd(), "database", "seeders");
1951
+ const seedersDir = this.app.resolveRuntimeDirectoryPath(configuredSeedersDir);
1952
+ if (!existsSync$1(seedersDir)) return void this.error(`ERROR: Seeders directory not found: ${this.app.formatPathForLog(configuredSeedersDir)}`);
1953
+ const classes = this.option("all") ? await this.loadAllSeeders(seedersDir) : await this.loadNamedSeeder(seedersDir, this.argument("name") ?? "DatabaseSeeder");
1954
+ if (classes.length === 0) return void this.error("ERROR: No seeder classes found to run.");
1955
+ for (const SeederClassItem of classes) await new SeederClassItem().run();
1956
+ this.success("Database seeding completed");
1957
+ classes.forEach((cls) => this.success(this.app.splitLogger("Seeded", cls.name)));
1958
+ }
1959
+ /**
1960
+ * Load all seeder classes from the specified directory.
1961
+ *
1962
+ * @param seedersDir
1963
+ * @returns
1964
+ */
1965
+ async loadAllSeeders(seedersDir) {
1966
+ const files = readdirSync$1(seedersDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).map((file) => this.app.resolveRuntimeScriptPath(join$1(seedersDir, file)));
1967
+ return (await Promise.all(files.map(async (file) => await this.loadSeederClassesFromFile(file)))).flat();
1968
+ }
1969
+ /**
1970
+ * Load seeder classes from a specific file or by class name.
1971
+ *
1972
+ * @param seedersDir
1973
+ * @param name
1974
+ * @returns
1975
+ */
1976
+ async loadNamedSeeder(seedersDir, name) {
1977
+ const base = name.replace(/Seeder$/, "");
1978
+ const target = [
1979
+ `${name}.ts`,
1980
+ `${name}.js`,
1981
+ `${name}.mjs`,
1982
+ `${name}.cjs`,
1983
+ `${base}Seeder.ts`,
1984
+ `${base}Seeder.js`,
1985
+ `${base}Seeder.mjs`,
1986
+ `${base}Seeder.cjs`
1987
+ ].map((file) => join$1(seedersDir, file)).find((file) => existsSync$1(file));
1988
+ if (!target) return [];
1989
+ const runtimeTarget = this.app.resolveRuntimeScriptPath(target);
1990
+ return await this.loadSeederClassesFromFile(runtimeTarget);
1991
+ }
1992
+ /**
1993
+ * Load seeder classes from a given file path.
1994
+ *
1995
+ * @param filePath The path to the file containing seeder classes.
1996
+ * @returns An array of seeder classes.
1997
+ */
1998
+ async loadSeederClassesFromFile(filePath) {
1999
+ const imported = await import(`${pathToFileURL$1(resolve(filePath)).href}?arkorm_seed=${Date.now()}`);
2000
+ return Object.values(imported).filter((value) => {
2001
+ if (typeof value !== "function") return false;
2002
+ return value.prototype instanceof Seeder;
2003
+ });
2004
+ }
2005
+ };
2006
+
2007
+ //#endregion
2008
+ //#region src/cli/logo.ts
2009
+ var logo_default = String.raw`
44
2010
  __/^^^^^^^^^^^^^^^^\__
45
2011
  ▄▄▄/ \▄▄
46
2012
  ▄██▀▀██▄ ▄▄
47
2013
  ███ ███ ████▄ ██ ▄█▀ ▄███▄ ████▄ ███▄███▄
48
2014
  ███▀▀███ ██ ▀▀ ████ ██ ██ ██ ▀▀ ██ ██ ██
49
2015
  ███ ███ ██ ██ ▀█▄ ▀███▀ ██ ██ ██ ██
50
- `;var Y=class extends ae{},Be=class{amount=1;sequence=0;states=[];count(e){return this.amount=Math.max(1,Math.floor(e)),this}state(e){return this.states.push(e),this}make(e={}){let t=this.buildAttributes(e);return new this.model(t)}makeMany(e=this.amount,t={}){let n=Math.max(1,Math.floor(e));return Array.from({length:n},()=>this.make(t))}async create(e={}){let t=this.make(e);if(typeof t.save!=`function`)throw Error(`Factory model does not support save().`);return await t.save()}async createMany(e=this.amount,t={}){let n=this.makeMany(e,t);return await Promise.all(n.map(async e=>{if(typeof e.save!=`function`)throw Error(`Factory model does not support save().`);return await e.save()}))}buildAttributes(e){let t=this.sequence;this.sequence+=1;let n=this.definition(t);for(let e of this.states)n=e(n,t);return{...n,...e}}},Ve=class extends Be{model;constructor(e,t){super(),this.resolver=t,this.model=e}definition(e){return this.resolver(e)}};const He=(e,t)=>new Ve(e,t);var X=class extends C{constructor(e=`No query results for the given model.`){super(e),this.name=`ModelNotFoundException`}};function Ue(e){return Object.entries(e).reduce((e,[t,n])=>(J(n)&&(e[t]=n),e),{})}function We(e){return Ue(e)}function Ge(e){return`${e.charAt(0).toLowerCase()}${e.slice(1)}s`}var Z=class{constraint=null;constrain(e){if(!this.constraint)return this.constraint=e,this;let t=this.constraint;return this.constraint=n=>{let r=t(n)??n;return e(r)??r},this}where(e){return this.constrain(t=>t.where(e))}whereKey(e,t){return this.constrain(n=>n.whereKey(e,t))}whereIn(e,t){return this.constrain(n=>n.whereIn(e,t))}orderBy(e){return this.constrain(t=>t.orderBy(e))}include(e){return this.constrain(t=>t.include(e))}with(e){return this.constrain(t=>t.with(e))}select(e){return this.constrain(t=>t.select(e))}skip(e){return this.constrain(t=>t.skip(e))}take(e){return this.constrain(t=>t.take(e))}withTrashed(){return this.constrain(e=>e.withTrashed())}onlyTrashed(){return this.constrain(e=>e.onlyTrashed())}withoutTrashed(){return this.constrain(e=>e.withoutTrashed())}scope(e,...t){return this.constrain(n=>n.scope(e,...t))}applyConstraint(e){return this.constraint?this.constraint(e)??e:e}async get(){return this.getResults()}async first(){let e=await this.getResults();return e instanceof Y?e.all()[0]??null:e}},Ke=class extends Z{constructor(e,t,n,r,i,a,o){super(),this.parent=e,this.related=t,this.throughDelegate=n,this.foreignPivotKey=r,this.relatedPivotKey=i,this.parentKey=a,this.relatedKey=o}async getResults(){let e=this.parent.getAttribute(this.parentKey),t=(await this.related.getDelegate(this.throughDelegate).findMany({where:{[this.foreignPivotKey]:e}})).map(e=>e[this.relatedPivotKey]);return this.applyConstraint(this.related.query().where({[this.relatedKey]:{in:t}})).get()}},qe=class extends Z{constructor(e,t,n,r){super(),this.parent=e,this.related=t,this.foreignKey=n,this.ownerKey=r}async getResults(){let e=this.parent.getAttribute(this.foreignKey);return this.applyConstraint(this.related.query().where({[this.ownerKey]:e})).first()}},Je=class extends Z{constructor(e,t,n,r){super(),this.parent=e,this.related=t,this.foreignKey=n,this.localKey=r}async getResults(){let e=this.parent.getAttribute(this.localKey);return this.applyConstraint(this.related.query().where({[this.foreignKey]:e})).get()}},Ye=class extends Z{constructor(e,t,n,r,i,a,o){super(),this.parent=e,this.related=t,this.throughDelegate=n,this.firstKey=r,this.secondKey=i,this.localKey=a,this.secondLocalKey=o}async getResults(){let e=this.parent.getAttribute(this.localKey),t=(await this.related.getDelegate(this.throughDelegate).findMany({where:{[this.firstKey]:e}})).map(e=>e[this.secondLocalKey]);return this.applyConstraint(this.related.query().where({[this.secondKey]:{in:t}})).get()}},Xe=class extends Z{constructor(e,t,n,r){super(),this.parent=e,this.related=t,this.foreignKey=n,this.localKey=r}async getResults(){let e=this.parent.getAttribute(this.localKey);return this.applyConstraint(this.related.query().where({[this.foreignKey]:e})).first()}},Ze=class extends Z{constructor(e,t,n,r,i,a,o){super(),this.parent=e,this.related=t,this.throughDelegate=n,this.firstKey=r,this.secondKey=i,this.localKey=a,this.secondLocalKey=o}async getResults(){let e=this.parent.getAttribute(this.localKey),t=await this.related.getDelegate(this.throughDelegate).findFirst({where:{[this.firstKey]:e}});return t?this.applyConstraint(this.related.query().where({[this.secondKey]:t[this.secondLocalKey]})).first():null}},Qe=class extends Z{constructor(e,t,n,r){super(),this.parent=e,this.related=t,this.morphName=n,this.localKey=r}async getResults(){let e=this.parent.getAttribute(this.localKey),t=this.parent.constructor.name;return this.applyConstraint(this.related.query().where({[`${this.morphName}Id`]:e,[`${this.morphName}Type`]:t})).get()}},$e=class extends Z{constructor(e,t,n,r){super(),this.parent=e,this.related=t,this.morphName=n,this.localKey=r}async getResults(){let e=this.parent.getAttribute(this.localKey),t=this.parent.constructor.name;return this.applyConstraint(this.related.query().where({[`${this.morphName}Id`]:e,[`${this.morphName}Type`]:t})).first()}},et=class extends Z{constructor(e,t,n,r,i,a,o){super(),this.parent=e,this.related=t,this.throughDelegate=n,this.morphName=r,this.relatedPivotKey=i,this.parentKey=a,this.relatedKey=o}async getResults(){let e=this.parent.getAttribute(this.parentKey),t=this.parent.constructor.name,n=(await this.related.getDelegate(this.throughDelegate).findMany({where:{[`${this.morphName}Id`]:e,[`${this.morphName}Type`]:t}})).map(e=>e[this.relatedPivotKey]);return this.applyConstraint(this.related.query().where({[this.relatedKey]:{in:n}})).get()}},Q=class e{static DEFAULT_PAGE_NAME=`page`;path;query;fragment;pageName;constructor(t={}){this.path=t.path??`/`,this.query=t.query??{},this.fragment=t.fragment??``,this.pageName=t.pageName??e.DEFAULT_PAGE_NAME}getPageName(){return this.pageName}url(e){let t=Math.max(1,e),[n,r=``]=this.path.split(`?`),i=new URLSearchParams(r);Object.entries(this.query).forEach(([e,t])=>{if(t==null){i.delete(e);return}i.set(e,String(t))}),i.set(this.pageName,String(t));let a=i.toString(),o=this.fragment.replace(/^#/,``);return!a&&!o?n:o?a?`${n}?${a}#${o}`:`${n}#${o}`:`${n}?${a}`}},tt=class{data;meta;urlDriver;constructor(e,t,n,r,i={}){let a=Math.max(1,Math.ceil(t/n)),o=t===0?null:(r-1)*n+1,s=t===0?null:Math.min(r*n,t);this.data=e;let c=q();this.urlDriver=c?c(i):new Q(i),this.meta={total:t,perPage:n,currentPage:r,lastPage:a,from:o,to:s}}getPageName(){return this.urlDriver.getPageName()}url(e){return this.urlDriver.url(e)}nextPageUrl(){return this.meta.currentPage>=this.meta.lastPage?null:this.url(this.meta.currentPage+1)}previousPageUrl(){return this.meta.currentPage<=1?null:this.url(this.meta.currentPage-1)}firstPageUrl(){return this.url(1)}lastPageUrl(){return this.url(this.meta.lastPage)}toJSON(){return{data:this.data,meta:this.meta,links:{first:this.firstPageUrl(),last:this.lastPageUrl(),prev:this.previousPageUrl(),next:this.nextPageUrl()}}}},$=class{data;meta;urlDriver;constructor(e,t,n,r,i={}){let a=e.all().length,o=a===0?null:(n-1)*t+1,s=a===0?null:(o??1)+a-1;this.data=e;let c=q();this.urlDriver=c?c(i):new Q(i),this.meta={perPage:t,currentPage:n,from:o,to:s,hasMorePages:r}}getPageName(){return this.urlDriver.getPageName()}url(e){return this.urlDriver.url(e)}nextPageUrl(){return this.meta.hasMorePages?this.url(this.meta.currentPage+1):null}previousPageUrl(){return this.meta.currentPage<=1?null:this.url(this.meta.currentPage-1)}toJSON(){return{data:this.data,meta:this.meta,links:{prev:this.previousPageUrl(),next:this.nextPageUrl()}}}},nt=class e{args={};eagerLoads={};includeTrashed=!1;onlyTrashedRecords=!1;randomOrderEnabled=!1;relationFilters=[];relationAggregates=[];constructor(e,t){this.delegate=e,this.model=t}where(e){return this.addLogicalWhere(`AND`,e)}orWhere(e){return this.addLogicalWhere(`OR`,e)}whereNot(e){return this.where({NOT:e})}orWhereNot(e){return this.orWhere({NOT:e})}whereNull(e){return this.where({[e]:null})}whereNotNull(e){return this.where({[e]:{not:null}})}whereBetween(e,t){let[n,r]=t;return this.where({[e]:{gte:n,lte:r}})}whereDate(e,t){let n=this.coerceDate(t),r=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate())),i=new Date(r);return i.setUTCDate(i.getUTCDate()+1),this.where({[e]:{gte:r,lt:i}})}whereMonth(e,t,n=new Date().getUTCFullYear()){let r=Math.min(12,Math.max(1,t)),i=new Date(Date.UTC(n,r-1,1)),a=new Date(Date.UTC(n,r,1));return this.where({[e]:{gte:i,lt:a}})}whereYear(e,t){let n=new Date(Date.UTC(t,0,1)),r=new Date(Date.UTC(t+1,0,1));return this.where({[e]:{gte:n,lt:r}})}whereKeyNot(e,t){return this.where({[e]:{not:t}})}orWhereIn(e,t){return this.orWhere({[e]:{in:t}})}whereNotIn(e,t){return this.where({[e]:{notIn:t}})}orWhereNotIn(e,t){return this.orWhere({[e]:{notIn:t}})}async firstWhere(e,t,n){let r=n!==void 0,i=r?t:`=`,a=r?n:t;return this.clone().where(this.buildComparisonWhere(e,i,a)).first()}addLogicalWhere(e,t){return this.args.where?(this.args.where={[e]:[this.args.where,t]},this):(this.args.where=t,this)}buildComparisonWhere(e,t,n){return t===`=`?{[e]:n}:t===`!=`?{[e]:{not:n}}:t===`>`?{[e]:{gt:n}}:t===`>=`?{[e]:{gte:n}}:t===`<`?{[e]:{lt:n}}:{[e]:{lte:n}}}coerceDate(e){let t=e instanceof Date?new Date(e.getTime()):new Date(e);if(Number.isNaN(t.getTime()))throw new C(`Invalid date value for date-based query helper.`);return t}whereKey(e,t){return this.where({[e]:t})}whereIn(e,t){return this.where({[e]:{in:t}})}orderBy(e){return this.randomOrderEnabled=!1,this.args.orderBy=e,this}inRandomOrder(){return this.randomOrderEnabled=!0,this}reorder(e,t=`asc`){return this.args.orderBy=void 0,this.randomOrderEnabled=!1,e?this.orderBy({[e]:t}):this}latest(e=`createdAt`){return this.orderBy({[e]:`desc`})}oldest(e=`createdAt`){return this.orderBy({[e]:`asc`})}include(e){return this.args.include=e,this}with(e){let t=this.normalizeWith(e),n=Object.keys(t);return this.args.include={...this.args.include||{},...n.reduce((e,t)=>(e[t]=!0,e),{})},Object.entries(t).forEach(([e,t])=>{this.eagerLoads[e]=t}),this}has(e,t=`>=`,n=1,r){return this.relationFilters.push({relation:e,callback:r,operator:t,count:n,boolean:`AND`}),this}orHas(e,t=`>=`,n=1){return this.relationFilters.push({relation:e,operator:t,count:n,boolean:`OR`}),this}doesntHave(e,t){return this.has(e,`<`,1,t)}orDoesntHave(e){return this.orHas(e,`<`,1)}whereHas(e,t,n=`>=`,r=1){return this.has(e,n,r,t)}orWhereHas(e,t,n=`>=`,r=1){return this.relationFilters.push({relation:e,callback:t,operator:n,count:r,boolean:`OR`}),this}whereDoesntHave(e,t){return this.whereHas(e,t,`<`,1)}orWhereDoesntHave(e,t){return this.orWhereHas(e,t,`<`,1)}withCount(e){return(Array.isArray(e)?e:[e]).forEach(e=>{this.relationAggregates.push({type:`count`,relation:e})}),this}withExists(e){return(Array.isArray(e)?e:[e]).forEach(e=>{this.relationAggregates.push({type:`exists`,relation:e})}),this}withSum(e,t){return this.relationAggregates.push({type:`sum`,relation:e,column:t}),this}withAvg(e,t){return this.relationAggregates.push({type:`avg`,relation:e,column:t}),this}withMin(e,t){return this.relationAggregates.push({type:`min`,relation:e,column:t}),this}withMax(e,t){return this.relationAggregates.push({type:`max`,relation:e,column:t}),this}withTrashed(){return this.includeTrashed=!0,this.onlyTrashedRecords=!1,this}onlyTrashed(){return this.onlyTrashedRecords=!0,this.includeTrashed=!1,this}withoutTrashed(){return this.includeTrashed=!1,this.onlyTrashedRecords=!1,this}scope(e,...t){let n=`scope${e.charAt(0).toUpperCase()}${e.slice(1)}`,r=this.model.prototype?.[n];if(typeof r!=`function`)throw new C(`Scope [${e}] is not defined.`);let i=r.call(void 0,this,...t);return i&&i!==this?i:this}when(e,t,n){let r=typeof e==`function`?e():e;return r?t(this,r):n?n(this,r):this}unless(e,t,n){let r=typeof e==`function`?e():e;return r?n?n(this,r):this:t(this,r)}tap(e){return e(this),this}pipe(e){return e(this)}select(e){return this.args.select=e,this}skip(e){return this.args.skip=e,this}offset(e){return this.skip(e)}take(e){return this.args.take=e,this}limit(e){return this.take(e)}forPage(e,t=15){let n=Math.max(1,e),r=Math.max(1,t);return this.skip((n-1)*r).take(r)}async get(){let e=new WeakMap,t=await this.delegate.findMany(this.buildFindArgs()),n=this.randomOrderEnabled?this.shuffleRows(t):t,r=this.model.hydrateMany(n),i=r;if(this.hasRelationFilters())if(this.hasOrRelationFilters()&&this.args.where){let t=new Set(r.map(e=>this.getModelId(e)).filter(e=>e!=null)),n=await this.delegate.findMany({...this.args,where:this.buildSoftDeleteOnlyWhere()}),a=this.model.hydrateMany(n);i=await this.filterModelsByRelationConstraints(a,e,t)}else i=await this.filterModelsByRelationConstraints(r,e);return this.hasRelationAggregates()&&await this.applyRelationAggregates(i,e),await Promise.all(i.map(async e=>{await e.load(this.eagerLoads)})),new Y(i)}async first(){if(this.hasRelationFilters()||this.hasRelationAggregates())return(await this.get()).all()[0]??null;if(this.randomOrderEnabled){let e=await this.delegate.findMany(this.buildFindArgs());if(e.length===0)return null;let t=this.shuffleRows(e)[0];if(!t)return null;let n=this.model.hydrate(t);return await n.load(this.eagerLoads),n}let e=await this.delegate.findFirst(this.buildFindArgs());if(!e)return null;let t=this.model.hydrate(e);return await t.load(this.eagerLoads),t}async firstOrFail(){let e=await this.first();if(!e)throw new X(`Record not found.`);return e}async find(e,t=`id`){return this.where({[t]:e}).first()}async findOr(e,t,n){let r=typeof t==`string`?t:`id`,i=typeof t==`function`?t:n;if(!i)throw new C(`findOr requires a fallback callback.`);return await this.find(e,r)||i()}async value(e){let t=await this.delegate.findFirst(this.buildFindArgs());return t?t[e]??null:null}async valueOrFail(e){let t=await this.value(e);if(t==null)throw new X(`Record not found.`);return t}async pluck(e,t){let n=await this.delegate.findMany(this.buildFindArgs());return t?new Y(n.sort((e,n)=>String(e[t]).localeCompare(String(n[t]))).map(t=>t[e])):new Y(n.map(t=>t[e]))}async create(e){let t=await this.delegate.create({data:e});return this.model.hydrate(t)}async update(e){let t=this.buildWhere();if(!t)throw new C(`Update requires a where clause.`);let n=await this.resolveUniqueWhere(t),r=await this.delegate.update({where:n,data:e});return this.model.hydrate(r)}async delete(){let e=this.buildWhere();if(!e)throw new C(`Delete requires a where clause.`);let t=await this.resolveUniqueWhere(e),n=await this.delegate.delete({where:t});return this.model.hydrate(n)}async count(){return this.hasRelationFilters()?(await this.get()).all().length:this.delegate.count({where:this.buildWhere()})}async exists(){return this.hasRelationFilters()?await this.count()>0:await this.delegate.findFirst(this.buildFindArgs())!=null}async doesntExist(){return!await this.exists()}async existsOr(e){return await this.exists()?!0:e()}async doesntExistOr(e){return await this.doesntExist()?!0:e()}async min(e){let t=await this.delegate.findMany(this.buildFindArgs());if(t.length===0)return null;let n=t.map(t=>t[e]).filter(e=>e!=null);return n.length===0?null:n.reduce((e,t)=>t<e?t:e)}async max(e){let t=await this.delegate.findMany(this.buildFindArgs());if(t.length===0)return null;let n=t.map(t=>t[e]).filter(e=>e!=null);return n.length===0?null:n.reduce((e,t)=>t>e?t:e)}async sum(e){return(await this.delegate.findMany(this.buildFindArgs())).reduce((t,n)=>{let r=n[e],i=typeof r==`number`?r:Number(r);return Number.isFinite(i)?t+i:t},0)}async avg(e){let t=(await this.delegate.findMany(this.buildFindArgs())).map(t=>{let n=t[e];return typeof n==`number`?n:Number(n)}).filter(e=>Number.isFinite(e));return t.length===0?null:t.reduce((e,t)=>e+t,0)/t.length}whereRaw(e,t=[]){let n=this.delegate;if(typeof n.applyRawWhere!=`function`)throw new C(`Raw where clauses are not supported by the current adapter.`);return this.args.where=n.applyRawWhere(this.buildWhere(),e,t),this}orWhereRaw(e,t=[]){let n=this.delegate;if(typeof n.applyRawWhere!=`function`)throw new C(`Raw where clauses are not supported by the current adapter.`);let r=n.applyRawWhere(void 0,e,t);return this.orWhere(r)}async paginate(e=1,t=15,n={}){if(this.hasRelationFilters()||this.hasRelationAggregates()){let r=Math.max(1,e),i=Math.max(1,t),a=(await this.get()).all(),o=(r-1)*i;return new tt(new Y(a.slice(o,o+i)),a.length,i,r,n)}let r=Math.max(1,e),i=Math.max(1,t),a=await this.count();return new tt(await this.clone().skip((r-1)*i).take(i).get(),a,i,r,n)}async simplePaginate(e=15,t=1,n={}){if(this.hasRelationFilters()||this.hasRelationAggregates()){let r=Math.max(1,t),i=Math.max(1,e),a=(await this.get()).all(),o=(r-1)*i,s=a.slice(o,o+i),c=o+i<a.length;return new $(new Y(s),i,r,c,n)}let r=Math.max(1,t),i=Math.max(1,e),a=await this.clone().skip((r-1)*i).take(i+1).get(),o=a.all().length>i;return new $(o?new Y(a.all().slice(0,i)):a,i,r,o,n)}clone(){let t=new e(this.delegate,this.model);return t.args.where=this.args.where,t.args.include=this.args.include,t.args.orderBy=this.args.orderBy,t.args.select=this.args.select,t.args.skip=this.args.skip,t.args.take=this.args.take,t.includeTrashed=this.includeTrashed,t.onlyTrashedRecords=this.onlyTrashedRecords,t.randomOrderEnabled=this.randomOrderEnabled,this.relationFilters.forEach(e=>{t.relationFilters.push({...e})}),this.relationAggregates.forEach(e=>{t.relationAggregates.push({...e})}),Object.entries(this.eagerLoads).forEach(([e,n])=>{t.eagerLoads[e]=n}),t}normalizeWith(e){return typeof e==`string`?{[e]:void 0}:Array.isArray(e)?e.reduce((e,t)=>(e[t]=void 0,e),{}):e}buildWhere(){let e=this.model.getSoftDeleteConfig();if(!e.enabled||this.includeTrashed)return this.args.where;let t=this.onlyTrashedRecords?{[e.column]:{not:null}}:{[e.column]:null};return this.args.where?{AND:[this.args.where,t]}:t}buildFindArgs(){return{...this.args,where:this.buildWhere()}}async resolveUniqueWhere(e){if(this.isUniqueWhere(e))return e;let t=await this.delegate.findFirst({where:e});if(!t)throw new C(`Record not found for update/delete operation.`);let n=t;if(!Object.prototype.hasOwnProperty.call(n,`id`))throw new C(`Unable to resolve a unique identifier for update/delete operation. Include an id in the query constraints.`);return{id:n.id}}isUniqueWhere(e){return Object.keys(e).length===1&&Object.prototype.hasOwnProperty.call(e,`id`)}shuffleRows(e){let t=[...e];for(let e=t.length-1;e>0;e--){let n=Math.floor(Math.random()*(e+1)),r=t[e];t[e]=t[n],t[n]=r}return t}hasRelationFilters(){return this.relationFilters.length>0}hasOrRelationFilters(){return this.relationFilters.some(e=>e.boolean===`OR`)}hasRelationAggregates(){return this.relationAggregates.length>0}async filterModelsByRelationConstraints(e,t,n){return(await Promise.all(e.map(async e=>{let r=null;n&&(r=n.has(this.getModelId(e)));for(let n of this.relationFilters){let i=await this.resolveRelatedCount(e,n.relation,t,n.callback),a=this.compareCount(i,n.operator,n.count);r=r==null?a:n.boolean===`AND`?r&&a:r||a}return{model:e,passes:r??!0}}))).filter(e=>e.passes).map(e=>e.model)}getModelId(e){let t=e;if(typeof t.getAttribute!=`function`)return null;let n=t.getAttribute(`id`);return typeof n==`number`||typeof n==`string`?n:null}buildSoftDeleteOnlyWhere(){let e=this.model.getSoftDeleteConfig();if(e.enabled&&!this.includeTrashed)return this.onlyTrashedRecords?{[e.column]:{not:null}}:{[e.column]:null}}async applyRelationAggregates(e,t){let n=t??new WeakMap;await Promise.all(e.map(async e=>{for(let t of this.relationAggregates){let r=await this.resolveRelatedResults(e,t.relation,n),i=Array.isArray(r)?r:r?[r]:[],a=this.buildAggregateAttributeKey(t);if(t.type===`count`){this.assignAggregate(e,a,i.length);continue}if(t.type===`exists`){this.assignAggregate(e,a,i.length>0);continue}let o=i.map(e=>e.getAttribute(t.column)).filter(e=>e!=null);if(t.type===`sum`){let t=o.reduce((e,t)=>{let n=typeof t==`number`?t:Number(t);return Number.isFinite(n)?e+n:e},0);this.assignAggregate(e,a,t);continue}if(t.type===`avg`){let t=o.map(e=>typeof e==`number`?e:Number(e)).filter(e=>Number.isFinite(e)),n=t.length===0?null:t.reduce((e,t)=>e+t,0)/t.length;this.assignAggregate(e,a,n);continue}if(t.type===`min`){let t=o.length===0?null:o.reduce((e,t)=>t<e?t:e);this.assignAggregate(e,a,t);continue}let s=o.length===0?null:o.reduce((e,t)=>t>e?t:e);this.assignAggregate(e,a,s)}}))}async resolveRelatedCount(e,t,n,r){let i=await this.resolveRelatedResults(e,t,n,r);return Array.isArray(i)?i.length:i?1:0}async resolveRelatedResults(e,t,n,r){let i=e,a=r??`__none__`,o=n.get(i);o||(o=new Map,n.set(i,o));let s=o.get(t);s||(s=new Map,o.set(t,s));let c=s.get(a);if(c)return await c;let l=(async()=>{let n=e[t];if(typeof n!=`function`)throw new C(`Relation [${t}] is not defined on the model.`);let i=n.call(e);if(r&&typeof i.constrain==`function`&&i.constrain(e=>r(e)??e),typeof i.get==`function`){let e=await i.get();return e instanceof Y?e.all():e}if(typeof i.getResults==`function`){let e=await i.getResults();return e instanceof Y?e.all():e}throw new C(`Relation [${t}] does not support result resolution.`)})();return s.set(a,l),await l}compareCount(e,t,n){return t===`>=`?e>=n:t===`>`?e>n:t===`=`?e===n:t===`!=`?e!==n:t===`<=`?e<=n:e<n}buildAggregateAttributeKey(e){let t=e.relation;if(e.type===`count`)return`${t}Count`;if(e.type===`exists`)return`${t}Exists`;let n=e.column?`${e.column.charAt(0).toUpperCase()}${e.column.slice(1)}`:``;return`${t}${`${e.type.charAt(0).toUpperCase()}${e.type.slice(1)}`}${n}`}assignAggregate(e,t,n){let r=e;if(typeof r.setAttribute==`function`){r.setAttribute(t,n);return}e[t]=n}},rt=class e{static factoryClass;static client;static delegate;static softDeletes=!1;static deletedAtColumn=`deletedAt`;static globalScopes={};static eventListeners={};casts={};hidden=[];visible=[];appends=[];attributes;constructor(e={}){return this.attributes={},this.fill(e),new Proxy(this,{get:(e,t,n)=>typeof t!=`string`||t in e?Reflect.get(e,t,n):e.getAttribute(t),set:(e,t,n,r)=>typeof t!=`string`||t in e?Reflect.set(e,t,n,r):(e.setAttribute(t,n),!0)})}static setClient(e){this.client=e}static setFactory(e){this.factoryClass=e}static factory(e){let t=this.factoryClass;if(!t)throw new C(`Factory is not configured for model [${this.name}].`);let n=new t;return typeof e==`number`&&n.count(e),n}static addGlobalScope(e,t){this.ensureOwnGlobalScopes(),this.globalScopes[e]=t}static removeGlobalScope(e){this.ensureOwnGlobalScopes(),delete this.globalScopes[e]}static clearGlobalScopes(){this.globalScopes={}}static on(e,t){this.ensureOwnEventListeners(),this.eventListeners[e]||(this.eventListeners[e]=[]),this.eventListeners[e]?.push(t)}static off(e,t){if(this.ensureOwnEventListeners(),!t){delete this.eventListeners[e];return}this.eventListeners[e]=(this.eventListeners[e]||[]).filter(e=>e!==t)}static clearEventListeners(){this.eventListeners={}}static getDelegate(e){Oe();let t=e||this.delegate||`${b(this.name).camel().plural()}`,n=[t,`${b(t).camel()}`,`${b(t).singular()}`,`${b(t).camel().singular()}`],r=ke(),i=n.map(e=>this.client?.[e]??r?.[e]).find(e=>J(e));if(!i)throw new C(`Database delegate [${t}] is not configured.`);return i}static query(){let e=new nt(this.getDelegate(),this),t=this;return t.ensureOwnGlobalScopes(),Object.values(t.globalScopes).forEach(t=>{let n=t(e);n&&n!==e&&(e=n)}),e}static withTrashed(){return this.query().withTrashed()}static onlyTrashed(){return this.query().onlyTrashed()}static scope(e,...t){return this.query().scope(e,...t)}static getSoftDeleteConfig(){return{enabled:this.softDeletes,column:this.deletedAtColumn}}static hydrate(e){return new this(e)}static hydrateMany(e){return e.map(e=>new this(e))}fill(e){return Object.entries(e).forEach(([e,t])=>{this.setAttribute(e,t)}),this}getAttribute(e){let t=this.resolveGetMutator(e),n=this.casts[e],r=this.attributes[e];return n&&(r=S(n).get(r)),t?t.call(this,r):r}setAttribute(e,t){let n=this.resolveSetMutator(e),r=this.casts[e],i=t;return n&&(i=n.call(this,i)),r&&(i=S(r).set(i)),this.attributes[e]=i,this}async save(){let t=this.getAttribute(`id`),n=this.getRawAttributes(),r=this.constructor;if(t==null){await e.dispatchEvent(r,`saving`,this),await e.dispatchEvent(r,`creating`,this);let t=await r.query().create(n);return this.fill(t.getRawAttributes()),await e.dispatchEvent(r,`created`,this),await e.dispatchEvent(r,`saved`,this),this}await e.dispatchEvent(r,`saving`,this),await e.dispatchEvent(r,`updating`,this);let i=await r.query().where({id:t}).update(n);return this.fill(i.getRawAttributes()),await e.dispatchEvent(r,`updated`,this),await e.dispatchEvent(r,`saved`,this),this}async delete(){let t=this.getAttribute(`id`);if(t==null)throw new C(`Cannot delete a model without an id.`);let n=this.constructor;await e.dispatchEvent(n,`deleting`,this);let r=n.getSoftDeleteConfig();if(r.enabled){let i=await n.query().where({id:t}).update({[r.column]:new Date});return this.fill(i.getRawAttributes()),await e.dispatchEvent(n,`deleted`,this),this}let i=await n.query().where({id:t}).delete();return this.fill(i.getRawAttributes()),await e.dispatchEvent(n,`deleted`,this),this}async forceDelete(){let t=this.getAttribute(`id`);if(t==null)throw new C(`Cannot force delete a model without an id.`);let n=this.constructor;await e.dispatchEvent(n,`forceDeleting`,this),await e.dispatchEvent(n,`deleting`,this);let r=await n.query().withTrashed().where({id:t}).delete();return this.fill(r.getRawAttributes()),await e.dispatchEvent(n,`deleted`,this),await e.dispatchEvent(n,`forceDeleted`,this),this}async restore(){let t=this.getAttribute(`id`);if(t==null)throw new C(`Cannot restore a model without an id.`);let n=this.constructor,r=n.getSoftDeleteConfig();if(!r.enabled)return this;await e.dispatchEvent(n,`restoring`,this);let i=await n.query().withTrashed().where({id:t}).update({[r.column]:null});return this.fill(i.getRawAttributes()),await e.dispatchEvent(n,`restored`,this),this}async load(e){let t=this.normalizeRelationMap(e);return await Promise.all(Object.entries(t).map(async([e,t])=>{let n=this[e];if(typeof n!=`function`)return;let r=n.call(this);t&&r.constrain(t);let i=await r.getResults();this.attributes[e]=i})),this}getRawAttributes(){return{...this.attributes}}toObject(){let e=(this.visible.length>0?this.visible:Object.keys(this.attributes).filter(e=>!this.hidden.includes(e))).reduce((e,t)=>{let n=this.getAttribute(t);return n instanceof Date&&(n=n.toISOString()),e[t]=n,e},{});return this.appends.forEach(t=>{e[t]=this.getAttribute(t)}),e}toJSON(){return this.toObject()}hasOne(e,t,n=`id`){return new Xe(this,e,t,n)}hasMany(e,t,n=`id`){return new Je(this,e,t,n)}belongsTo(e,t,n=`id`){return new qe(this,e,t,n)}belongsToMany(e,t,n,r,i=`id`,a=`id`){return new Ke(this,e,t,n,r,i,a)}hasOneThrough(e,t,n,r,i=`id`,a=`id`){return new Ze(this,e,t,n,r,i,a)}hasManyThrough(e,t,n,r,i=`id`,a=`id`){return new Ye(this,e,t,n,r,i,a)}morphOne(e,t,n=`id`){return new $e(this,e,t,n)}morphMany(e,t,n=`id`){return new Qe(this,e,t,n)}morphToMany(e,t,n,r,i=`id`,a=`id`){return new et(this,e,t,n,r,i,a)}resolveGetMutator(e){let t=`get${b(e).studly()}Attribute`,n=this[t];return typeof n==`function`?n:null}resolveSetMutator(e){let t=`set${b(e).studly()}Attribute`,n=this[t];return typeof n==`function`?n:null}static ensureOwnGlobalScopes(){Object.prototype.hasOwnProperty.call(this,`globalScopes`)||(this.globalScopes={...this.globalScopes||{}})}static ensureOwnEventListeners(){Object.prototype.hasOwnProperty.call(this,`eventListeners`)||(this.eventListeners={...this.eventListeners||{}})}static async dispatchEvent(e,t,n){e.ensureOwnEventListeners();let r=e.eventListeners[t]||[];for(let e of r)await e(n)}normalizeRelationMap(e){return typeof e==`string`?{[e]:void 0}:Array.isArray(e)?e.reduce((e,t)=>(e[t]=void 0,e),{}):e}};export{Y as ArkormCollection,C as ArkormException,Ae as CliApp,je as InitCommand,Ve as InlineFactory,tt as LengthAwarePaginator,Me as MakeFactoryCommand,Ne as MakeMigrationCommand,Pe as MakeModelCommand,Fe as MakeSeederCommand,Ie as MigrateCommand,w as Migration,rt as Model,Be as ModelFactory,X as ModelNotFoundException,Le as ModelsSyncCommand,ce as PRISMA_MODEL_REGEX,$ as Paginator,nt as QueryBuilder,se as SchemaBuilder,ze as SeedCommand,Re as Seeder,T as TableBuilder,Q as URLDriver,de as applyAlterTableOperation,M as applyCreateTableOperation,fe as applyDropTableOperation,I as applyMigrationToPrismaSchema,pe as applyOperationsToPrismaSchema,k as buildFieldLine,A as buildIndexLine,ge as buildMigrationSource,ue as buildModelBlock,Ce as configureArkormRuntime,F as createMigrationTimestamp,Ue as createPrismaAdapter,We as createPrismaDelegateMap,Se as defineConfig,He as defineFactory,Oe as ensureArkormConfigLoading,D as escapeRegex,j as findModelBlock,O as formatDefaultValue,_e as generateMigrationFile,K as getDefaultStubsPath,ve as getMigrationPlan,q as getRuntimePaginationURLDriverFactory,ke as getRuntimePrismaClient,U as getUserConfig,Ge as inferDelegateName,J as isDelegateLike,G as loadArkormConfig,P as pad,we as resetArkormRuntimeForTests,S as resolveCast,me as resolveMigrationClassName,le as resolvePrismaType,ye as runMigrationWithPrisma,N as runPrismaCommand,he as toMigrationFileSlug,E as toModelName};
2016
+ `;
2017
+
2018
+ //#endregion
2019
+ //#region src/Collection.ts
2020
+ var ArkormCollection = class extends Collection {};
2021
+
2022
+ //#endregion
2023
+ //#region src/database/factories.ts
2024
+ /**
2025
+ * Base class for defining model factories.
2026
+ * Not meant to be used directly.
2027
+ *
2028
+ * @template TModel The type of model the factory creates.
2029
+ * @template TAttributes The type of attributes used to create the model.
2030
+ * @author Legacy (3m1n3nc3)
2031
+ * @since 0.1.0
2032
+ */
2033
+ var ModelFactory = class {
2034
+ amount = 1;
2035
+ sequence = 0;
2036
+ states = [];
2037
+ /**
2038
+ * Set the number of models to create.
2039
+ *
2040
+ * @param amount
2041
+ * @returns
2042
+ */
2043
+ count(amount) {
2044
+ this.amount = Math.max(1, Math.floor(amount));
2045
+ return this;
2046
+ }
2047
+ /**
2048
+ * Define a state transformation for the factory.
2049
+ * States are applied in the order they were defined.
2050
+ *
2051
+ * @param resolver A function that takes the current attributes and sequence number, and returns the transformed attributes.
2052
+ * @returns The factory instance for chaining.
2053
+ */
2054
+ state(resolver) {
2055
+ this.states.push(resolver);
2056
+ return this;
2057
+ }
2058
+ /**
2059
+ * Create a new model instance without saving it to the database.
2060
+ *
2061
+ * @param overrides
2062
+ * @returns
2063
+ */
2064
+ make(overrides = {}) {
2065
+ const attributes = this.buildAttributes(overrides);
2066
+ return new this.model(attributes);
2067
+ }
2068
+ /**
2069
+ * Create multiple model instances without saving them to the database.
2070
+ *
2071
+ * @param amount
2072
+ * @param overrides
2073
+ * @returns
2074
+ */
2075
+ makeMany(amount = this.amount, overrides = {}) {
2076
+ const total = Math.max(1, Math.floor(amount));
2077
+ return Array.from({ length: total }, () => this.make(overrides));
2078
+ }
2079
+ /**
2080
+ * Create a new model instance and save it to the database.
2081
+ *
2082
+ * @param overrides
2083
+ * @returns
2084
+ */
2085
+ async create(overrides = {}) {
2086
+ const model = this.make(overrides);
2087
+ if (typeof model.save !== "function") throw new Error("Factory model does not support save().");
2088
+ return await model.save();
2089
+ }
2090
+ /**
2091
+ * Create multiple model instances and save them to the database.
2092
+ *
2093
+ * @param amount
2094
+ * @param overrides
2095
+ * @returns
2096
+ */
2097
+ async createMany(amount = this.amount, overrides = {}) {
2098
+ const models = this.makeMany(amount, overrides);
2099
+ return await Promise.all(models.map(async (model) => {
2100
+ if (typeof model.save !== "function") throw new Error("Factory model does not support save().");
2101
+ return await model.save();
2102
+ }));
2103
+ }
2104
+ /**
2105
+ * Build the attributes for a model instance, applying the factory
2106
+ * definition and any defined states, and merging in any overrides.
2107
+ *
2108
+ * @param overrides
2109
+ * @returns
2110
+ */
2111
+ buildAttributes(overrides) {
2112
+ const sequence = this.sequence;
2113
+ this.sequence += 1;
2114
+ let resolved = this.definition(sequence);
2115
+ for (const state of this.states) resolved = state(resolved, sequence);
2116
+ return {
2117
+ ...resolved,
2118
+ ...overrides
2119
+ };
2120
+ }
2121
+ };
2122
+ /**
2123
+ * A helper class for defining factories using an inline definition
2124
+ * function, without needing to create a separate factory class.
2125
+ *
2126
+ * @template TModel
2127
+ * @template TAttributes
2128
+ * @author Legacy (3m1n3nc3)
2129
+ * @since 0.1.0
2130
+ */
2131
+ var InlineFactory = class extends ModelFactory {
2132
+ model;
2133
+ constructor(model, resolver) {
2134
+ super();
2135
+ this.resolver = resolver;
2136
+ this.model = model;
2137
+ }
2138
+ definition(sequence) {
2139
+ return this.resolver(sequence);
2140
+ }
2141
+ };
2142
+ /**
2143
+ * Define a factory for a given model using an inline definition function.
2144
+ *
2145
+ * @template TModel The type of model the factory creates.
2146
+ * @template TAttributes The type of attributes used to create the model.
2147
+ * @param model The model constructor.
2148
+ * @param definition The factory definition function.
2149
+ * @returns A new instance of the model factory.
2150
+ */
2151
+ const defineFactory = (model, definition) => {
2152
+ return new InlineFactory(model, definition);
2153
+ };
2154
+
2155
+ //#endregion
2156
+ //#region src/Exceptions/ModelNotFoundException.ts
2157
+ /**
2158
+ * The ModelNotFoundException class is a custom error type for handling
2159
+ * cases where a requested model instance cannot be found in the database.
2160
+ *
2161
+ * @author Legacy (3m1n3nc3)
2162
+ * @since 0.1.0
2163
+ */
2164
+ var ModelNotFoundException = class extends ArkormException {
2165
+ constructor(message = "No query results for the given model.") {
2166
+ super(message);
2167
+ this.name = "ModelNotFoundException";
2168
+ }
2169
+ };
2170
+
2171
+ //#endregion
2172
+ //#region src/helpers/prisma.ts
2173
+ /**
2174
+ * Create an adapter to convert a Prisma client instance into a format
2175
+ * compatible with ArkORM's expectations.
2176
+ *
2177
+ * @param prisma The Prisma client instance to adapt.
2178
+ * @param mapping An optional mapping of Prisma delegate names to ArkORM delegate names.
2179
+ * @returns A record of adapted Prisma delegates compatible with ArkORM.
2180
+ */
2181
+ function createPrismaAdapter(prisma) {
2182
+ return Object.entries(prisma).reduce((accumulator, [key, value]) => {
2183
+ if (!isDelegateLike(value)) return accumulator;
2184
+ accumulator[key] = value;
2185
+ return accumulator;
2186
+ }, {});
2187
+ }
2188
+ /**
2189
+ * Create a delegate mapping record for Model.setClient() from a Prisma client.
2190
+ *
2191
+ * @param prisma The Prisma client instance.
2192
+ * @param mapping Optional mapping of Arkormˣ delegate names to Prisma delegate names.
2193
+ * @returns A delegate map keyed by Arkormˣ delegate names.
2194
+ */
2195
+ function createPrismaDelegateMap(prisma) {
2196
+ return createPrismaAdapter(prisma);
2197
+ }
2198
+ /**
2199
+ * Infer the Prisma delegate name for a given model name using a simple convention.
2200
+ *
2201
+ * @param modelName The name of the model to infer the delegate name for.
2202
+ * @returns The inferred Prisma delegate name.
2203
+ */
2204
+ function inferDelegateName(modelName) {
2205
+ return `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}s`;
2206
+ }
2207
+
2208
+ //#endregion
2209
+ //#region src/relationship/Relation.ts
2210
+ /**
2211
+ * Base class for all relationship types. Not meant to be used directly.
2212
+ *
2213
+ * @author Legacy (3m1n3nc3)
2214
+ * @since 0.1.0
2215
+ */
2216
+ var Relation = class {
2217
+ constraint = null;
2218
+ /**
2219
+ * Apply a constraint to the relationship query.
2220
+ *
2221
+ * @param constraint The constraint function to apply to the query.
2222
+ * @returns The current relation instance.
2223
+ */
2224
+ constrain(constraint) {
2225
+ if (!this.constraint) {
2226
+ this.constraint = constraint;
2227
+ return this;
2228
+ }
2229
+ const previousConstraint = this.constraint;
2230
+ this.constraint = (query) => {
2231
+ const constrained = previousConstraint(query) ?? query;
2232
+ return constraint(constrained) ?? constrained;
2233
+ };
2234
+ return this;
2235
+ }
2236
+ /**
2237
+ * Add a where clause to the relationship query.
2238
+ *
2239
+ * @param where
2240
+ * @returns
2241
+ */
2242
+ where(where) {
2243
+ return this.constrain((query) => query.where(where));
2244
+ }
2245
+ /**
2246
+ * Add a strongly-typed where key clause to the relationship query.
2247
+ *
2248
+ * @param key
2249
+ * @param value
2250
+ * @returns
2251
+ */
2252
+ whereKey(key, value) {
2253
+ return this.constrain((query) => query.whereKey(key, value));
2254
+ }
2255
+ /**
2256
+ * Add a strongly-typed where in clause to the relationship query.
2257
+ *
2258
+ * @param key
2259
+ * @param values
2260
+ * @returns
2261
+ */
2262
+ whereIn(key, values) {
2263
+ return this.constrain((query) => query.whereIn(key, values));
2264
+ }
2265
+ /**
2266
+ * Add an order by clause to the relationship query.
2267
+ *
2268
+ * @param orderBy
2269
+ * @returns
2270
+ */
2271
+ orderBy(orderBy) {
2272
+ return this.constrain((query) => query.orderBy(orderBy));
2273
+ }
2274
+ /**
2275
+ * Add an include clause to the relationship query.
2276
+ *
2277
+ * @param include
2278
+ * @returns
2279
+ */
2280
+ include(include) {
2281
+ return this.constrain((query) => query.include(include));
2282
+ }
2283
+ /**
2284
+ * Add eager loading relations to the relationship query.
2285
+ *
2286
+ * @param relations
2287
+ * @returns
2288
+ */
2289
+ with(relations) {
2290
+ return this.constrain((query) => query.with(relations));
2291
+ }
2292
+ /**
2293
+ * Add a select clause to the relationship query.
2294
+ *
2295
+ * @param select
2296
+ * @returns
2297
+ */
2298
+ select(select) {
2299
+ return this.constrain((query) => query.select(select));
2300
+ }
2301
+ /**
2302
+ * Add a skip clause to the relationship query.
2303
+ *
2304
+ * @param skip
2305
+ * @returns
2306
+ */
2307
+ skip(skip) {
2308
+ return this.constrain((query) => query.skip(skip));
2309
+ }
2310
+ /**
2311
+ * Add a take clause to the relationship query.
2312
+ *
2313
+ * @param take
2314
+ * @returns
2315
+ */
2316
+ take(take) {
2317
+ return this.constrain((query) => query.take(take));
2318
+ }
2319
+ /**
2320
+ * Include soft-deleted records in the relationship query.
2321
+ *
2322
+ * @returns
2323
+ */
2324
+ withTrashed() {
2325
+ return this.constrain((query) => query.withTrashed());
2326
+ }
2327
+ /**
2328
+ * Limit relationship query to only soft-deleted records.
2329
+ *
2330
+ * @returns
2331
+ */
2332
+ onlyTrashed() {
2333
+ return this.constrain((query) => query.onlyTrashed());
2334
+ }
2335
+ /**
2336
+ * Exclude soft-deleted records from the relationship query.
2337
+ *
2338
+ * @returns
2339
+ */
2340
+ withoutTrashed() {
2341
+ return this.constrain((query) => query.withoutTrashed());
2342
+ }
2343
+ /**
2344
+ * Apply a scope to the relationship query.
2345
+ *
2346
+ * @param name
2347
+ * @param args
2348
+ * @returns
2349
+ */
2350
+ scope(name, ...args) {
2351
+ return this.constrain((query) => query.scope(name, ...args));
2352
+ }
2353
+ /**
2354
+ * Apply the defined constraint to the given query, if any.
2355
+ *
2356
+ * @param query The query builder instance to apply the constraint to.
2357
+ *
2358
+ * @returns The query builder instance with the constraint applied, if any.
2359
+ */
2360
+ applyConstraint(query) {
2361
+ if (!this.constraint) return query;
2362
+ return this.constraint(query) ?? query;
2363
+ }
2364
+ /**
2365
+ * Execute the relationship query and return relation results.
2366
+ *
2367
+ * @returns
2368
+ */
2369
+ async get() {
2370
+ return this.getResults();
2371
+ }
2372
+ /**
2373
+ * Execute the relationship query and return the first related model.
2374
+ *
2375
+ * @returns
2376
+ */
2377
+ async first() {
2378
+ const results = await this.getResults();
2379
+ if (results instanceof ArkormCollection) return results.all()[0] ?? null;
2380
+ return results;
2381
+ }
2382
+ };
2383
+
2384
+ //#endregion
2385
+ //#region src/relationship/BelongsToManyRelation.ts
2386
+ /**
2387
+ * Defines a many-to-many relationship.
2388
+ *
2389
+ * @author Legacy (3m1n3nc3)
2390
+ * @since 0.1.0
2391
+ */
2392
+ var BelongsToManyRelation = class extends Relation {
2393
+ constructor(parent, related, throughDelegate, foreignPivotKey, relatedPivotKey, parentKey, relatedKey) {
2394
+ super();
2395
+ this.parent = parent;
2396
+ this.related = related;
2397
+ this.throughDelegate = throughDelegate;
2398
+ this.foreignPivotKey = foreignPivotKey;
2399
+ this.relatedPivotKey = relatedPivotKey;
2400
+ this.parentKey = parentKey;
2401
+ this.relatedKey = relatedKey;
2402
+ }
2403
+ /**
2404
+ * Fetches the related models for this relationship.
2405
+ *
2406
+ * @returns
2407
+ */
2408
+ async getResults() {
2409
+ const parentValue = this.parent.getAttribute(this.parentKey);
2410
+ const ids = (await this.related.getDelegate(this.throughDelegate).findMany({ where: { [this.foreignPivotKey]: parentValue } })).map((row) => row[this.relatedPivotKey]);
2411
+ return this.applyConstraint(this.related.query().where({ [this.relatedKey]: { in: ids } })).get();
2412
+ }
2413
+ };
2414
+
2415
+ //#endregion
2416
+ //#region src/relationship/BelongsToRelation.ts
2417
+ /**
2418
+ * Defines an inverse one-to-one or many relationship.
2419
+ *
2420
+ * @author Legacy (3m1n3nc3)
2421
+ * @since 0.1.0
2422
+ */
2423
+ var BelongsToRelation = class extends Relation {
2424
+ constructor(parent, related, foreignKey, ownerKey) {
2425
+ super();
2426
+ this.parent = parent;
2427
+ this.related = related;
2428
+ this.foreignKey = foreignKey;
2429
+ this.ownerKey = ownerKey;
2430
+ }
2431
+ /**
2432
+ * Fetches the related models for this relationship.
2433
+ *
2434
+ * @returns
2435
+ */
2436
+ async getResults() {
2437
+ const foreignValue = this.parent.getAttribute(this.foreignKey);
2438
+ return this.applyConstraint(this.related.query().where({ [this.ownerKey]: foreignValue })).first();
2439
+ }
2440
+ };
2441
+
2442
+ //#endregion
2443
+ //#region src/relationship/HasManyRelation.ts
2444
+ /**
2445
+ * Defines a one-to-many relationship.
2446
+ *
2447
+ * @author Legacy (3m1n3nc3)
2448
+ * @since 0.1.0
2449
+ */
2450
+ var HasManyRelation = class extends Relation {
2451
+ constructor(parent, related, foreignKey, localKey) {
2452
+ super();
2453
+ this.parent = parent;
2454
+ this.related = related;
2455
+ this.foreignKey = foreignKey;
2456
+ this.localKey = localKey;
2457
+ }
2458
+ /**
2459
+ * Fetches the related models for this relationship.
2460
+ *
2461
+ * @returns
2462
+ */
2463
+ async getResults() {
2464
+ const localValue = this.parent.getAttribute(this.localKey);
2465
+ return this.applyConstraint(this.related.query().where({ [this.foreignKey]: localValue })).get();
2466
+ }
2467
+ };
2468
+
2469
+ //#endregion
2470
+ //#region src/relationship/HasManyThroughRelation.ts
2471
+ /**
2472
+ * Defines a has-many-through relationship, which provides a convenient way to access
2473
+ * distant relations via an intermediate relation.
2474
+ *
2475
+ * @author Legacy (3m1n3nc3)
2476
+ * @since 0.1.0
2477
+ */
2478
+ var HasManyThroughRelation = class extends Relation {
2479
+ constructor(parent, related, throughDelegate, firstKey, secondKey, localKey, secondLocalKey) {
2480
+ super();
2481
+ this.parent = parent;
2482
+ this.related = related;
2483
+ this.throughDelegate = throughDelegate;
2484
+ this.firstKey = firstKey;
2485
+ this.secondKey = secondKey;
2486
+ this.localKey = localKey;
2487
+ this.secondLocalKey = secondLocalKey;
2488
+ }
2489
+ /**
2490
+ * Fetches the related models for this relationship.
2491
+ *
2492
+ * @returns
2493
+ */
2494
+ async getResults() {
2495
+ const localValue = this.parent.getAttribute(this.localKey);
2496
+ const keys = (await this.related.getDelegate(this.throughDelegate).findMany({ where: { [this.firstKey]: localValue } })).map((row) => row[this.secondLocalKey]);
2497
+ return this.applyConstraint(this.related.query().where({ [this.secondKey]: { in: keys } })).get();
2498
+ }
2499
+ };
2500
+
2501
+ //#endregion
2502
+ //#region src/relationship/HasOneRelation.ts
2503
+ /**
2504
+ * Represents a "has one" relationship between two models.
2505
+ *
2506
+ * @author Legacy (3m1n3nc3)
2507
+ * @since 0.1.0
2508
+ */
2509
+ var HasOneRelation = class extends Relation {
2510
+ constructor(parent, related, foreignKey, localKey) {
2511
+ super();
2512
+ this.parent = parent;
2513
+ this.related = related;
2514
+ this.foreignKey = foreignKey;
2515
+ this.localKey = localKey;
2516
+ }
2517
+ /**
2518
+ * Fetches the related models for this relationship.
2519
+ *
2520
+ * @returns
2521
+ */
2522
+ async getResults() {
2523
+ const localValue = this.parent.getAttribute(this.localKey);
2524
+ return this.applyConstraint(this.related.query().where({ [this.foreignKey]: localValue })).first();
2525
+ }
2526
+ };
2527
+
2528
+ //#endregion
2529
+ //#region src/relationship/HasOneThroughRelation.ts
2530
+ /**
2531
+ * Represents a "has one through" relationship, where the parent model is related
2532
+ * to exactly one instance of the related model through an intermediate model.
2533
+ *
2534
+ * @author Legacy (3m1n3nc3)
2535
+ * @since 0.1.0
2536
+ */
2537
+ var HasOneThroughRelation = class extends Relation {
2538
+ constructor(parent, related, throughDelegate, firstKey, secondKey, localKey, secondLocalKey) {
2539
+ super();
2540
+ this.parent = parent;
2541
+ this.related = related;
2542
+ this.throughDelegate = throughDelegate;
2543
+ this.firstKey = firstKey;
2544
+ this.secondKey = secondKey;
2545
+ this.localKey = localKey;
2546
+ this.secondLocalKey = secondLocalKey;
2547
+ }
2548
+ /**
2549
+ * Fetches the related models for this relationship.
2550
+ *
2551
+ * @returns
2552
+ */
2553
+ async getResults() {
2554
+ const localValue = this.parent.getAttribute(this.localKey);
2555
+ const intermediate = await this.related.getDelegate(this.throughDelegate).findFirst({ where: { [this.firstKey]: localValue } });
2556
+ if (!intermediate) return null;
2557
+ return this.applyConstraint(this.related.query().where({ [this.secondKey]: intermediate[this.secondLocalKey] })).first();
2558
+ }
2559
+ };
2560
+
2561
+ //#endregion
2562
+ //#region src/relationship/MorphManyRelation.ts
2563
+ /**
2564
+ * Defines a polymorphic one-to-many relationship.
2565
+ *
2566
+ * @author Legacy (3m1n3nc3)
2567
+ * @since 0.1.0
2568
+ */
2569
+ var MorphManyRelation = class extends Relation {
2570
+ constructor(parent, related, morphName, localKey) {
2571
+ super();
2572
+ this.parent = parent;
2573
+ this.related = related;
2574
+ this.morphName = morphName;
2575
+ this.localKey = localKey;
2576
+ }
2577
+ /**
2578
+ * Fetches the related models for this relationship.
2579
+ *
2580
+ * @returns
2581
+ */
2582
+ async getResults() {
2583
+ const id = this.parent.getAttribute(this.localKey);
2584
+ const type = this.parent.constructor.name;
2585
+ return this.applyConstraint(this.related.query().where({
2586
+ [`${this.morphName}Id`]: id,
2587
+ [`${this.morphName}Type`]: type
2588
+ })).get();
2589
+ }
2590
+ };
2591
+
2592
+ //#endregion
2593
+ //#region src/relationship/MorphOneRelation.ts
2594
+ /**
2595
+ * Defines a polymorphic one-to-one relationship.
2596
+ *
2597
+ * @author Legacy (3m1n3nc3)
2598
+ * @since 0.1.0
2599
+ */
2600
+ var MorphOneRelation = class extends Relation {
2601
+ constructor(parent, related, morphName, localKey) {
2602
+ super();
2603
+ this.parent = parent;
2604
+ this.related = related;
2605
+ this.morphName = morphName;
2606
+ this.localKey = localKey;
2607
+ }
2608
+ /**
2609
+ * Fetches the related models for this relationship.
2610
+ *
2611
+ * @returns
2612
+ */
2613
+ async getResults() {
2614
+ const id = this.parent.getAttribute(this.localKey);
2615
+ const type = this.parent.constructor.name;
2616
+ return this.applyConstraint(this.related.query().where({
2617
+ [`${this.morphName}Id`]: id,
2618
+ [`${this.morphName}Type`]: type
2619
+ })).first();
2620
+ }
2621
+ };
2622
+
2623
+ //#endregion
2624
+ //#region src/relationship/MorphToManyRelation.ts
2625
+ /**
2626
+ * Defines a polymorphic many-to-many relationship.
2627
+ *
2628
+ * @author Legacy (3m1n3nc3)
2629
+ * @since 0.1.0
2630
+ */
2631
+ var MorphToManyRelation = class extends Relation {
2632
+ constructor(parent, related, throughDelegate, morphName, relatedPivotKey, parentKey, relatedKey) {
2633
+ super();
2634
+ this.parent = parent;
2635
+ this.related = related;
2636
+ this.throughDelegate = throughDelegate;
2637
+ this.morphName = morphName;
2638
+ this.relatedPivotKey = relatedPivotKey;
2639
+ this.parentKey = parentKey;
2640
+ this.relatedKey = relatedKey;
2641
+ }
2642
+ /**
2643
+ * Fetches the related models for this relationship.
2644
+ *
2645
+ * @returns
2646
+ */
2647
+ async getResults() {
2648
+ const parentValue = this.parent.getAttribute(this.parentKey);
2649
+ const morphType = this.parent.constructor.name;
2650
+ const ids = (await this.related.getDelegate(this.throughDelegate).findMany({ where: {
2651
+ [`${this.morphName}Id`]: parentValue,
2652
+ [`${this.morphName}Type`]: morphType
2653
+ } })).map((row) => row[this.relatedPivotKey]);
2654
+ return this.applyConstraint(this.related.query().where({ [this.relatedKey]: { in: ids } })).get();
2655
+ }
2656
+ };
2657
+
2658
+ //#endregion
2659
+ //#region src/URLDriver.ts
2660
+ /**
2661
+ * URLDriver builds pagination URLs from paginator options.
2662
+ *
2663
+ * @author Legacy (3m1n3nc3)
2664
+ * @since 0.1.0
2665
+ */
2666
+ var URLDriver = class URLDriver {
2667
+ static DEFAULT_PAGE_NAME = "page";
2668
+ path;
2669
+ query;
2670
+ fragment;
2671
+ pageName;
2672
+ constructor(options = {}) {
2673
+ this.path = options.path ?? "/";
2674
+ this.query = options.query ?? {};
2675
+ this.fragment = options.fragment ?? "";
2676
+ this.pageName = options.pageName ?? URLDriver.DEFAULT_PAGE_NAME;
2677
+ }
2678
+ getPageName() {
2679
+ return this.pageName;
2680
+ }
2681
+ url(page) {
2682
+ const targetPage = Math.max(1, page);
2683
+ const [basePath, pathQuery = ""] = this.path.split("?");
2684
+ const search = new URLSearchParams(pathQuery);
2685
+ Object.entries(this.query).forEach(([key, value]) => {
2686
+ if (value == null) {
2687
+ search.delete(key);
2688
+ return;
2689
+ }
2690
+ search.set(key, String(value));
2691
+ });
2692
+ search.set(this.pageName, String(targetPage));
2693
+ const queryString = search.toString();
2694
+ const normalizedFragment = this.fragment.replace(/^#/, "");
2695
+ if (!queryString && !normalizedFragment) return basePath;
2696
+ if (!normalizedFragment) return `${basePath}?${queryString}`;
2697
+ if (!queryString) return `${basePath}#${normalizedFragment}`;
2698
+ return `${basePath}?${queryString}#${normalizedFragment}`;
2699
+ }
2700
+ };
2701
+
2702
+ //#endregion
2703
+ //#region src/Paginator.ts
2704
+ /**
2705
+ * The LengthAwarePaginator class encapsulates paginated results with full
2706
+ * metadata including the total result count and last page.
2707
+ *
2708
+ * @template T The type of the data being paginated.
2709
+ * @author Legacy (3m1n3nc3)
2710
+ * @since 0.1.0
2711
+ */
2712
+ var LengthAwarePaginator = class {
2713
+ data;
2714
+ meta;
2715
+ urlDriver;
2716
+ /**
2717
+ * Creates a new LengthAwarePaginator instance.
2718
+ *
2719
+ * @param data The collection of data being paginated.
2720
+ * @param total The total number of items.
2721
+ * @param perPage The number of items per page.
2722
+ * @param currentPage The current page number.
2723
+ * @param options URL generation options.
2724
+ */
2725
+ constructor(data, total, perPage, currentPage, options = {}) {
2726
+ const lastPage = Math.max(1, Math.ceil(total / perPage));
2727
+ const from = total === 0 ? null : (currentPage - 1) * perPage + 1;
2728
+ const to = total === 0 ? null : Math.min(currentPage * perPage, total);
2729
+ this.data = data;
2730
+ const urlDriverFactory = getRuntimePaginationURLDriverFactory();
2731
+ this.urlDriver = urlDriverFactory ? urlDriverFactory(options) : new URLDriver(options);
2732
+ this.meta = {
2733
+ total,
2734
+ perPage,
2735
+ currentPage,
2736
+ lastPage,
2737
+ from,
2738
+ to
2739
+ };
2740
+ }
2741
+ getPageName() {
2742
+ return this.urlDriver.getPageName();
2743
+ }
2744
+ url(page) {
2745
+ return this.urlDriver.url(page);
2746
+ }
2747
+ nextPageUrl() {
2748
+ if (this.meta.currentPage >= this.meta.lastPage) return null;
2749
+ return this.url(this.meta.currentPage + 1);
2750
+ }
2751
+ previousPageUrl() {
2752
+ if (this.meta.currentPage <= 1) return null;
2753
+ return this.url(this.meta.currentPage - 1);
2754
+ }
2755
+ firstPageUrl() {
2756
+ return this.url(1);
2757
+ }
2758
+ lastPageUrl() {
2759
+ return this.url(this.meta.lastPage);
2760
+ }
2761
+ /**
2762
+ * Converts the paginator instance to a JSON-serializable object.
2763
+ *
2764
+ * @returns
2765
+ */
2766
+ toJSON() {
2767
+ return {
2768
+ data: this.data,
2769
+ meta: this.meta,
2770
+ links: {
2771
+ first: this.firstPageUrl(),
2772
+ last: this.lastPageUrl(),
2773
+ prev: this.previousPageUrl(),
2774
+ next: this.nextPageUrl()
2775
+ }
2776
+ };
2777
+ }
2778
+ };
2779
+ /**
2780
+ * The Paginator class encapsulates simple pagination results without total count.
2781
+ *
2782
+ * @template T The type of the data being paginated.
2783
+ */
2784
+ var Paginator = class {
2785
+ data;
2786
+ meta;
2787
+ urlDriver;
2788
+ /**
2789
+ * Creates a new simple Paginator instance.
2790
+ *
2791
+ * @param data The collection of data being paginated.
2792
+ * @param perPage The number of items per page.
2793
+ * @param currentPage The current page number.
2794
+ * @param hasMorePages Indicates whether additional pages exist.
2795
+ * @param options URL generation options.
2796
+ */
2797
+ constructor(data, perPage, currentPage, hasMorePages, options = {}) {
2798
+ const count = data.all().length;
2799
+ const from = count === 0 ? null : (currentPage - 1) * perPage + 1;
2800
+ const to = count === 0 ? null : (from ?? 1) + count - 1;
2801
+ this.data = data;
2802
+ const urlDriverFactory = getRuntimePaginationURLDriverFactory();
2803
+ this.urlDriver = urlDriverFactory ? urlDriverFactory(options) : new URLDriver(options);
2804
+ this.meta = {
2805
+ perPage,
2806
+ currentPage,
2807
+ from,
2808
+ to,
2809
+ hasMorePages
2810
+ };
2811
+ }
2812
+ getPageName() {
2813
+ return this.urlDriver.getPageName();
2814
+ }
2815
+ url(page) {
2816
+ return this.urlDriver.url(page);
2817
+ }
2818
+ nextPageUrl() {
2819
+ if (!this.meta.hasMorePages) return null;
2820
+ return this.url(this.meta.currentPage + 1);
2821
+ }
2822
+ previousPageUrl() {
2823
+ if (this.meta.currentPage <= 1) return null;
2824
+ return this.url(this.meta.currentPage - 1);
2825
+ }
2826
+ toJSON() {
2827
+ return {
2828
+ data: this.data,
2829
+ meta: this.meta,
2830
+ links: {
2831
+ prev: this.previousPageUrl(),
2832
+ next: this.nextPageUrl()
2833
+ }
2834
+ };
2835
+ }
2836
+ };
2837
+
2838
+ //#endregion
2839
+ //#region src/QueryBuilder.ts
2840
+ /**
2841
+ * The QueryBuilder class provides a fluent interface for building and
2842
+ * executing database queries.
2843
+ *
2844
+ * @template TModel The type of the model being queried.
2845
+ * @author Legacy (3m1n3nc3)
2846
+ * @since 0.1.0
2847
+ */
2848
+ var QueryBuilder = class QueryBuilder {
2849
+ args = {};
2850
+ eagerLoads = {};
2851
+ includeTrashed = false;
2852
+ onlyTrashedRecords = false;
2853
+ randomOrderEnabled = false;
2854
+ relationFilters = [];
2855
+ relationAggregates = [];
2856
+ /**
2857
+ * Creates a new QueryBuilder instance.
2858
+ *
2859
+ * @param delegate
2860
+ * @param model
2861
+ */
2862
+ constructor(delegate, model) {
2863
+ this.delegate = delegate;
2864
+ this.model = model;
2865
+ }
2866
+ /**
2867
+ * Adds a where clause to the query. Multiple calls to where will combine
2868
+ * the clauses with AND logic.
2869
+ *
2870
+ * @param where
2871
+ * @returns
2872
+ */
2873
+ where(where) {
2874
+ return this.addLogicalWhere("AND", where);
2875
+ }
2876
+ /**
2877
+ * Adds an OR where clause to the query.
2878
+ *
2879
+ * @param where
2880
+ * @returns
2881
+ */
2882
+ orWhere(where) {
2883
+ return this.addLogicalWhere("OR", where);
2884
+ }
2885
+ /**
2886
+ * Adds a NOT where clause to the query.
2887
+ *
2888
+ * @param where
2889
+ * @returns
2890
+ */
2891
+ whereNot(where) {
2892
+ return this.where({ NOT: where });
2893
+ }
2894
+ /**
2895
+ * Adds an OR NOT where clause to the query.
2896
+ *
2897
+ * @param where
2898
+ * @returns
2899
+ */
2900
+ orWhereNot(where) {
2901
+ return this.orWhere({ NOT: where });
2902
+ }
2903
+ /**
2904
+ * Adds a null check for a key.
2905
+ *
2906
+ * @param key
2907
+ * @returns
2908
+ */
2909
+ whereNull(key) {
2910
+ return this.where({ [key]: null });
2911
+ }
2912
+ /**
2913
+ * Adds a not-null check for a key.
2914
+ *
2915
+ * @param key
2916
+ * @returns
2917
+ */
2918
+ whereNotNull(key) {
2919
+ return this.where({ [key]: { not: null } });
2920
+ }
2921
+ /**
2922
+ * Adds a between range clause for a key.
2923
+ *
2924
+ * @param key
2925
+ * @param range
2926
+ * @returns
2927
+ */
2928
+ whereBetween(key, range) {
2929
+ const [min, max] = range;
2930
+ return this.where({ [key]: {
2931
+ gte: min,
2932
+ lte: max
2933
+ } });
2934
+ }
2935
+ /**
2936
+ * Adds a date-only equality clause for a date-like key.
2937
+ *
2938
+ * @param key
2939
+ * @param value
2940
+ * @returns
2941
+ */
2942
+ whereDate(key, value) {
2943
+ const target = this.coerceDate(value);
2944
+ const start = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth(), target.getUTCDate()));
2945
+ const end = new Date(start);
2946
+ end.setUTCDate(end.getUTCDate() + 1);
2947
+ return this.where({ [key]: {
2948
+ gte: start,
2949
+ lt: end
2950
+ } });
2951
+ }
2952
+ /**
2953
+ * Adds a month clause for a date-like key.
2954
+ *
2955
+ * @param key
2956
+ * @param month
2957
+ * @param year
2958
+ * @returns
2959
+ */
2960
+ whereMonth(key, month, year = (/* @__PURE__ */ new Date()).getUTCFullYear()) {
2961
+ const normalizedMonth = Math.min(12, Math.max(1, month));
2962
+ const start = new Date(Date.UTC(year, normalizedMonth - 1, 1));
2963
+ const end = new Date(Date.UTC(year, normalizedMonth, 1));
2964
+ return this.where({ [key]: {
2965
+ gte: start,
2966
+ lt: end
2967
+ } });
2968
+ }
2969
+ /**
2970
+ * Adds a year clause for a date-like key.
2971
+ *
2972
+ * @param key
2973
+ * @param year
2974
+ * @returns
2975
+ */
2976
+ whereYear(key, year) {
2977
+ const start = new Date(Date.UTC(year, 0, 1));
2978
+ const end = new Date(Date.UTC(year + 1, 0, 1));
2979
+ return this.where({ [key]: {
2980
+ gte: start,
2981
+ lt: end
2982
+ } });
2983
+ }
2984
+ /**
2985
+ * Adds a strongly-typed inequality where clause for a single attribute key.
2986
+ *
2987
+ * @param key
2988
+ * @param value
2989
+ * @returns
2990
+ */
2991
+ whereKeyNot(key, value) {
2992
+ return this.where({ [key]: { not: value } });
2993
+ }
2994
+ /**
2995
+ * Adds a strongly-typed OR IN where clause for a single attribute key.
2996
+ *
2997
+ * @param key
2998
+ * @param values
2999
+ * @returns
3000
+ */
3001
+ orWhereIn(key, values) {
3002
+ return this.orWhere({ [key]: { in: values } });
3003
+ }
3004
+ /**
3005
+ * Adds a strongly-typed NOT IN where clause for a single attribute key.
3006
+ *
3007
+ * @param key
3008
+ * @param values
3009
+ * @returns
3010
+ */
3011
+ whereNotIn(key, values) {
3012
+ return this.where({ [key]: { notIn: values } });
3013
+ }
3014
+ /**
3015
+ * Adds a strongly-typed OR NOT IN where clause for a single attribute key.
3016
+ *
3017
+ * @param key
3018
+ * @param values
3019
+ * @returns
3020
+ */
3021
+ orWhereNotIn(key, values) {
3022
+ return this.orWhere({ [key]: { notIn: values } });
3023
+ }
3024
+ async firstWhere(key, operatorOrValue, maybeValue) {
3025
+ const hasOperator = maybeValue !== void 0;
3026
+ const operator = hasOperator ? operatorOrValue : "=";
3027
+ const value = hasOperator ? maybeValue : operatorOrValue;
3028
+ return this.clone().where(this.buildComparisonWhere(key, operator, value)).first();
3029
+ }
3030
+ addLogicalWhere(operator, where) {
3031
+ if (!this.args.where) {
3032
+ this.args.where = where;
3033
+ return this;
3034
+ }
3035
+ this.args.where = { [operator]: [this.args.where, where] };
3036
+ return this;
3037
+ }
3038
+ buildComparisonWhere(key, operator, value) {
3039
+ if (operator === "=") return { [key]: value };
3040
+ if (operator === "!=") return { [key]: { not: value } };
3041
+ if (operator === ">") return { [key]: { gt: value } };
3042
+ if (operator === ">=") return { [key]: { gte: value } };
3043
+ if (operator === "<") return { [key]: { lt: value } };
3044
+ return { [key]: { lte: value } };
3045
+ }
3046
+ coerceDate(value) {
3047
+ const parsed = value instanceof Date ? new Date(value.getTime()) : new Date(value);
3048
+ if (Number.isNaN(parsed.getTime())) throw new ArkormException("Invalid date value for date-based query helper.");
3049
+ return parsed;
3050
+ }
3051
+ /**
3052
+ * Adds a strongly-typed equality where clause for a single attribute key.
3053
+ *
3054
+ * @param key
3055
+ * @param value
3056
+ * @returns
3057
+ */
3058
+ whereKey(key, value) {
3059
+ return this.where({ [key]: value });
3060
+ }
3061
+ /**
3062
+ * Adds a strongly-typed IN where clause for a single attribute key.
3063
+ *
3064
+ * @param key
3065
+ * @param values
3066
+ * @returns
3067
+ */
3068
+ whereIn(key, values) {
3069
+ return this.where({ [key]: { in: values } });
3070
+ }
3071
+ /**
3072
+ * Adds an orderBy clause to the query. This will overwrite any existing orderBy clause.
3073
+ *
3074
+ * @param orderBy
3075
+ * @returns
3076
+ */
3077
+ orderBy(orderBy) {
3078
+ this.randomOrderEnabled = false;
3079
+ this.args.orderBy = orderBy;
3080
+ return this;
3081
+ }
3082
+ /**
3083
+ * Puts the query results in random order.
3084
+ *
3085
+ * @returns
3086
+ */
3087
+ inRandomOrder() {
3088
+ this.randomOrderEnabled = true;
3089
+ return this;
3090
+ }
3091
+ /**
3092
+ * Removes existing order clauses and optionally applies a new one.
3093
+ *
3094
+ * @param column
3095
+ * @param direction
3096
+ * @returns
3097
+ */
3098
+ reorder(column, direction = "asc") {
3099
+ this.args.orderBy = void 0;
3100
+ this.randomOrderEnabled = false;
3101
+ if (!column) return this;
3102
+ return this.orderBy({ [column]: direction });
3103
+ }
3104
+ /**
3105
+ * Adds an orderBy descending clause for a timestamp-like column.
3106
+ *
3107
+ * @param column
3108
+ * @returns
3109
+ */
3110
+ latest(column = "createdAt") {
3111
+ return this.orderBy({ [column]: "desc" });
3112
+ }
3113
+ /**
3114
+ * Adds an orderBy ascending clause for a timestamp-like column.
3115
+ *
3116
+ * @param column
3117
+ * @returns
3118
+ */
3119
+ oldest(column = "createdAt") {
3120
+ return this.orderBy({ [column]: "asc" });
3121
+ }
3122
+ /**
3123
+ * Adds an include clause to the query. This will overwrite any existing include clause.
3124
+ *
3125
+ * @param include
3126
+ * @returns
3127
+ */
3128
+ include(include) {
3129
+ this.args.include = include;
3130
+ return this;
3131
+ }
3132
+ /**
3133
+ * Adds eager loading for the specified relations.
3134
+ * This will merge with any existing include clause.
3135
+ *
3136
+ * @param relations
3137
+ * @returns
3138
+ */
3139
+ with(relations) {
3140
+ const relationMap = this.normalizeWith(relations);
3141
+ const names = Object.keys(relationMap);
3142
+ this.args.include = {
3143
+ ...this.args.include || {},
3144
+ ...names.reduce((accumulator, name) => {
3145
+ accumulator[name] = true;
3146
+ return accumulator;
3147
+ }, {})
3148
+ };
3149
+ Object.entries(relationMap).forEach(([name, constraint]) => {
3150
+ this.eagerLoads[name] = constraint;
3151
+ });
3152
+ return this;
3153
+ }
3154
+ /**
3155
+ * Add a relationship count/existence constraint.
3156
+ *
3157
+ * @param relation
3158
+ * @param operator
3159
+ * @param count
3160
+ * @param callback
3161
+ * @returns
3162
+ */
3163
+ has(relation, operator = ">=", count = 1, callback) {
3164
+ this.relationFilters.push({
3165
+ relation,
3166
+ callback,
3167
+ operator,
3168
+ count,
3169
+ boolean: "AND"
3170
+ });
3171
+ return this;
3172
+ }
3173
+ /**
3174
+ * Add an OR relationship count/existence constraint.
3175
+ *
3176
+ * @param relation
3177
+ * @param operator
3178
+ * @param count
3179
+ * @returns
3180
+ */
3181
+ orHas(relation, operator = ">=", count = 1) {
3182
+ this.relationFilters.push({
3183
+ relation,
3184
+ operator,
3185
+ count,
3186
+ boolean: "OR"
3187
+ });
3188
+ return this;
3189
+ }
3190
+ /**
3191
+ * Add a relationship does-not-have constraint.
3192
+ *
3193
+ * @param relation
3194
+ * @param callback
3195
+ * @returns
3196
+ */
3197
+ doesntHave(relation, callback) {
3198
+ return this.has(relation, "<", 1, callback);
3199
+ }
3200
+ /**
3201
+ * Add an OR relationship does-not-have constraint.
3202
+ *
3203
+ * @param relation
3204
+ * @returns
3205
+ */
3206
+ orDoesntHave(relation) {
3207
+ return this.orHas(relation, "<", 1);
3208
+ }
3209
+ /**
3210
+ * Add a constrained relationship has clause.
3211
+ *
3212
+ * @param relation
3213
+ * @param callback
3214
+ * @param operator
3215
+ * @param count
3216
+ * @returns
3217
+ */
3218
+ whereHas(relation, callback, operator = ">=", count = 1) {
3219
+ return this.has(relation, operator, count, callback);
3220
+ }
3221
+ /**
3222
+ * Add an OR constrained relationship has clause.
3223
+ *
3224
+ * @param relation
3225
+ * @param callback
3226
+ * @param operator
3227
+ * @param count
3228
+ * @returns
3229
+ */
3230
+ orWhereHas(relation, callback, operator = ">=", count = 1) {
3231
+ this.relationFilters.push({
3232
+ relation,
3233
+ callback,
3234
+ operator,
3235
+ count,
3236
+ boolean: "OR"
3237
+ });
3238
+ return this;
3239
+ }
3240
+ /**
3241
+ * Add a constrained relationship does-not-have clause.
3242
+ *
3243
+ * @param relation
3244
+ * @param callback
3245
+ * @returns
3246
+ */
3247
+ whereDoesntHave(relation, callback) {
3248
+ return this.whereHas(relation, callback, "<", 1);
3249
+ }
3250
+ /**
3251
+ * Add an OR constrained relationship does-not-have clause.
3252
+ *
3253
+ * @param relation
3254
+ * @param callback
3255
+ * @returns
3256
+ */
3257
+ orWhereDoesntHave(relation, callback) {
3258
+ return this.orWhereHas(relation, callback, "<", 1);
3259
+ }
3260
+ /**
3261
+ * Add relationship count aggregate attributes.
3262
+ *
3263
+ * @param relations
3264
+ * @returns
3265
+ */
3266
+ withCount(relations) {
3267
+ (Array.isArray(relations) ? relations : [relations]).forEach((relation) => {
3268
+ this.relationAggregates.push({
3269
+ type: "count",
3270
+ relation
3271
+ });
3272
+ });
3273
+ return this;
3274
+ }
3275
+ /**
3276
+ * Add relationship existence aggregate attributes.
3277
+ *
3278
+ * @param relations
3279
+ * @returns
3280
+ */
3281
+ withExists(relations) {
3282
+ (Array.isArray(relations) ? relations : [relations]).forEach((relation) => {
3283
+ this.relationAggregates.push({
3284
+ type: "exists",
3285
+ relation
3286
+ });
3287
+ });
3288
+ return this;
3289
+ }
3290
+ /**
3291
+ * Add relationship sum aggregate attribute.
3292
+ *
3293
+ * @param relation
3294
+ * @param column
3295
+ * @returns
3296
+ */
3297
+ withSum(relation, column) {
3298
+ this.relationAggregates.push({
3299
+ type: "sum",
3300
+ relation,
3301
+ column
3302
+ });
3303
+ return this;
3304
+ }
3305
+ /**
3306
+ * Add relationship average aggregate attribute.
3307
+ *
3308
+ * @param relation
3309
+ * @param column
3310
+ * @returns
3311
+ */
3312
+ withAvg(relation, column) {
3313
+ this.relationAggregates.push({
3314
+ type: "avg",
3315
+ relation,
3316
+ column
3317
+ });
3318
+ return this;
3319
+ }
3320
+ /**
3321
+ * Add relationship minimum aggregate attribute.
3322
+ *
3323
+ * @param relation
3324
+ * @param column
3325
+ * @returns
3326
+ */
3327
+ withMin(relation, column) {
3328
+ this.relationAggregates.push({
3329
+ type: "min",
3330
+ relation,
3331
+ column
3332
+ });
3333
+ return this;
3334
+ }
3335
+ /**
3336
+ * Add relationship maximum aggregate attribute.
3337
+ *
3338
+ * @param relation
3339
+ * @param column
3340
+ * @returns
3341
+ */
3342
+ withMax(relation, column) {
3343
+ this.relationAggregates.push({
3344
+ type: "max",
3345
+ relation,
3346
+ column
3347
+ });
3348
+ return this;
3349
+ }
3350
+ /**
3351
+ * Includes soft-deleted records in the query results.
3352
+ * This method is only applicable if the model has soft delete enabled.
3353
+ *
3354
+ * @returns
3355
+ */
3356
+ withTrashed() {
3357
+ this.includeTrashed = true;
3358
+ this.onlyTrashedRecords = false;
3359
+ return this;
3360
+ }
3361
+ /**
3362
+ * Limits the query results to only soft-deleted records.
3363
+ * This method is only applicable if the model has soft delete enabled.
3364
+ *
3365
+ * @returns
3366
+ */
3367
+ onlyTrashed() {
3368
+ this.onlyTrashedRecords = true;
3369
+ this.includeTrashed = false;
3370
+ return this;
3371
+ }
3372
+ /**
3373
+ * Excludes soft-deleted records from the query results.
3374
+ * This is the default behavior, but this method can be used to explicitly
3375
+ * enforce it after using withTrashed or onlyTrashed.
3376
+ *
3377
+ * @returns
3378
+ */
3379
+ withoutTrashed() {
3380
+ this.includeTrashed = false;
3381
+ this.onlyTrashedRecords = false;
3382
+ return this;
3383
+ }
3384
+ /**
3385
+ * Applies a named scope to the query. A scope is a reusable query constraint
3386
+ * defined as a static method on the model. The scope method will look for a
3387
+ * method with the name `scope{Name}` on the model's prototype.
3388
+ * If found, it will call that method with the current query builder
3389
+ * instance and any additional arguments provided.
3390
+ *
3391
+ * @param name
3392
+ * @param args
3393
+ * @returns
3394
+ */
3395
+ scope(name, ...args) {
3396
+ const methodName = `scope${name.charAt(0).toUpperCase()}${name.slice(1)}`;
3397
+ const scope = this.model.prototype?.[methodName];
3398
+ if (typeof scope !== "function") throw new ArkormException(`Scope [${name}] is not defined.`);
3399
+ const scoped = scope.call(void 0, this, ...args);
3400
+ if (scoped && scoped !== this) return scoped;
3401
+ return this;
3402
+ }
3403
+ /**
3404
+ * Apply the callback when value is truthy.
3405
+ *
3406
+ * @param value
3407
+ * @param callback
3408
+ * @param defaultCallback
3409
+ * @returns
3410
+ */
3411
+ when(value, callback, defaultCallback) {
3412
+ const resolved = typeof value === "function" ? value() : value;
3413
+ if (resolved) return callback(this, resolved);
3414
+ if (defaultCallback) return defaultCallback(this, resolved);
3415
+ return this;
3416
+ }
3417
+ /**
3418
+ * Apply the callback when value is falsy.
3419
+ *
3420
+ * @param value
3421
+ * @param callback
3422
+ * @param defaultCallback
3423
+ * @returns
3424
+ */
3425
+ unless(value, callback, defaultCallback) {
3426
+ const resolved = typeof value === "function" ? value() : value;
3427
+ if (!resolved) return callback(this, resolved);
3428
+ if (defaultCallback) return defaultCallback(this, resolved);
3429
+ return this;
3430
+ }
3431
+ /**
3432
+ * Pass the query builder into a callback and return this.
3433
+ *
3434
+ * @param callback
3435
+ * @returns
3436
+ */
3437
+ tap(callback) {
3438
+ callback(this);
3439
+ return this;
3440
+ }
3441
+ /**
3442
+ * Pass the query builder into a callback and return callback result.
3443
+ *
3444
+ * @param callback
3445
+ * @returns
3446
+ */
3447
+ pipe(callback) {
3448
+ return callback(this);
3449
+ }
3450
+ /**
3451
+ * Adds a select clause to the query. This will overwrite any existing select clause.
3452
+ *
3453
+ * @param select
3454
+ * @returns
3455
+ */
3456
+ select(select) {
3457
+ this.args.select = select;
3458
+ return this;
3459
+ }
3460
+ /**
3461
+ * Adds a skip clause to the query for pagination.
3462
+ * This will overwrite any existing skip clause.
3463
+ *
3464
+ * @param skip
3465
+ * @returns
3466
+ */
3467
+ skip(skip) {
3468
+ this.args.skip = skip;
3469
+ return this;
3470
+ }
3471
+ /**
3472
+ * Alias for skip.
3473
+ *
3474
+ * @param value
3475
+ * @returns
3476
+ */
3477
+ offset(value) {
3478
+ return this.skip(value);
3479
+ }
3480
+ /**
3481
+ * Adds a take clause to the query for pagination.
3482
+ *
3483
+ * @param take
3484
+ * @returns
3485
+ */
3486
+ take(take) {
3487
+ this.args.take = take;
3488
+ return this;
3489
+ }
3490
+ /**
3491
+ * Alias for take.
3492
+ *
3493
+ * @param value
3494
+ * @returns
3495
+ */
3496
+ limit(value) {
3497
+ return this.take(value);
3498
+ }
3499
+ /**
3500
+ * Sets offset/limit for a 1-based page.
3501
+ *
3502
+ * @param page
3503
+ * @param perPage
3504
+ * @returns
3505
+ */
3506
+ forPage(page, perPage = 15) {
3507
+ const currentPage = Math.max(1, page);
3508
+ const pageSize = Math.max(1, perPage);
3509
+ return this.skip((currentPage - 1) * pageSize).take(pageSize);
3510
+ }
3511
+ /**
3512
+ * Executes the query and returns the results as a collection of model instances.
3513
+ *
3514
+ * @returns
3515
+ */
3516
+ async get() {
3517
+ const relationCache = /* @__PURE__ */ new WeakMap();
3518
+ const rows = await this.delegate.findMany(this.buildFindArgs());
3519
+ const normalizedRows = this.randomOrderEnabled ? this.shuffleRows(rows) : rows;
3520
+ const models = this.model.hydrateMany(normalizedRows);
3521
+ let filteredModels = models;
3522
+ if (this.hasRelationFilters()) if (this.hasOrRelationFilters() && this.args.where) {
3523
+ const baseIds = new Set(models.map((model) => this.getModelId(model)).filter((id) => id != null));
3524
+ const allRows = await this.delegate.findMany({
3525
+ ...this.args,
3526
+ where: this.buildSoftDeleteOnlyWhere()
3527
+ });
3528
+ const allModels = this.model.hydrateMany(allRows);
3529
+ filteredModels = await this.filterModelsByRelationConstraints(allModels, relationCache, baseIds);
3530
+ } else filteredModels = await this.filterModelsByRelationConstraints(models, relationCache);
3531
+ if (this.hasRelationAggregates()) await this.applyRelationAggregates(filteredModels, relationCache);
3532
+ await Promise.all(filteredModels.map(async (model) => {
3533
+ await model.load(this.eagerLoads);
3534
+ }));
3535
+ return new ArkormCollection(filteredModels);
3536
+ }
3537
+ /**
3538
+ * Executes the query and returns the first result as a model
3539
+ * instance, or null if no results are found.
3540
+ *
3541
+ * @returns
3542
+ */
3543
+ async first() {
3544
+ if (this.hasRelationFilters() || this.hasRelationAggregates()) return (await this.get()).all()[0] ?? null;
3545
+ if (this.randomOrderEnabled) {
3546
+ const rows = await this.delegate.findMany(this.buildFindArgs());
3547
+ if (rows.length === 0) return null;
3548
+ const row = this.shuffleRows(rows)[0];
3549
+ if (!row) return null;
3550
+ const model = this.model.hydrate(row);
3551
+ await model.load(this.eagerLoads);
3552
+ return model;
3553
+ }
3554
+ const row = await this.delegate.findFirst(this.buildFindArgs());
3555
+ if (!row) return null;
3556
+ const model = this.model.hydrate(row);
3557
+ await model.load(this.eagerLoads);
3558
+ return model;
3559
+ }
3560
+ /**
3561
+ * Executes the query and returns the first result as a model instance.
3562
+ *
3563
+ * @returns
3564
+ */
3565
+ async firstOrFail() {
3566
+ const model = await this.first();
3567
+ if (!model) throw new ModelNotFoundException("Record not found.");
3568
+ return model;
3569
+ }
3570
+ async find(value, key = "id") {
3571
+ return this.where({ [key]: value }).first();
3572
+ }
3573
+ async findOr(value, keyOrCallback, maybeCallback) {
3574
+ const key = typeof keyOrCallback === "string" ? keyOrCallback : "id";
3575
+ const callback = typeof keyOrCallback === "function" ? keyOrCallback : maybeCallback;
3576
+ if (!callback) throw new ArkormException("findOr requires a fallback callback.");
3577
+ const found = await this.find(value, key);
3578
+ if (found) return found;
3579
+ return callback();
3580
+ }
3581
+ /**
3582
+ * Returns a single column value from the first record.
3583
+ *
3584
+ * @param column
3585
+ * @returns
3586
+ */
3587
+ async value(column) {
3588
+ const row = await this.delegate.findFirst(this.buildFindArgs());
3589
+ if (!row) return null;
3590
+ return row[column] ?? null;
3591
+ }
3592
+ /**
3593
+ * Returns a single column value from the first record or throws.
3594
+ *
3595
+ * @param column
3596
+ * @returns
3597
+ */
3598
+ async valueOrFail(column) {
3599
+ const result = await this.value(column);
3600
+ if (result == null) throw new ModelNotFoundException("Record not found.");
3601
+ return result;
3602
+ }
3603
+ /**
3604
+ * Returns a collection with values for the given column.
3605
+ *
3606
+ * @param column
3607
+ * @param key
3608
+ * @returns
3609
+ */
3610
+ async pluck(column, key) {
3611
+ const rows = await this.delegate.findMany(this.buildFindArgs());
3612
+ if (!key) return new ArkormCollection(rows.map((row) => row[column]));
3613
+ return new ArkormCollection(rows.sort((leftRow, rightRow) => String(leftRow[key]).localeCompare(String(rightRow[key]))).map((row) => row[column]));
3614
+ }
3615
+ /**
3616
+ * Creates a new record with the specified data and returns it as a model instance.
3617
+ *
3618
+ * @param data
3619
+ * @returns
3620
+ */
3621
+ async create(data) {
3622
+ const created = await this.delegate.create({ data });
3623
+ return this.model.hydrate(created);
3624
+ }
3625
+ /**
3626
+ * Updates records matching the current query constraints with the
3627
+ * specified data and returns the updated record(s) as model instance(s).
3628
+ *
3629
+ * @param data
3630
+ * @returns
3631
+ */
3632
+ async update(data) {
3633
+ const where = this.buildWhere();
3634
+ if (!where) throw new ArkormException("Update requires a where clause.");
3635
+ const uniqueWhere = await this.resolveUniqueWhere(where);
3636
+ const updated = await this.delegate.update({
3637
+ where: uniqueWhere,
3638
+ data
3639
+ });
3640
+ return this.model.hydrate(updated);
3641
+ }
3642
+ /**
3643
+ * Deletes records matching the current query constraints and returns
3644
+ * the deleted record(s) as model instance(s).
3645
+ *
3646
+ * @returns
3647
+ */
3648
+ async delete() {
3649
+ const where = this.buildWhere();
3650
+ if (!where) throw new ArkormException("Delete requires a where clause.");
3651
+ const uniqueWhere = await this.resolveUniqueWhere(where);
3652
+ const deleted = await this.delegate.delete({ where: uniqueWhere });
3653
+ return this.model.hydrate(deleted);
3654
+ }
3655
+ /**
3656
+ * Counts the number of records matching the current query constraints.
3657
+ *
3658
+ * @returns
3659
+ */
3660
+ async count() {
3661
+ if (this.hasRelationFilters()) return (await this.get()).all().length;
3662
+ return this.delegate.count({ where: this.buildWhere() });
3663
+ }
3664
+ /**
3665
+ * Determines if any records exist for the current query constraints.
3666
+ *
3667
+ * @returns
3668
+ */
3669
+ async exists() {
3670
+ if (this.hasRelationFilters()) return await this.count() > 0;
3671
+ return await this.delegate.findFirst(this.buildFindArgs()) != null;
3672
+ }
3673
+ /**
3674
+ * Determines if no records exist for the current query constraints.
3675
+ *
3676
+ * @returns
3677
+ */
3678
+ async doesntExist() {
3679
+ return !await this.exists();
3680
+ }
3681
+ /**
3682
+ * Execute callback when no records exist.
3683
+ *
3684
+ * @param callback
3685
+ * @returns
3686
+ */
3687
+ async existsOr(callback) {
3688
+ if (await this.exists()) return true;
3689
+ return callback();
3690
+ }
3691
+ /**
3692
+ * Execute callback when records exist.
3693
+ *
3694
+ * @param callback
3695
+ * @returns
3696
+ */
3697
+ async doesntExistOr(callback) {
3698
+ if (await this.doesntExist()) return true;
3699
+ return callback();
3700
+ }
3701
+ /**
3702
+ * Returns minimum value for a column.
3703
+ *
3704
+ * @param column
3705
+ * @returns
3706
+ */
3707
+ async min(column) {
3708
+ const rows = await this.delegate.findMany(this.buildFindArgs());
3709
+ if (rows.length === 0) return null;
3710
+ const values = rows.map((row) => row[column]).filter((value) => value != null);
3711
+ if (values.length === 0) return null;
3712
+ return values.reduce((minValue, currentValue) => currentValue < minValue ? currentValue : minValue);
3713
+ }
3714
+ /**
3715
+ * Returns maximum value for a column.
3716
+ *
3717
+ * @param column
3718
+ * @returns
3719
+ */
3720
+ async max(column) {
3721
+ const rows = await this.delegate.findMany(this.buildFindArgs());
3722
+ if (rows.length === 0) return null;
3723
+ const values = rows.map((row) => row[column]).filter((value) => value != null);
3724
+ if (values.length === 0) return null;
3725
+ return values.reduce((maxValue, currentValue) => currentValue > maxValue ? currentValue : maxValue);
3726
+ }
3727
+ /**
3728
+ * Returns sum of numeric values for a column.
3729
+ *
3730
+ * @param column
3731
+ * @returns
3732
+ */
3733
+ async sum(column) {
3734
+ return (await this.delegate.findMany(this.buildFindArgs())).reduce((total, row) => {
3735
+ const value = row[column];
3736
+ const numeric = typeof value === "number" ? value : Number(value);
3737
+ return Number.isFinite(numeric) ? total + numeric : total;
3738
+ }, 0);
3739
+ }
3740
+ /**
3741
+ * Returns average of numeric values for a column.
3742
+ *
3743
+ * @param column
3744
+ * @returns
3745
+ */
3746
+ async avg(column) {
3747
+ const values = (await this.delegate.findMany(this.buildFindArgs())).map((row) => {
3748
+ const value = row[column];
3749
+ return typeof value === "number" ? value : Number(value);
3750
+ }).filter((value) => Number.isFinite(value));
3751
+ if (values.length === 0) return null;
3752
+ return values.reduce((total, value) => total + value, 0) / values.length;
3753
+ }
3754
+ /**
3755
+ * Adds a raw where clause when supported by the adapter.
3756
+ *
3757
+ * @param sql
3758
+ * @param bindings
3759
+ * @returns
3760
+ */
3761
+ whereRaw(sql, bindings = []) {
3762
+ const delegate = this.delegate;
3763
+ if (typeof delegate.applyRawWhere !== "function") throw new ArkormException("Raw where clauses are not supported by the current adapter.");
3764
+ this.args.where = delegate.applyRawWhere(this.buildWhere(), sql, bindings);
3765
+ return this;
3766
+ }
3767
+ /**
3768
+ * Adds a raw OR where clause when supported by the adapter.
3769
+ *
3770
+ * @param sql
3771
+ * @param bindings
3772
+ * @returns
3773
+ */
3774
+ orWhereRaw(sql, bindings = []) {
3775
+ const delegate = this.delegate;
3776
+ if (typeof delegate.applyRawWhere !== "function") throw new ArkormException("Raw where clauses are not supported by the current adapter.");
3777
+ const rawWhere = delegate.applyRawWhere(void 0, sql, bindings);
3778
+ return this.orWhere(rawWhere);
3779
+ }
3780
+ /**
3781
+ * Paginates the query results and returns a LengthAwarePaginator instance
3782
+ * containing data and total-aware pagination metadata.
3783
+ *
3784
+ * @param page
3785
+ * @param perPage
3786
+ * @param options
3787
+ * @returns
3788
+ */
3789
+ async paginate(page = 1, perPage = 15, options = {}) {
3790
+ if (this.hasRelationFilters() || this.hasRelationAggregates()) {
3791
+ const currentPage = Math.max(1, page);
3792
+ const pageSize = Math.max(1, perPage);
3793
+ const rows = (await this.get()).all();
3794
+ const start = (currentPage - 1) * pageSize;
3795
+ return new LengthAwarePaginator(new ArkormCollection(rows.slice(start, start + pageSize)), rows.length, pageSize, currentPage, options);
3796
+ }
3797
+ const currentPage = Math.max(1, page);
3798
+ const pageSize = Math.max(1, perPage);
3799
+ const total = await this.count();
3800
+ return new LengthAwarePaginator(await this.clone().skip((currentPage - 1) * pageSize).take(pageSize).get(), total, pageSize, currentPage, options);
3801
+ }
3802
+ /**
3803
+ * Paginates results without calculating total row count.
3804
+ *
3805
+ * @param perPage
3806
+ * @param page
3807
+ * @returns
3808
+ */
3809
+ async simplePaginate(perPage = 15, page = 1, options = {}) {
3810
+ if (this.hasRelationFilters() || this.hasRelationAggregates()) {
3811
+ const currentPage = Math.max(1, page);
3812
+ const pageSize = Math.max(1, perPage);
3813
+ const rows = (await this.get()).all();
3814
+ const start = (currentPage - 1) * pageSize;
3815
+ const pageRows = rows.slice(start, start + pageSize);
3816
+ const hasMorePages = start + pageSize < rows.length;
3817
+ return new Paginator(new ArkormCollection(pageRows), pageSize, currentPage, hasMorePages, options);
3818
+ }
3819
+ const currentPage = Math.max(1, page);
3820
+ const pageSize = Math.max(1, perPage);
3821
+ const items = await this.clone().skip((currentPage - 1) * pageSize).take(pageSize + 1).get();
3822
+ const hasMorePages = items.all().length > pageSize;
3823
+ return new Paginator(hasMorePages ? new ArkormCollection(items.all().slice(0, pageSize)) : items, pageSize, currentPage, hasMorePages, options);
3824
+ }
3825
+ /**
3826
+ * Creates a clone of the current query builder instance with the same state.
3827
+ *
3828
+ * @returns
3829
+ */
3830
+ clone() {
3831
+ const builder = new QueryBuilder(this.delegate, this.model);
3832
+ builder.args.where = this.args.where;
3833
+ builder.args.include = this.args.include;
3834
+ builder.args.orderBy = this.args.orderBy;
3835
+ builder.args.select = this.args.select;
3836
+ builder.args.skip = this.args.skip;
3837
+ builder.args.take = this.args.take;
3838
+ builder.includeTrashed = this.includeTrashed;
3839
+ builder.onlyTrashedRecords = this.onlyTrashedRecords;
3840
+ builder.randomOrderEnabled = this.randomOrderEnabled;
3841
+ this.relationFilters.forEach((filter) => {
3842
+ builder.relationFilters.push({ ...filter });
3843
+ });
3844
+ this.relationAggregates.forEach((aggregate) => {
3845
+ builder.relationAggregates.push({ ...aggregate });
3846
+ });
3847
+ Object.entries(this.eagerLoads).forEach(([key, value]) => {
3848
+ builder.eagerLoads[key] = value;
3849
+ });
3850
+ return builder;
3851
+ }
3852
+ /**
3853
+ * Normalizes the input for eager loading relations into a consistent format.
3854
+ *
3855
+ * @param relations
3856
+ * @returns
3857
+ */
3858
+ normalizeWith(relations) {
3859
+ if (typeof relations === "string") return { [relations]: void 0 };
3860
+ if (Array.isArray(relations)) return relations.reduce((accumulator, relation) => {
3861
+ accumulator[relation] = void 0;
3862
+ return accumulator;
3863
+ }, {});
3864
+ return relations;
3865
+ }
3866
+ /**
3867
+ * Builds the where clause for the query, taking into account soft delete
3868
+ * settings if applicable.
3869
+ *
3870
+ * @returns
3871
+ */
3872
+ buildWhere() {
3873
+ const softDeleteConfig = this.model.getSoftDeleteConfig();
3874
+ if (!softDeleteConfig.enabled) return this.args.where;
3875
+ if (this.includeTrashed) return this.args.where;
3876
+ const softDeleteClause = this.onlyTrashedRecords ? { [softDeleteConfig.column]: { not: null } } : { [softDeleteConfig.column]: null };
3877
+ if (!this.args.where) return softDeleteClause;
3878
+ return { AND: [this.args.where, softDeleteClause] };
3879
+ }
3880
+ /**
3881
+ * Builds the arguments for the findMany delegate method, including the where clause.
3882
+ *
3883
+ * @returns
3884
+ */
3885
+ buildFindArgs() {
3886
+ return {
3887
+ ...this.args,
3888
+ where: this.buildWhere()
3889
+ };
3890
+ }
3891
+ /**
3892
+ * Resolves a unique where clause for update and delete operations.
3893
+ *
3894
+ * @param where
3895
+ * @returns
3896
+ */
3897
+ async resolveUniqueWhere(where) {
3898
+ if (this.isUniqueWhere(where)) return where;
3899
+ const row = await this.delegate.findFirst({ where });
3900
+ if (!row) throw new ArkormException("Record not found for update/delete operation.");
3901
+ const record = row;
3902
+ if (!Object.prototype.hasOwnProperty.call(record, "id")) throw new ArkormException("Unable to resolve a unique identifier for update/delete operation. Include an id in the query constraints.");
3903
+ return { id: record.id };
3904
+ }
3905
+ /**
3906
+ * Checks if the provided where clause is already a unique
3907
+ * identifier (i.e., contains only an 'id' field).
3908
+ *
3909
+ * @param where
3910
+ * @returns
3911
+ */
3912
+ isUniqueWhere(where) {
3913
+ return Object.keys(where).length === 1 && Object.prototype.hasOwnProperty.call(where, "id");
3914
+ }
3915
+ shuffleRows(rows) {
3916
+ const shuffled = [...rows];
3917
+ for (let index = shuffled.length - 1; index > 0; index--) {
3918
+ const swapIndex = Math.floor(Math.random() * (index + 1));
3919
+ const current = shuffled[index];
3920
+ shuffled[index] = shuffled[swapIndex];
3921
+ shuffled[swapIndex] = current;
3922
+ }
3923
+ return shuffled;
3924
+ }
3925
+ hasRelationFilters() {
3926
+ return this.relationFilters.length > 0;
3927
+ }
3928
+ hasOrRelationFilters() {
3929
+ return this.relationFilters.some((filter) => filter.boolean === "OR");
3930
+ }
3931
+ hasRelationAggregates() {
3932
+ return this.relationAggregates.length > 0;
3933
+ }
3934
+ async filterModelsByRelationConstraints(models, relationCache, baseIds) {
3935
+ return (await Promise.all(models.map(async (model) => {
3936
+ let result = null;
3937
+ if (baseIds) result = baseIds.has(this.getModelId(model));
3938
+ for (const filter of this.relationFilters) {
3939
+ const relatedCount = await this.resolveRelatedCount(model, filter.relation, relationCache, filter.callback);
3940
+ const condition = this.compareCount(relatedCount, filter.operator, filter.count);
3941
+ if (result == null) result = condition;
3942
+ else result = filter.boolean === "AND" ? result && condition : result || condition;
3943
+ }
3944
+ return {
3945
+ model,
3946
+ passes: result ?? true
3947
+ };
3948
+ }))).filter((entry) => entry.passes).map((entry) => entry.model);
3949
+ }
3950
+ getModelId(model) {
3951
+ const readable = model;
3952
+ if (typeof readable.getAttribute !== "function") return null;
3953
+ const id = readable.getAttribute("id");
3954
+ if (typeof id === "number" || typeof id === "string") return id;
3955
+ return null;
3956
+ }
3957
+ buildSoftDeleteOnlyWhere() {
3958
+ const softDeleteConfig = this.model.getSoftDeleteConfig();
3959
+ if (!softDeleteConfig.enabled) return void 0;
3960
+ if (this.includeTrashed) return void 0;
3961
+ return this.onlyTrashedRecords ? { [softDeleteConfig.column]: { not: null } } : { [softDeleteConfig.column]: null };
3962
+ }
3963
+ async applyRelationAggregates(models, relationCache) {
3964
+ const cache = relationCache ?? /* @__PURE__ */ new WeakMap();
3965
+ await Promise.all(models.map(async (model) => {
3966
+ for (const aggregate of this.relationAggregates) {
3967
+ const results = await this.resolveRelatedResults(model, aggregate.relation, cache);
3968
+ const list = Array.isArray(results) ? results : results ? [results] : [];
3969
+ const attributeKey = this.buildAggregateAttributeKey(aggregate);
3970
+ if (aggregate.type === "count") {
3971
+ this.assignAggregate(model, attributeKey, list.length);
3972
+ continue;
3973
+ }
3974
+ if (aggregate.type === "exists") {
3975
+ this.assignAggregate(model, attributeKey, list.length > 0);
3976
+ continue;
3977
+ }
3978
+ const values = list.map((item) => item.getAttribute(aggregate.column)).filter((value) => value != null);
3979
+ if (aggregate.type === "sum") {
3980
+ const sum = values.reduce((total, value) => {
3981
+ const numeric = typeof value === "number" ? value : Number(value);
3982
+ return Number.isFinite(numeric) ? total + numeric : total;
3983
+ }, 0);
3984
+ this.assignAggregate(model, attributeKey, sum);
3985
+ continue;
3986
+ }
3987
+ if (aggregate.type === "avg") {
3988
+ const numericValues = values.map((value) => typeof value === "number" ? value : Number(value)).filter((value) => Number.isFinite(value));
3989
+ const avg = numericValues.length === 0 ? null : numericValues.reduce((total, value) => total + value, 0) / numericValues.length;
3990
+ this.assignAggregate(model, attributeKey, avg);
3991
+ continue;
3992
+ }
3993
+ if (aggregate.type === "min") {
3994
+ const min = values.length === 0 ? null : values.reduce((left, right) => right < left ? right : left);
3995
+ this.assignAggregate(model, attributeKey, min);
3996
+ continue;
3997
+ }
3998
+ const max = values.length === 0 ? null : values.reduce((left, right) => right > left ? right : left);
3999
+ this.assignAggregate(model, attributeKey, max);
4000
+ }
4001
+ }));
4002
+ }
4003
+ async resolveRelatedCount(model, relation, relationCache, callback) {
4004
+ const results = await this.resolveRelatedResults(model, relation, relationCache, callback);
4005
+ if (Array.isArray(results)) return results.length;
4006
+ return results ? 1 : 0;
4007
+ }
4008
+ async resolveRelatedResults(model, relation, relationCache, callback) {
4009
+ const modelCacheKey = model;
4010
+ const callbackCacheKey = callback ?? "__none__";
4011
+ let relationMap = relationCache.get(modelCacheKey);
4012
+ if (!relationMap) {
4013
+ relationMap = /* @__PURE__ */ new Map();
4014
+ relationCache.set(modelCacheKey, relationMap);
4015
+ }
4016
+ let callbackMap = relationMap.get(relation);
4017
+ if (!callbackMap) {
4018
+ callbackMap = /* @__PURE__ */ new Map();
4019
+ relationMap.set(relation, callbackMap);
4020
+ }
4021
+ const cached = callbackMap.get(callbackCacheKey);
4022
+ if (cached) return await cached;
4023
+ const resolver = (async () => {
4024
+ const relationMethod = model[relation];
4025
+ if (typeof relationMethod !== "function") throw new ArkormException(`Relation [${relation}] is not defined on the model.`);
4026
+ const relationInstance = relationMethod.call(model);
4027
+ if (callback && typeof relationInstance.constrain === "function") relationInstance.constrain((query) => {
4028
+ return callback(query) ?? query;
4029
+ });
4030
+ if (typeof relationInstance.get === "function") {
4031
+ const results = await relationInstance.get();
4032
+ if (results instanceof ArkormCollection) return results.all();
4033
+ return results;
4034
+ }
4035
+ if (typeof relationInstance.getResults === "function") {
4036
+ const results = await relationInstance.getResults();
4037
+ if (results instanceof ArkormCollection) return results.all();
4038
+ return results;
4039
+ }
4040
+ throw new ArkormException(`Relation [${relation}] does not support result resolution.`);
4041
+ })();
4042
+ callbackMap.set(callbackCacheKey, resolver);
4043
+ return await resolver;
4044
+ }
4045
+ compareCount(left, operator, right) {
4046
+ if (operator === ">=") return left >= right;
4047
+ if (operator === ">") return left > right;
4048
+ if (operator === "=") return left === right;
4049
+ if (operator === "!=") return left !== right;
4050
+ if (operator === "<=") return left <= right;
4051
+ return left < right;
4052
+ }
4053
+ buildAggregateAttributeKey(aggregate) {
4054
+ const relationName = aggregate.relation;
4055
+ if (aggregate.type === "count") return `${relationName}Count`;
4056
+ if (aggregate.type === "exists") return `${relationName}Exists`;
4057
+ const columnName = aggregate.column ? `${aggregate.column.charAt(0).toUpperCase()}${aggregate.column.slice(1)}` : "";
4058
+ return `${relationName}${`${aggregate.type.charAt(0).toUpperCase()}${aggregate.type.slice(1)}`}${columnName}`;
4059
+ }
4060
+ assignAggregate(model, key, value) {
4061
+ const assignable = model;
4062
+ if (typeof assignable.setAttribute === "function") {
4063
+ assignable.setAttribute(key, value);
4064
+ return;
4065
+ }
4066
+ model[key] = value;
4067
+ }
4068
+ };
4069
+
4070
+ //#endregion
4071
+ //#region src/Model.ts
4072
+ /**
4073
+ * Base model class that all models should extend.
4074
+ *
4075
+ * @template TModel The type of the model extending this base class.
4076
+ *
4077
+ * @author Legacy (3m1n3nc3)
4078
+ * @since 0.1.0
4079
+ */
4080
+ var Model = class Model {
4081
+ static factoryClass;
4082
+ static client;
4083
+ static delegate;
4084
+ static softDeletes = false;
4085
+ static deletedAtColumn = "deletedAt";
4086
+ static globalScopes = {};
4087
+ static eventListeners = {};
4088
+ casts = {};
4089
+ hidden = [];
4090
+ visible = [];
4091
+ appends = [];
4092
+ attributes;
4093
+ constructor(attributes = {}) {
4094
+ this.attributes = {};
4095
+ this.fill(attributes);
4096
+ return new Proxy(this, {
4097
+ get: (target, key, receiver) => {
4098
+ if (typeof key !== "string" || key in target) return Reflect.get(target, key, receiver);
4099
+ return target.getAttribute(key);
4100
+ },
4101
+ set: (target, key, value, receiver) => {
4102
+ if (typeof key !== "string" || key in target) return Reflect.set(target, key, value, receiver);
4103
+ target.setAttribute(key, value);
4104
+ return true;
4105
+ }
4106
+ });
4107
+ }
4108
+ /**
4109
+ * Set the Prisma client delegates for all models.
4110
+ *
4111
+ * @param client
4112
+ */
4113
+ static setClient(client) {
4114
+ this.client = client;
4115
+ }
4116
+ static setFactory(factoryClass) {
4117
+ this.factoryClass = factoryClass;
4118
+ }
4119
+ static factory(count) {
4120
+ const factoryClass = this.factoryClass;
4121
+ if (!factoryClass) throw new ArkormException(`Factory is not configured for model [${this.name}].`);
4122
+ const factory = new factoryClass();
4123
+ if (typeof count === "number") factory.count(count);
4124
+ return factory;
4125
+ }
4126
+ /**
4127
+ * Register a global scope for the model.
4128
+ *
4129
+ * @param name
4130
+ * @param scope
4131
+ */
4132
+ static addGlobalScope(name, scope) {
4133
+ this.ensureOwnGlobalScopes();
4134
+ this.globalScopes[name] = scope;
4135
+ }
4136
+ /**
4137
+ * Remove a global scope by name.
4138
+ *
4139
+ * @param name
4140
+ */
4141
+ static removeGlobalScope(name) {
4142
+ this.ensureOwnGlobalScopes();
4143
+ delete this.globalScopes[name];
4144
+ }
4145
+ /**
4146
+ * Clear all global scopes for the model.
4147
+ */
4148
+ static clearGlobalScopes() {
4149
+ this.globalScopes = {};
4150
+ }
4151
+ /**
4152
+ * Register an event listener for a model lifecycle event.
4153
+ *
4154
+ * @param event
4155
+ * @param listener
4156
+ */
4157
+ static on(event, listener) {
4158
+ this.ensureOwnEventListeners();
4159
+ if (!this.eventListeners[event]) this.eventListeners[event] = [];
4160
+ this.eventListeners[event]?.push(listener);
4161
+ }
4162
+ /**
4163
+ * Remove listeners for an event. If listener is omitted, all listeners for that event are removed.
4164
+ *
4165
+ * @param event
4166
+ * @param listener
4167
+ */
4168
+ static off(event, listener) {
4169
+ this.ensureOwnEventListeners();
4170
+ if (!listener) {
4171
+ delete this.eventListeners[event];
4172
+ return;
4173
+ }
4174
+ this.eventListeners[event] = (this.eventListeners[event] || []).filter((registered) => registered !== listener);
4175
+ }
4176
+ /**
4177
+ * Clears all event listeners for the model.
4178
+ */
4179
+ static clearEventListeners() {
4180
+ this.eventListeners = {};
4181
+ }
4182
+ /**
4183
+ * Get the Prisma delegate for the model.
4184
+ * If a delegate name is provided, it will attempt to resolve that delegate.
4185
+ * Otherwise, it will attempt to resolve a delegate based on the model's name or
4186
+ * the static `delegate` property.
4187
+ *
4188
+ * @param delegate
4189
+ * @returns
4190
+ */
4191
+ static getDelegate(delegate) {
4192
+ ensureArkormConfigLoading();
4193
+ const key = delegate || this.delegate || `${str(this.name).camel().plural()}`;
4194
+ const candidates = [
4195
+ key,
4196
+ `${str(key).camel()}`,
4197
+ `${str(key).singular()}`,
4198
+ `${str(key).camel().singular()}`
4199
+ ];
4200
+ const runtimeClient = getRuntimePrismaClient();
4201
+ const resolved = candidates.map((name) => this.client?.[name] ?? runtimeClient?.[name]).find((candidate) => isDelegateLike(candidate));
4202
+ if (!resolved) throw new ArkormException(`Database delegate [${key}] is not configured.`);
4203
+ return resolved;
4204
+ }
4205
+ /**
4206
+ * Get a new query builder instance for the model.
4207
+ *
4208
+ * @param this
4209
+ * @returns
4210
+ */
4211
+ static query() {
4212
+ let builder = new QueryBuilder(this.getDelegate(), this);
4213
+ const modelClass = this;
4214
+ modelClass.ensureOwnGlobalScopes();
4215
+ Object.values(modelClass.globalScopes).forEach((scope) => {
4216
+ const scoped = scope(builder);
4217
+ if (scoped && scoped !== builder) builder = scoped;
4218
+ });
4219
+ return builder;
4220
+ }
4221
+ /**
4222
+ * Get a query builder instance that includes soft-deleted records.
4223
+ *
4224
+ * @param this
4225
+ * @returns
4226
+ */
4227
+ static withTrashed() {
4228
+ return this.query().withTrashed();
4229
+ }
4230
+ /**
4231
+ * Get a query builder instance that only includes soft-deleted records.
4232
+ *
4233
+ * @param this
4234
+ * @returns
4235
+ */
4236
+ static onlyTrashed() {
4237
+ return this.query().onlyTrashed();
4238
+ }
4239
+ /**
4240
+ * Get a query builder instance that excludes soft-deleted records.
4241
+ * This is the default behavior of the query builder, but this method can be used
4242
+ * to explicitly specify it after using `withTrashed` or `onlyTrashed`.
4243
+ *
4244
+ * @param this
4245
+ * @param name
4246
+ * @param args
4247
+ * @returns
4248
+ */
4249
+ static scope(name, ...args) {
4250
+ return this.query().scope(name, ...args);
4251
+ }
4252
+ /**
4253
+ * Get the soft delete configuration for the model, including whether
4254
+ * soft deletes are enabled and the name of the deleted at column.
4255
+ *
4256
+ * @returns
4257
+ */
4258
+ static getSoftDeleteConfig() {
4259
+ return {
4260
+ enabled: this.softDeletes,
4261
+ column: this.deletedAtColumn
4262
+ };
4263
+ }
4264
+ /**
4265
+ * Hydrate a model instance from a plain object of attributes.
4266
+ *
4267
+ * @param this
4268
+ * @param attributes
4269
+ * @returns
4270
+ */
4271
+ static hydrate(attributes) {
4272
+ return new this(attributes);
4273
+ }
4274
+ /**
4275
+ * Hydrate multiple model instances from an array of plain objects of attributes.
4276
+ *
4277
+ * @param this
4278
+ * @param attributes
4279
+ * @returns
4280
+ */
4281
+ static hydrateMany(attributes) {
4282
+ return attributes.map((attribute) => new this(attribute));
4283
+ }
4284
+ fill(attributes) {
4285
+ Object.entries(attributes).forEach(([key, value]) => {
4286
+ this.setAttribute(key, value);
4287
+ });
4288
+ return this;
4289
+ }
4290
+ getAttribute(key) {
4291
+ const mutator = this.resolveGetMutator(key);
4292
+ const cast = this.casts[key];
4293
+ let value = this.attributes[key];
4294
+ if (cast) value = resolveCast(cast).get(value);
4295
+ if (mutator) return mutator.call(this, value);
4296
+ return value;
4297
+ }
4298
+ setAttribute(key, value) {
4299
+ const mutator = this.resolveSetMutator(key);
4300
+ const cast = this.casts[key];
4301
+ let resolved = value;
4302
+ if (mutator) resolved = mutator.call(this, resolved);
4303
+ if (cast) resolved = resolveCast(cast).set(resolved);
4304
+ this.attributes[key] = resolved;
4305
+ return this;
4306
+ }
4307
+ /**
4308
+ * Save the model to the database.
4309
+ * If the model has an identifier (id), it will perform an update.
4310
+ * Otherwise, it will perform a create.
4311
+ *
4312
+ * @returns
4313
+ */
4314
+ async save() {
4315
+ const identifier = this.getAttribute("id");
4316
+ const payload = this.getRawAttributes();
4317
+ const constructor = this.constructor;
4318
+ if (identifier == null) {
4319
+ await Model.dispatchEvent(constructor, "saving", this);
4320
+ await Model.dispatchEvent(constructor, "creating", this);
4321
+ const model = await constructor.query().create(payload);
4322
+ this.fill(model.getRawAttributes());
4323
+ await Model.dispatchEvent(constructor, "created", this);
4324
+ await Model.dispatchEvent(constructor, "saved", this);
4325
+ return this;
4326
+ }
4327
+ await Model.dispatchEvent(constructor, "saving", this);
4328
+ await Model.dispatchEvent(constructor, "updating", this);
4329
+ const model = await constructor.query().where({ id: identifier }).update(payload);
4330
+ this.fill(model.getRawAttributes());
4331
+ await Model.dispatchEvent(constructor, "updated", this);
4332
+ await Model.dispatchEvent(constructor, "saved", this);
4333
+ return this;
4334
+ }
4335
+ /**
4336
+ * Delete the model from the database.
4337
+ * If soft deletes are enabled, it will perform a soft delete by
4338
+ * setting the deleted at column to the current date.
4339
+ * Otherwise, it will perform a hard delete.
4340
+ *
4341
+ * @returns
4342
+ */
4343
+ async delete() {
4344
+ const identifier = this.getAttribute("id");
4345
+ if (identifier == null) throw new ArkormException("Cannot delete a model without an id.");
4346
+ const constructor = this.constructor;
4347
+ await Model.dispatchEvent(constructor, "deleting", this);
4348
+ const softDeleteConfig = constructor.getSoftDeleteConfig();
4349
+ if (softDeleteConfig.enabled) {
4350
+ const model = await constructor.query().where({ id: identifier }).update({ [softDeleteConfig.column]: /* @__PURE__ */ new Date() });
4351
+ this.fill(model.getRawAttributes());
4352
+ await Model.dispatchEvent(constructor, "deleted", this);
4353
+ return this;
4354
+ }
4355
+ const deleted = await constructor.query().where({ id: identifier }).delete();
4356
+ this.fill(deleted.getRawAttributes());
4357
+ await Model.dispatchEvent(constructor, "deleted", this);
4358
+ return this;
4359
+ }
4360
+ /**
4361
+ * Permanently delete the model from the database, regardless of whether soft
4362
+ * deletes are enabled.
4363
+ *
4364
+ * @returns
4365
+ */
4366
+ async forceDelete() {
4367
+ const identifier = this.getAttribute("id");
4368
+ if (identifier == null) throw new ArkormException("Cannot force delete a model without an id.");
4369
+ const constructor = this.constructor;
4370
+ await Model.dispatchEvent(constructor, "forceDeleting", this);
4371
+ await Model.dispatchEvent(constructor, "deleting", this);
4372
+ const deleted = await constructor.query().withTrashed().where({ id: identifier }).delete();
4373
+ this.fill(deleted.getRawAttributes());
4374
+ await Model.dispatchEvent(constructor, "deleted", this);
4375
+ await Model.dispatchEvent(constructor, "forceDeleted", this);
4376
+ return this;
4377
+ }
4378
+ /**
4379
+ * Restore a soft-deleted model by setting the deleted at column to null.
4380
+ *
4381
+ * @returns
4382
+ */
4383
+ async restore() {
4384
+ const identifier = this.getAttribute("id");
4385
+ if (identifier == null) throw new ArkormException("Cannot restore a model without an id.");
4386
+ const constructor = this.constructor;
4387
+ const softDeleteConfig = constructor.getSoftDeleteConfig();
4388
+ if (!softDeleteConfig.enabled) return this;
4389
+ await Model.dispatchEvent(constructor, "restoring", this);
4390
+ const model = await constructor.query().withTrashed().where({ id: identifier }).update({ [softDeleteConfig.column]: null });
4391
+ this.fill(model.getRawAttributes());
4392
+ await Model.dispatchEvent(constructor, "restored", this);
4393
+ return this;
4394
+ }
4395
+ /**
4396
+ * Load related models onto the current model instance.
4397
+ *
4398
+ * @param relations
4399
+ * @returns
4400
+ */
4401
+ async load(relations) {
4402
+ const relationMap = this.normalizeRelationMap(relations);
4403
+ await Promise.all(Object.entries(relationMap).map(async ([name, constraint]) => {
4404
+ const resolver = this[name];
4405
+ if (typeof resolver !== "function") return;
4406
+ const relation = resolver.call(this);
4407
+ if (constraint) relation.constrain(constraint);
4408
+ const results = await relation.getResults();
4409
+ this.attributes[name] = results;
4410
+ }));
4411
+ return this;
4412
+ }
4413
+ /**
4414
+ * Get the raw attributes of the model without applying any mutators or casts.
4415
+ *
4416
+ * @returns
4417
+ */
4418
+ getRawAttributes() {
4419
+ return { ...this.attributes };
4420
+ }
4421
+ /**
4422
+ * Convert the model instance to a plain object, applying visibility
4423
+ * rules, appends, and mutators.
4424
+ *
4425
+ * @returns
4426
+ */
4427
+ toObject() {
4428
+ const object = (this.visible.length > 0 ? this.visible : Object.keys(this.attributes).filter((key) => !this.hidden.includes(key))).reduce((accumulator, key) => {
4429
+ let value = this.getAttribute(key);
4430
+ if (value instanceof Date) value = value.toISOString();
4431
+ accumulator[key] = value;
4432
+ return accumulator;
4433
+ }, {});
4434
+ this.appends.forEach((attribute) => {
4435
+ object[attribute] = this.getAttribute(attribute);
4436
+ });
4437
+ return object;
4438
+ }
4439
+ /**
4440
+ * Convert the model instance to JSON by first converting it to a plain object.
4441
+ *
4442
+ * @returns
4443
+ */
4444
+ toJSON() {
4445
+ return this.toObject();
4446
+ }
4447
+ /**
4448
+ * Define a has one relationship.
4449
+ *
4450
+ * @param related
4451
+ * @param foreignKey
4452
+ * @param localKey
4453
+ * @returns
4454
+ */
4455
+ hasOne(related, foreignKey, localKey = "id") {
4456
+ return new HasOneRelation(this, related, foreignKey, localKey);
4457
+ }
4458
+ /**
4459
+ * Define a has many relationship.
4460
+ *
4461
+ * @param related
4462
+ * @param foreignKey
4463
+ * @param localKey
4464
+ * @returns
4465
+ */
4466
+ hasMany(related, foreignKey, localKey = "id") {
4467
+ return new HasManyRelation(this, related, foreignKey, localKey);
4468
+ }
4469
+ /**
4470
+ * Define a belongs to relationship.
4471
+ *
4472
+ * @param related
4473
+ * @param foreignKey
4474
+ * @param ownerKey
4475
+ * @returns
4476
+ */
4477
+ belongsTo(related, foreignKey, ownerKey = "id") {
4478
+ return new BelongsToRelation(this, related, foreignKey, ownerKey);
4479
+ }
4480
+ /**
4481
+ * Define a belongs to many relationship.
4482
+ *
4483
+ * @param related
4484
+ * @param throughDelegate
4485
+ * @param foreignPivotKey
4486
+ * @param relatedPivotKey
4487
+ * @param parentKey
4488
+ * @param relatedKey
4489
+ * @returns
4490
+ */
4491
+ belongsToMany(related, throughDelegate, foreignPivotKey, relatedPivotKey, parentKey = "id", relatedKey = "id") {
4492
+ return new BelongsToManyRelation(this, related, throughDelegate, foreignPivotKey, relatedPivotKey, parentKey, relatedKey);
4493
+ }
4494
+ /**
4495
+ * Define a has one through relationship.
4496
+ *
4497
+ * @param related
4498
+ * @param throughDelegate
4499
+ * @param firstKey
4500
+ * @param secondKey
4501
+ * @param localKey
4502
+ * @param secondLocalKey
4503
+ * @returns
4504
+ */
4505
+ hasOneThrough(related, throughDelegate, firstKey, secondKey, localKey = "id", secondLocalKey = "id") {
4506
+ return new HasOneThroughRelation(this, related, throughDelegate, firstKey, secondKey, localKey, secondLocalKey);
4507
+ }
4508
+ /**
4509
+ * Define a has many through relationship.
4510
+ *
4511
+ * @param related
4512
+ * @param throughDelegate
4513
+ * @param firstKey
4514
+ * @param secondKey
4515
+ * @param localKey
4516
+ * @param secondLocalKey
4517
+ * @returns
4518
+ */
4519
+ hasManyThrough(related, throughDelegate, firstKey, secondKey, localKey = "id", secondLocalKey = "id") {
4520
+ return new HasManyThroughRelation(this, related, throughDelegate, firstKey, secondKey, localKey, secondLocalKey);
4521
+ }
4522
+ /**
4523
+ * Define a polymorphic one to one relationship.
4524
+ *
4525
+ * @param related
4526
+ * @param morphName
4527
+ * @param localKey
4528
+ * @returns
4529
+ */
4530
+ morphOne(related, morphName, localKey = "id") {
4531
+ return new MorphOneRelation(this, related, morphName, localKey);
4532
+ }
4533
+ /**
4534
+ * Define a polymorphic one to many relationship.
4535
+ *
4536
+ * @param related
4537
+ * @param morphName
4538
+ * @param localKey
4539
+ * @returns
4540
+ */
4541
+ morphMany(related, morphName, localKey = "id") {
4542
+ return new MorphManyRelation(this, related, morphName, localKey);
4543
+ }
4544
+ /**
4545
+ * Define a polymorphic many to many relationship.
4546
+ *
4547
+ * @param related
4548
+ * @param throughDelegate
4549
+ * @param morphName
4550
+ * @param relatedPivotKey
4551
+ * @param parentKey
4552
+ * @param relatedKey
4553
+ * @returns
4554
+ */
4555
+ morphToMany(related, throughDelegate, morphName, relatedPivotKey, parentKey = "id", relatedKey = "id") {
4556
+ return new MorphToManyRelation(this, related, throughDelegate, morphName, relatedPivotKey, parentKey, relatedKey);
4557
+ }
4558
+ /**
4559
+ * Resolve a get mutator method for a given attribute key, if it exists.
4560
+ *
4561
+ * @param key
4562
+ * @returns
4563
+ */
4564
+ resolveGetMutator(key) {
4565
+ const methodName = `get${str(key).studly()}Attribute`;
4566
+ const method = this[methodName];
4567
+ return typeof method === "function" ? method : null;
4568
+ }
4569
+ /**
4570
+ * Resolve a set mutator method for a given attribute key, if it exists.
4571
+ *
4572
+ * @param key
4573
+ * @returns
4574
+ */
4575
+ resolveSetMutator(key) {
4576
+ const methodName = `set${str(key).studly()}Attribute`;
4577
+ const method = this[methodName];
4578
+ return typeof method === "function" ? method : null;
4579
+ }
4580
+ /**
4581
+ * Ensures global scopes are own properties on subclass constructors.
4582
+ */
4583
+ static ensureOwnGlobalScopes() {
4584
+ if (!Object.prototype.hasOwnProperty.call(this, "globalScopes")) this.globalScopes = { ...this.globalScopes || {} };
4585
+ }
4586
+ /**
4587
+ * Ensures event listeners are own properties on subclass constructors.
4588
+ */
4589
+ static ensureOwnEventListeners() {
4590
+ if (!Object.prototype.hasOwnProperty.call(this, "eventListeners")) this.eventListeners = { ...this.eventListeners || {} };
4591
+ }
4592
+ /**
4593
+ * Dispatches lifecycle events to registered listeners.
4594
+ *
4595
+ * @param modelClass
4596
+ * @param event
4597
+ * @param model
4598
+ */
4599
+ static async dispatchEvent(modelClass, event, model) {
4600
+ modelClass.ensureOwnEventListeners();
4601
+ const listeners = modelClass.eventListeners[event] || [];
4602
+ for (const listener of listeners) await listener(model);
4603
+ }
4604
+ /**
4605
+ * Normalize the relation map for eager loading.
4606
+ *
4607
+ * @param relations
4608
+ * @returns
4609
+ */
4610
+ normalizeRelationMap(relations) {
4611
+ if (typeof relations === "string") return { [relations]: void 0 };
4612
+ if (Array.isArray(relations)) return relations.reduce((accumulator, relation) => {
4613
+ accumulator[relation] = void 0;
4614
+ return accumulator;
4615
+ }, {});
4616
+ return relations;
4617
+ }
4618
+ };
4619
+
4620
+ //#endregion
4621
+ export { ArkormCollection, ArkormException, CliApp, InitCommand, InlineFactory, LengthAwarePaginator, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, Migration, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, QueryBuilder, SchemaBuilder, SeedCommand, Seeder, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildMigrationSource, buildModelBlock, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, ensureArkormConfigLoading, escapeRegex, findModelBlock, formatDefaultValue, generateMigrationFile, getDefaultStubsPath, getMigrationPlan, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, loadArkormConfig, pad, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName };