@@ -18,7 +18,7 @@ import { getTraitTables } from '@stacksjs/database'
1818import  {  handleError  }  from  '@stacksjs/error-handling' 
1919import  {  path  }  from  '@stacksjs/path' 
2020import  {  fs  }  from  '@stacksjs/storage' 
21- import  {  camelCase ,  kebabCase ,  plural ,  singular ,  slugify ,  snakeCase  }  from  '@stacksjs/strings' 
21+ import  {  camelCase ,  kebabCase ,  pascalCase ,   plural ,  singular ,  slugify ,  snakeCase  }  from  '@stacksjs/strings' 
2222import  {  isString  }  from  '@stacksjs/validation' 
2323import  {  globSync  }  from  'tinyglobby' 
2424import  {  generateModelString  }  from  './generate' 
@@ -1133,30 +1133,57 @@ export async function generateApiRoutes(modelFiles: string[]): Promise<void> {
11331133  await  writer . end ( ) 
11341134} 
11351135
1136+ export  async  function  deleteExistingTypes ( ) : Promise < void >  { 
1137+   try  { 
1138+     const  typeFiles  =  globSync ( path . frameworkPath ( 'orm/src/types/*Type.ts' ) ) 
1139+     for  ( const  file  of  typeFiles )  { 
1140+       await  Bun . write ( file ,  '' ) 
1141+       log . info ( 'Deleted type file:' ,  file ) 
1142+     } 
1143+   } 
1144+   catch  ( error )  { 
1145+     handleError ( error ) 
1146+   } 
1147+ } 
1148+ 
11361149export  async  function  deleteExistingModels ( modelStringFile ?: string ) : Promise < void >  { 
1137-   const  typePath  =  path . frameworkPath ( `orm/src/types.ts` ) 
1150+   try  { 
1151+     const  typePath  =  path . frameworkPath ( `orm/src/types.ts` ) 
11381152
1139-   await  fs . writeFile ( typePath ,  '' ) 
1153+      await  fs . writeFile ( typePath ,  '' ) 
11401154
1141-   if  ( modelStringFile )  { 
1142-     const  modelPath  =  path . frameworkPath ( `orm/src/models/${ modelStringFile }  .ts` ) 
1143-     if  ( fs . existsSync ( modelPath ) ) 
1144-       await  fs . promises . unlink ( modelPath ) 
1155+      if  ( modelStringFile )  { 
1156+        const  modelPath  =  path . frameworkPath ( `orm/src/models/${ modelStringFile }  .ts` ) 
1157+        if  ( fs . existsSync ( modelPath ) ) 
1158+          await  fs . promises . unlink ( modelPath ) 
11451159
1146-     return 
1147-   } 
1160+        return 
1161+      } 
11481162
1149-   const  modelPaths  =  globSync ( [ path . frameworkPath ( `orm/src/models/*.ts` ) ] ,  {  absolute : true  } ) 
1163+      const  modelPaths  =  globSync ( [ path . frameworkPath ( `orm/src/models/*.ts` ) ] ,  {  absolute : true  } ) 
11501164
1151-   await  Promise . all ( 
1152-     modelPaths . map ( async  ( modelPath )  =>  { 
1153-       if  ( fs . existsSync ( modelPath ) )  { 
1154-         log . info ( `Deleting Model: ${ italic ( modelPath ) }  ` ) 
1155-         await  fs . promises . unlink ( modelPath ) 
1156-         log . success ( `Deleted Model: ${ italic ( modelPath ) }  ` ) 
1157-       } 
1158-     } ) , 
1159-   ) 
1165+     await  Promise . all ( 
1166+       modelPaths . map ( async  ( modelPath )  =>  { 
1167+         if  ( fs . existsSync ( modelPath ) )  { 
1168+           log . info ( `Deleting Model: ${ italic ( modelPath ) }  ` ) 
1169+           await  fs . promises . unlink ( modelPath ) 
1170+           log . success ( `Deleted Model: ${ italic ( modelPath ) }  ` ) 
1171+         } 
1172+       } ) , 
1173+     ) 
1174+ 
1175+     await  deleteExistingTypes ( ) 
1176+     await  deleteExistingOrmActions ( modelStringFile ) 
1177+     await  deleteExistingModelNameTypes ( ) 
1178+     await  deleteAttributeTypes ( ) 
1179+     await  deleteModelEvents ( ) 
1180+     await  deleteOrmImports ( ) 
1181+     await  deleteExistingModelRequest ( modelStringFile ) 
1182+     await  deleteExistingOrmRoute ( ) 
1183+   } 
1184+   catch  ( error )  { 
1185+     handleError ( error ) 
1186+   } 
11601187} 
11611188
11621189export  async  function  deleteExistingOrmActions ( modelStringFile ?: string ) : Promise < void >  { 
@@ -1449,6 +1476,8 @@ export async function generateModelFiles(modelStringFile?: string): Promise<void
14491476      const  imports  =  extractImports ( modelFile ) 
14501477      const  classString  =  await  generateModelString ( tableName ,  modelName ,  model ,  fields ,  imports ) 
14511478
1479+       await  writeTypeFile ( tableName ,  modelName ,  model ,  fields ) 
1480+ 
14521481      const  writer  =  file . writer ( ) 
14531482      log . info ( `Writing Model: ${ italic ( modelName ) }  ` ) 
14541483      writer . write ( classString ) 
@@ -1480,8 +1509,9 @@ async function writeModelOrmImports(modelFiles: string[]): Promise<void> {
14801509    const  model  =  ( await  import ( modelFile ) ) . default  as  Model 
14811510
14821511    const  modelName  =  getModelName ( model ,  modelFile ) 
1512+     const  tableName  =  getTableName ( model ,  modelFile ) 
14831513
1484-     ormImportString  +=  `export { default as ${ modelName }  , type ${ modelName }  JsonResponse, ${ modelName }  Model, type New${ modelName }  , type ${ modelName }  Update } from './models/${ modelName }  '\n\n` 
1514+     ormImportString  +=  `export { default as ${ modelName }  , type ${ modelName }  JsonResponse, ${ modelName }  Model, type ${ pascalCase ( tableName ) } Table, type  New${ modelName }  , type ${ modelName }  Update } from './models/${ modelName }  '\n\n` 
14851515  } 
14861516
14871517  const  file  =  Bun . file ( path . frameworkPath ( `orm/src/index.ts` ) ) 
@@ -1596,3 +1626,185 @@ export function formatDate(date: Date): string {
15961626export  function  toTimestamp ( date : Date ) : number  { 
15971627  return  date . getTime ( ) 
15981628} 
1629+ 
1630+ export  async  function  generateTypeString ( 
1631+   tableName : string , 
1632+   modelName : string , 
1633+   model : Model , 
1634+   attributes : ModelElement [ ] , 
1635+ ) : Promise < string >  { 
1636+   const  formattedTableName  =  pascalCase ( tableName ) 
1637+ 
1638+   // Generate the base table interface 
1639+   let  tableInterface  =  `export interface ${ formattedTableName }  Table { 
1640+   id: Generated<number> 
1641+ ` 
1642+ 
1643+   // Add attributes to the table interface 
1644+   for  ( const  attribute  of  attributes )  { 
1645+     const  entity  =  mapEntity ( attribute ) 
1646+     const  optionalIndicator  =  attribute . required  ===  false  ? '?'  : '' 
1647+     tableInterface  +=  `  ${ snakeCase ( attribute . field ) } ${ optionalIndicator }  : ${ entity }  \n` 
1648+   } 
1649+ 
1650+   // Add common fields 
1651+   if  ( model . traits ?. useUuid ) 
1652+     tableInterface  +=  '  uuid?: string\n' 
1653+ 
1654+   if  ( model . traits ?. useTimestamps  ??  model . traits ?. timestampable  ??  true )  { 
1655+     tableInterface  +=  '  created_at?: string\n' 
1656+     tableInterface  +=  '  updated_at?: string\n' 
1657+   } 
1658+ 
1659+   if  ( model . traits ?. useSoftDeletes  ??  model . traits ?. softDeletable  ??  false ) 
1660+     tableInterface  +=  '  deleted_at?: string\n' 
1661+ 
1662+   tableInterface  +=  '}\n\n' 
1663+ 
1664+   // Generate the read type 
1665+   const  readType  =  `export type ${ modelName }  Read = ${ formattedTableName }  Table\n\n` 
1666+ 
1667+   // Generate the write type 
1668+   const  writeType  =  `export type ${ modelName }  Write = Omit<${ formattedTableName }  Table, 'created_at'> & { 
1669+   created_at?: string 
1670+ }\n\n` 
1671+ 
1672+   // Generate the response interface 
1673+   const  responseInterface  =  `export interface ${ modelName }  Response { 
1674+   data: ${ modelName }  JsonResponse[] 
1675+   paging: { 
1676+     total_records: number 
1677+     page: number 
1678+     total_pages: number 
1679+   } 
1680+   next_cursor: number | null 
1681+ }\n\n` 
1682+ 
1683+   // Generate the JSON response interface 
1684+   const  jsonResponseInterface  =  `export interface ${ modelName }  JsonResponse extends Omit<Selectable<${ modelName }  Read>, 'password'> { 
1685+   [key: string]: any 
1686+ }\n\n` 
1687+ 
1688+   // Generate the new and update types 
1689+   const  newType  =  `export type New${ modelName }   = Insertable<${ modelName }  Write>\n` 
1690+   const  updateType  =  `export type ${ modelName }  Update = Updateable<${ modelName }  Write>\n\n` 
1691+ 
1692+   // Generate the static interface 
1693+   const  staticInterface  =  `export interface I${ modelName }  ModelStatic { 
1694+   with: (relations: string[]) => I${ modelName }  Model 
1695+   select: (params: (keyof ${ modelName }  JsonResponse)[] | RawBuilder<string> | string) => I${ modelName }  Model 
1696+   find: (id: number) => Promise<I${ modelName }  Model | undefined> 
1697+   first: () => Promise<I${ modelName }  Model | undefined> 
1698+   last: () => Promise<I${ modelName }  Model | undefined> 
1699+   firstOrFail: () => Promise<I${ modelName }  Model | undefined> 
1700+   all: () => Promise<I${ modelName }  Model[]> 
1701+   findOrFail: (id: number) => Promise<I${ modelName }  Model | undefined> 
1702+   findMany: (ids: number[]) => Promise<I${ modelName }  Model[]> 
1703+   latest: (column?: keyof ${ formattedTableName }  Table) => Promise<I${ modelName }  Model | undefined> 
1704+   oldest: (column?: keyof ${ formattedTableName }  Table) => Promise<I${ modelName }  Model | undefined> 
1705+   skip: (count: number) => I${ modelName }  Model 
1706+   take: (count: number) => I${ modelName }  Model 
1707+   where: <V = string>(column: keyof ${ formattedTableName }  Table, ...args: [V] | [Operator, V]) => I${ modelName }  Model 
1708+   orWhere: (...conditions: [string, any][]) => I${ modelName }  Model 
1709+   whereNotIn: <V = number>(column: keyof ${ formattedTableName }  Table, values: V[]) => I${ modelName }  Model 
1710+   whereBetween: <V = number>(column: keyof ${ formattedTableName }  Table, range: [V, V]) => I${ modelName }  Model 
1711+   whereRef: (column: keyof ${ formattedTableName }  Table, ...args: string[]) => I${ modelName }  Model 
1712+   when: (condition: boolean, callback: (query: I${ modelName }  Model) => I${ modelName }  Model) => I${ modelName }  Model 
1713+   whereNull: (column: keyof ${ formattedTableName }  Table) => I${ modelName }  Model 
1714+   whereNotNull: (column: keyof ${ formattedTableName }  Table) => I${ modelName }  Model 
1715+   whereLike: (column: keyof ${ formattedTableName }  Table, value: string) => I${ modelName }  Model 
1716+   orderBy: (column: keyof ${ formattedTableName }  Table, order: 'asc' | 'desc') => I${ modelName }  Model 
1717+   orderByAsc: (column: keyof ${ formattedTableName }  Table) => I${ modelName }  Model 
1718+   orderByDesc: (column: keyof ${ formattedTableName }  Table) => I${ modelName }  Model 
1719+   groupBy: (column: keyof ${ formattedTableName }  Table) => I${ modelName }  Model 
1720+   having: <V = string>(column: keyof ${ formattedTableName }  Table, operator: Operator, value: V) => I${ modelName }  Model 
1721+   inRandomOrder: () => I${ modelName }  Model 
1722+   whereColumn: (first: keyof ${ formattedTableName }  Table, operator: Operator, second: keyof ${ formattedTableName }  Table) => I${ modelName }  Model 
1723+   max: (field: keyof ${ formattedTableName }  Table) => Promise<number> 
1724+   min: (field: keyof ${ formattedTableName }  Table) => Promise<number> 
1725+   avg: (field: keyof ${ formattedTableName }  Table) => Promise<number> 
1726+   sum: (field: keyof ${ formattedTableName }  Table) => Promise<number> 
1727+   count: () => Promise<number> 
1728+   get: () => Promise<I${ modelName }  Model[]> 
1729+   pluck: <K extends keyof I${ modelName }  Model>(field: K) => Promise<I${ modelName }  Model[K][]> 
1730+   chunk: (size: number, callback: (models: I${ modelName }  Model[]) => Promise<void>) => Promise<void> 
1731+   paginate: (options?: { limit?: number, offset?: number, page?: number }) => Promise<{ 
1732+     data: I${ modelName }  Model[] 
1733+     paging: { 
1734+       total_records: number 
1735+       page: number 
1736+       total_pages: number 
1737+     } 
1738+     next_cursor: number | null 
1739+   }> 
1740+   create: (new${ modelName }  : New${ modelName }  ) => Promise<I${ modelName }  Model> 
1741+   firstOrCreate: (search: Partial<${ formattedTableName }  Table>, values?: New${ modelName }  ) => Promise<I${ modelName }  Model> 
1742+   updateOrCreate: (search: Partial<${ formattedTableName }  Table>, values?: New${ modelName }  ) => Promise<I${ modelName }  Model> 
1743+   createMany: (new${ modelName }  : New${ modelName }  []) => Promise<void> 
1744+   forceCreate: (new${ modelName }  : New${ modelName }  ) => Promise<I${ modelName }  Model> 
1745+   remove: (id: number) => Promise<any> 
1746+   whereIn: <V = number>(column: keyof ${ formattedTableName }  Table, values: V[]) => I${ modelName }  Model 
1747+   distinct: (column: keyof ${ modelName }  JsonResponse) => I${ modelName }  Model 
1748+   join: (table: string, firstCol: string, secondCol: string) => I${ modelName }  Model 
1749+ }\n\n` 
1750+ 
1751+   // Generate the instance interface 
1752+   let  instanceInterface  =  `export interface I${ modelName }  Model { 
1753+   // Properties 
1754+   readonly id: number\n` 
1755+ 
1756+   // Add getters and setters for each attribute 
1757+   for  ( const  attribute  of  attributes )  { 
1758+     const  field  =  snakeCase ( attribute . field ) 
1759+     const  optionalIndicator  =  attribute . required  ===  false  ? ' | undefined'  : '' 
1760+     instanceInterface  +=  `  get ${ field }  (): ${ mapEntity ( attribute ) } ${ optionalIndicator }  \n` 
1761+     instanceInterface  +=  `  set ${ field }  (value: ${ mapEntity ( attribute ) }  )\n` 
1762+   } 
1763+ 
1764+   // Add common getters and setters 
1765+   if  ( model . traits ?. useUuid )  { 
1766+     instanceInterface  +=  '  get uuid(): string | undefined\n' 
1767+     instanceInterface  +=  '  set uuid(value: string)\n' 
1768+   } 
1769+ 
1770+   if  ( model . traits ?. useTimestamps  ??  model . traits ?. timestampable  ??  true )  { 
1771+     instanceInterface  +=  '  get created_at(): string | undefined\n' 
1772+     instanceInterface  +=  '  get updated_at(): string | undefined\n' 
1773+     instanceInterface  +=  '  set updated_at(value: string)\n' 
1774+   } 
1775+ 
1776+   // Add instance methods 
1777+   instanceInterface  +=  ` 
1778+   // Instance methods 
1779+   createInstance: (data: ${ modelName }  JsonResponse) => I${ modelName }  Model 
1780+   create: (new${ modelName }  : New${ modelName }  ) => Promise<I${ modelName }  Model> 
1781+   update: (new${ modelName }  : ${ modelName }  Update) => Promise<I${ modelName }  Model | undefined> 
1782+   forceUpdate: (new${ modelName }  : ${ modelName }  Update) => Promise<I${ modelName }  Model | undefined> 
1783+   save: () => Promise<I${ modelName }  Model> 
1784+   delete: () => Promise<number> 
1785+   toSearchableObject: () => Partial<${ modelName }  JsonResponse> 
1786+   toJSON: () => ${ modelName }  JsonResponse 
1787+   parseResult: (model: I${ modelName }  Model) => I${ modelName }  Model 
1788+ }\n\n` 
1789+ 
1790+   // Generate the combined type 
1791+   const  combinedType  =  `export type ${ modelName }  ModelType = I${ modelName }  Model & I${ modelName }  ModelStatic\n` 
1792+ 
1793+   // Combine all type declarations 
1794+   return  `import type { Generated, Insertable, Selectable, Updateable, RawBuilder } from '@stacksjs/database' 
1795+ import type { Operator } from '@stacksjs/orm' 
1796+ 
1797+ ${ tableInterface } ${ readType } ${ writeType } ${ responseInterface } ${ jsonResponseInterface } ${ newType } ${ updateType } ${ staticInterface } ${ instanceInterface } ${ combinedType }  `
1798+ } 
1799+ 
1800+ export  async  function  writeTypeFile ( 
1801+   tableName : string , 
1802+   modelName : string , 
1803+   model : Model , 
1804+   attributes : ModelElement [ ] , 
1805+ ) : Promise < void >  { 
1806+   log . info ( 'Writing type file for' ,  modelName ) 
1807+   const  typeString  =  await  generateTypeString ( tableName ,  modelName ,  model ,  attributes ) 
1808+   const  typeFilePath  =  path . frameworkPath ( `orm/src/types/${ modelName }  Type.ts` ) 
1809+   await  Bun . write ( typeFilePath ,  typeString ) 
1810+ } 
0 commit comments