@stacksjs/actions 0.62.0 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/add.js +3 -0
  2. package/dist/add.js.map +10 -0
  3. package/dist/build/component-libs.js +3 -0
  4. package/dist/build/component-libs.js.map +10 -0
  5. package/dist/build/core.js +3 -0
  6. package/dist/build/core.js.map +10 -0
  7. package/dist/build/stacks.js +3 -0
  8. package/dist/build/stacks.js.map +10 -0
  9. package/dist/build.js +6 -4
  10. package/dist/build.js.map +14 -0
  11. package/dist/bump.js +7 -4
  12. package/dist/bump.js.map +10 -0
  13. package/dist/changelog.js +3 -0
  14. package/dist/changelog.js.map +10 -0
  15. package/dist/clean.js +3 -0
  16. package/dist/clean.js.map +10 -0
  17. package/dist/commit.js +3 -0
  18. package/dist/commit.js.map +10 -0
  19. package/dist/copy-types.js +3 -0
  20. package/dist/copy-types.js.map +10 -0
  21. package/dist/database/seed.js +3 -0
  22. package/dist/database/seed.js.map +10 -0
  23. package/dist/deploy/index.js +14 -5
  24. package/dist/deploy/index.js.map +10 -0
  25. package/dist/dev/components.js +3 -0
  26. package/dist/dev/components.js.map +10 -0
  27. package/dist/dev/docs.js +3 -0
  28. package/dist/dev/docs.js.map +10 -0
  29. package/dist/dev/index.js +4 -2
  30. package/dist/dev/index.js.map +11 -0
  31. package/dist/examples.js +3 -0
  32. package/dist/examples.js.map +10 -0
  33. package/dist/fresh.js +3 -0
  34. package/dist/fresh.js.map +10 -0
  35. package/dist/generate/component-meta.js +3 -0
  36. package/dist/generate/component-meta.js.map +11 -0
  37. package/dist/generate/ide-helpers.js +5 -2
  38. package/dist/generate/ide-helpers.js.map +12 -0
  39. package/dist/generate/index.js +6 -4
  40. package/dist/generate/index.js.map +13 -0
  41. package/dist/generate/lib-entries.js +3 -0
  42. package/dist/generate/lib-entries.js.map +12 -0
  43. package/dist/generate/vscode-custom-data.js +5 -2
  44. package/dist/generate/vscode-custom-data.js.map +12 -0
  45. package/dist/helpers/component-meta.js +3 -0
  46. package/dist/helpers/component-meta.js.map +10 -0
  47. package/dist/helpers/lib-entries.js +3 -0
  48. package/dist/helpers/lib-entries.js.map +11 -0
  49. package/dist/helpers/package-json.js +3 -0
  50. package/dist/helpers/package-json.js.map +11 -0
  51. package/dist/helpers/utils.js +4 -2
  52. package/dist/helpers/utils.js.map +10 -0
  53. package/dist/helpers/vscode-custom-data.js +5 -2
  54. package/dist/helpers/vscode-custom-data.js.map +11 -0
  55. package/dist/index.js +21 -5
  56. package/dist/index.js.map +18 -0
  57. package/dist/key-generate.js +3 -0
  58. package/dist/key-generate.js.map +10 -0
  59. package/dist/lint/fix.js +3 -0
  60. package/dist/lint/fix.js.map +10 -0
  61. package/dist/lint/index.js +3 -0
  62. package/dist/lint/index.js.map +10 -0
  63. package/dist/make.js +3 -0
  64. package/dist/make.js.map +10 -0
  65. package/dist/prepublish.js +3 -0
  66. package/dist/prepublish.js.map +10 -0
  67. package/dist/release.js +21 -5
  68. package/dist/release.js.map +19 -0
  69. package/dist/test/coverage.js +3 -0
  70. package/dist/test/coverage.js.map +10 -0
  71. package/dist/test/feature.js +3 -0
  72. package/dist/test/feature.js.map +10 -0
  73. package/dist/test/ui.js +3 -0
  74. package/dist/test/ui.js.map +10 -0
  75. package/dist/test/unit.js +3 -0
  76. package/dist/test/unit.js.map +10 -0
  77. package/dist/test.js +3 -0
  78. package/dist/test.js.map +10 -0
  79. package/dist/typecheck.js +3 -0
  80. package/dist/typecheck.js.map +10 -0
  81. package/dist/types.js +3 -0
  82. package/dist/types.js.map +10 -0
  83. package/dist/upgrade/dependencies.js +3 -0
  84. package/dist/upgrade/dependencies.js.map +9 -0
  85. package/dist/upgrade/framework.js +3 -0
  86. package/dist/upgrade/framework.js.map +9 -0
  87. package/dist/upgrade/index.js +4 -2
  88. package/dist/upgrade/index.js.map +11 -0
  89. package/dist/upgrade.js +4 -1
  90. package/dist/upgrade.js.map +10 -0
  91. package/package.json +3 -3
  92. package/src/action.ts +15 -1
  93. package/src/build/server.ts +1 -1
  94. package/src/bump.ts +5 -5
  95. package/src/cdn/invalidate-cache.ts +1 -1
  96. package/src/deploy/index.ts +14 -6
  97. package/src/files/watch.ts +8 -0
  98. package/src/helpers/utils.ts +2 -3
  99. package/src/orm/generate-model.ts +384 -144
@@ -2,9 +2,10 @@ import { log } from '@stacksjs/logging'
2
2
  import { getModelName, getTableName } from '@stacksjs/orm'
3
3
  import { path } from '@stacksjs/path'
4
4
  import { fs, glob } from '@stacksjs/storage'
5
- import { camelCase, pascalCase, plural, singular } from '@stacksjs/strings'
5
+ import { camelCase, pascalCase, plural, singular, snakeCase } from '@stacksjs/strings'
6
6
  import type { Attributes, Model, RelationConfig } from '@stacksjs/types'
7
7
  import { isString } from '@stacksjs/validation'
8
+
8
9
  export interface FieldArrayElement {
9
10
  entity: string
10
11
  charValue?: string | null
@@ -22,7 +23,7 @@ await initiateModelGeneration()
22
23
  await setKyselyTypes()
23
24
 
24
25
  async function generateApiRoutes(modelFiles: string[]) {
25
- const file = Bun.file(path.projectStoragePath(`framework/orm/routes.ts`))
26
+ const file = Bun.file(path.frameworkPath(`orm/routes.ts`))
26
27
  const writer = file.writer()
27
28
  let routeString = `import { route } from '@stacksjs/router'\n\n\n`
28
29
 
@@ -36,6 +37,8 @@ async function generateApiRoutes(modelFiles: string[]) {
36
37
  if (model.traits?.useApi) {
37
38
  const apiRoutes = model.traits?.useApi?.routes
38
39
  const middlewares = model.traits.useApi?.middleware
40
+ const uri = model.traits.useApi?.uri || tableName
41
+
39
42
  if (middlewares) {
40
43
  middlewareString = `.middleware([`
41
44
  if (middlewares.length) {
@@ -51,24 +54,37 @@ async function generateApiRoutes(modelFiles: string[]) {
51
54
  middlewareString += `])`
52
55
  }
53
56
 
54
- if (apiRoutes?.length) {
55
- for (const apiRoute of apiRoutes) {
56
- await writeOrmActions(apiRoute as string, modelName)
57
+ if (Object.keys(model.traits.useApi.routes).length > 0) {
58
+ if (apiRoutes) {
59
+ for (const apiRoute in apiRoutes) {
60
+ // if (Object.prototype.hasOwnProperty.call(apiRoutes, route)) {
61
+ // console.log(`Route: ${route}, Path: ${apiRoutes[route]}`);
62
+ // }
63
+
64
+ let path: string | null = ''
65
+
66
+ await writeOrmActions(apiRoute as string, modelName)
57
67
 
58
- if (apiRoute === 'index')
59
- routeString += `await route.get('${tableName}', 'Actions/${modelName}IndexOrmAction')${middlewareString}\n\n`
68
+ path = `${apiRoutes[apiRoute]}.ts`
60
69
 
61
- if (apiRoute === 'store')
62
- routeString += `await route.post('${tableName}', 'Actions/${modelName}StoreOrmAction')${middlewareString}\n\n`
70
+ if (!path.includes('/')) {
71
+ path = await lookupFile(path)
72
+ }
73
+
74
+ if (!path) {
75
+ throw { message: 'Action Not Found!' }
76
+ }
63
77
 
64
- if (apiRoute === 'update')
65
- routeString += `await route.patch('${tableName}/{id}', 'Actions/${modelName}UpdateOrmAction')${middlewareString}\n\n`
78
+ if (apiRoute === 'index') routeString += `await route.get('${uri}', '${path}')\n\n`
66
79
 
67
- if (apiRoute === 'show')
68
- routeString += `await route.get('${tableName}/{id}', 'Actions/${modelName}ShowOrmAction')${middlewareString}\n\n`
80
+ if (apiRoute === 'show') routeString += `await route.get('${uri}/{id}', '${path}')\n\n`
69
81
 
70
- if (apiRoute === 'destroy')
71
- routeString += `await route.delete('${tableName}/{id}', 'Actions/${modelName}DestroyOrmAction')${middlewareString}\n\n`
82
+ if (apiRoute === 'store') routeString += `await route.post('${uri}', '${path}')\n\n`
83
+
84
+ if (apiRoute === 'update') routeString += `await route.patch('${uri}/{id}', '${path}')\n\n`
85
+
86
+ if (apiRoute === 'destroy') routeString += `await route.delete('${uri}/{id}', '${path}')\n\n`
87
+ }
72
88
  }
73
89
  }
74
90
  }
@@ -78,18 +94,36 @@ async function generateApiRoutes(modelFiles: string[]) {
78
94
  await writer.end()
79
95
  }
80
96
 
97
+ async function lookupFile(fileName: string): Promise<string | null> {
98
+ const ormDirectory = path.builtUserActionsPath('src')
99
+ const filePath = path.join(ormDirectory, fileName)
100
+ const pathExists = await fs.existsSync(filePath)
101
+
102
+ // Check if the directory exists
103
+ if (pathExists) {
104
+ return filePath
105
+ }
106
+
107
+ const actionDirectory = path.userActionsPath()
108
+ const actionFilePath = path.join(actionDirectory, fileName)
109
+ const fileExists = await fs.existsSync(actionFilePath)
110
+
111
+ if (fileExists) {
112
+ return actionFilePath
113
+ }
114
+
115
+ return null
116
+ }
117
+
81
118
  async function writeModelNames() {
82
119
  const modelFiles = glob.sync(path.userModelsPath('*.ts'))
83
-
84
120
  let fileString = `export type ModelNames = `
85
121
 
86
122
  for (let i = 0; i < modelFiles.length; i++) {
87
123
  const modeFileElement = modelFiles[i] as string
88
-
89
124
  const model = (await import(modeFileElement)).default as Model
90
125
  const modelName = getModelName(model, modeFileElement)
91
-
92
- const typeFile = Bun.file(path.projectStoragePath(`framework/core/types/src/model-names.ts`))
126
+ const typeFile = Bun.file(path.corePath(`types/src/model-names.ts`))
93
127
 
94
128
  fileString += `'${modelName}'`
95
129
 
@@ -105,8 +139,8 @@ async function writeModelNames() {
105
139
 
106
140
  async function writeModelRequest() {
107
141
  const modelFiles = glob.sync(path.userModelsPath('*.ts'))
108
-
109
142
  const requestD = Bun.file(path.frameworkPath('types/requests.d.ts'))
143
+
110
144
  let importTypes = ``
111
145
  let importTypesString = ``
112
146
  let typeString = `import { Request } from '../core/router/src/request'\n\n`
@@ -132,10 +166,10 @@ async function writeModelRequest() {
132
166
  let fileString = `import { Request } from '@stacksjs/router'\nimport type { VineType } from '@stacksjs/types'\nimport { validateField } from '@stacksjs/validation'\nimport { customValidate } from '@stacksjs/validation'\n\n`
133
167
 
134
168
  const modeFileElement = modelFiles[i] as string
135
-
136
169
  const model = (await import(modeFileElement)).default as Model
137
170
  const modelName = getModelName(model, modeFileElement)
138
-
171
+ const useTimestamps = model?.traits?.useTimestamps ?? model?.traits?.timestampable ?? true
172
+ const useSoftDeletes = model?.traits?.useSoftDeletes ?? model?.traits?.softDeletable ?? false
139
173
  const attributes = await extractFields(model, modeFileElement)
140
174
 
141
175
  fieldString += ` id?: number\n`
@@ -147,20 +181,18 @@ async function writeModelRequest() {
147
181
  const otherModelRelations = await fetchOtherModelRelations(model, modelName)
148
182
 
149
183
  for (const attribute of attributes) {
150
- let defaultValue: any = `''`
151
184
  const entity = attribute.fieldArray?.entity === 'enum' ? 'string[]' : attribute.fieldArray?.entity
185
+ let defaultValue: any = `''`
152
186
 
153
187
  if (attribute.fieldArray?.entity === 'boolean') defaultValue = false
154
-
155
188
  if (attribute.fieldArray?.entity === 'number') defaultValue = 0
156
189
 
157
190
  fieldString += ` ${attribute.field}: ${entity}\n `
158
-
159
191
  fieldStringType += `'${attribute.field}'`
192
+
160
193
  if (keyCounter < attributes.length - 1) fieldStringType += ' |'
161
194
 
162
195
  fieldStringInt += `public ${attribute.field} = ${defaultValue}\n`
163
-
164
196
  keyCounter++
165
197
  }
166
198
 
@@ -175,14 +207,20 @@ async function writeModelRequest() {
175
207
  // fieldStringType += ' |'
176
208
 
177
209
  fieldStringInt += `public ${otherModel.foreignKey} = 0\n`
178
-
179
210
  keyCounterForeign++
180
211
  }
181
212
 
182
- fieldStringInt += `public created_at = ''
183
- public updated_at = ''
184
- public deleted_at = ''
213
+ if (useTimestamps) {
214
+ fieldStringInt += `public created_at = ''
215
+ public updated_at = ''
185
216
  `
217
+ }
218
+
219
+ if (useSoftDeletes) {
220
+ fieldStringInt += `
221
+ public deleted_at = ''
222
+ `
223
+ }
186
224
 
187
225
  fieldString += `created_at?: string
188
226
  updated_at?: string
@@ -190,7 +228,7 @@ async function writeModelRequest() {
190
228
 
191
229
  const modelLowerCase = camelCase(modelName)
192
230
 
193
- const requestFile = Bun.file(path.projectStoragePath(`framework/requests/${modelName}Request.ts`))
231
+ const requestFile = Bun.file(path.frameworkPath(`requests/${modelName}Request.ts`))
194
232
 
195
233
  importTypes = `${modelName}RequestType`
196
234
  importTypesString += `${importTypes}`
@@ -233,11 +271,11 @@ async function writeModelRequest() {
233
271
  } else {
234
272
  await customValidate(attributes, this.all())
235
273
  }
236
-
274
+
237
275
  }
238
276
  }
239
-
240
- export const ${modelLowerCase}Request = new ${modelName}Request()
277
+
278
+ export const request = new ${modelName}Request()
241
279
  `
242
280
 
243
281
  const writer = requestFile.writer()
@@ -274,7 +312,7 @@ async function writeOrmActions(apiRoute: string, modelName: String): Promise<voi
274
312
  handleString += `async handle(request: ${modelName}RequestType) {
275
313
  const id = await request.getParam('id')
276
314
 
277
- return ${modelName}.findOrFail(Number(id))
315
+ return await ${modelName}.findOrFail(Number(id))
278
316
  },`
279
317
 
280
318
  method = 'GET'
@@ -308,7 +346,7 @@ async function writeOrmActions(apiRoute: string, modelName: String): Promise<voi
308
346
  if (apiRoute === 'update') {
309
347
  handleString += `async handle(request: ${modelName}RequestType) {
310
348
  await request.validate()
311
-
349
+
312
350
  const id = request.getParam('id')
313
351
 
314
352
  const model = await ${modelName}.findOrFail(Number(id))
@@ -327,18 +365,19 @@ async function writeOrmActions(apiRoute: string, modelName: String): Promise<voi
327
365
  })
328
366
  `
329
367
 
330
- const file = Bun.file(path.projectStoragePath(`framework/orm/Actions/${modelName}${formattedApiRoute}OrmAction.ts`))
368
+ const file = Bun.file(path.builtUserActionsPath(`src/${modelName}${formattedApiRoute}OrmAction.ts`))
331
369
 
332
370
  const writer = file.writer()
333
371
 
334
372
  writer.write(actionString)
335
373
  }
336
374
 
337
- async function initiateModelGeneration(): Promise<void> {
338
- await deleteExistingModels()
339
- await deleteExistingOrmActions()
375
+ async function initiateModelGeneration(modelStringFile?: string): Promise<void> {
376
+ await deleteExistingModels(modelStringFile)
377
+ await deleteExistingOrmActions(modelStringFile)
340
378
  await deleteExistingModelNameTypes()
341
- await deleteExistingModelRequest()
379
+ await deleteExistingModelRequest(modelStringFile)
380
+ await deleteExistingOrmRoute()
342
381
 
343
382
  await writeModelNames()
344
383
  await writeModelRequest()
@@ -348,13 +387,15 @@ async function initiateModelGeneration(): Promise<void> {
348
387
  await generateApiRoutes(modelFiles)
349
388
 
350
389
  for (const modelFile of modelFiles) {
390
+ if (modelStringFile && modelStringFile !== modelFile) continue
391
+
351
392
  log.debug(`Processing model file: ${modelFile}`)
352
393
 
353
394
  const model = (await import(modelFile)).default as Model
354
395
  const tableName = await getTableName(model, modelFile)
355
396
  const modelName = getModelName(model, modelFile)
356
397
 
357
- const file = Bun.file(path.projectStoragePath(`framework/orm/src/models/${modelName}.ts`))
398
+ const file = Bun.file(path.frameworkPath(`orm/src/models/${modelName}.ts`))
358
399
  const fields = await extractFields(model, modelFile)
359
400
  const classString = await generateModelString(tableName, modelName, model, fields)
360
401
 
@@ -422,44 +463,74 @@ function hasRelations(obj: any, key: string): boolean {
422
463
  return key in obj
423
464
  }
424
465
 
425
- async function deleteExistingModels() {
426
- const modelPaths = glob.sync(path.projectStoragePath(`framework/orm/src/models/*.ts`))
466
+ async function deleteExistingModels(modelStringFile?: string) {
467
+ const typePath = path.frameworkPath(`orm/src/types.ts`)
468
+
469
+ if (fs.existsSync(typePath)) await Bun.$`rm ${typePath}`
470
+
471
+ if (modelStringFile) {
472
+ const modelPath = path.frameworkPath(`orm/src/models/${modelStringFile}.ts`)
473
+
474
+ await Bun.$`rm ${modelPath}`
475
+
476
+ return
477
+ }
478
+
479
+ const modelPaths = glob.sync(path.frameworkPath(`orm/src/models/*.ts`))
427
480
 
428
481
  for (const modelPath of modelPaths) {
429
482
  if (fs.existsSync(modelPath)) await Bun.$`rm ${modelPath}`
430
483
  }
484
+ }
485
+
486
+ async function deleteExistingOrmActions(modelStringFile?: string) {
487
+ const routes = path.frameworkPath(`orm/routes`)
488
+ if (fs.existsSync(routes)) await Bun.$`rm ${routes}`
431
489
 
432
- const typePath = path.projectStoragePath(`framework/core/orm/src/generated/types.ts`)
490
+ if (modelStringFile) {
491
+ const ormPath = path.builtUserActionsPath(`src/${modelStringFile}.ts`)
433
492
 
434
- if (fs.existsSync(typePath)) await Bun.$`rm ${typePath}`
435
- }
493
+ if (fs.existsSync(ormPath)) await Bun.$`rm ${ormPath}`
494
+
495
+ return
496
+ }
436
497
 
437
- async function deleteExistingOrmActions() {
438
- const ormPaths = glob.sync(path.projectStoragePath(`framework/orm/Actions/*.ts`))
439
- const routes = path.projectStoragePath(`framework/orm/routes`)
498
+ const ormPaths = glob.sync(path.builtUserActionsPath(`**/*.ts`))
440
499
 
441
500
  for (const ormPath of ormPaths) {
442
501
  if (fs.existsSync(ormPath)) await Bun.$`rm ${ormPath}`
443
502
  }
444
-
445
- if (fs.existsSync(routes)) await Bun.$`rm ${routes}`
446
503
  }
447
504
 
448
505
  async function deleteExistingModelNameTypes() {
449
- const typeFile = path.projectStoragePath(`framework/core/types/src/model-names.ts`)
506
+ const typeFile = path.corePath(`types/src/model-names.ts`)
450
507
 
451
508
  if (fs.existsSync(typeFile)) await Bun.$`rm ${typeFile}`
452
509
  }
453
510
 
454
- async function deleteExistingModelRequest() {
455
- const requestFiles = glob.sync(path.projectStoragePath(`framework/requests/*.ts`))
511
+ async function deleteExistingModelRequest(modelStringFile?: string) {
456
512
  const requestD = path.frameworkPath('types/requests.d.ts')
513
+ if (fs.existsSync(requestD)) await Bun.$`rm ${requestD}`
514
+
515
+ if (modelStringFile) {
516
+ const requestFile = path.frameworkPath(`requests/${modelStringFile}.ts`)
517
+
518
+ if (fs.existsSync(requestFile)) await Bun.$`rm ${requestFile}`
519
+
520
+ return
521
+ }
522
+
523
+ const requestFiles = glob.sync(path.frameworkPath(`requests/*.ts`))
457
524
 
458
525
  for (const requestFile of requestFiles) {
459
526
  if (fs.existsSync(requestFile)) await Bun.$`rm ${requestFile}`
460
527
  }
528
+ }
461
529
 
462
- if (fs.existsSync(requestD)) await Bun.$`rm ${requestD}`
530
+ async function deleteExistingOrmRoute() {
531
+ const ormRoute = path.projectStoragePath('framework/orm/routes.ts')
532
+
533
+ if (fs.existsSync(ormRoute)) await Bun.$`rm ${ormRoute}`
463
534
  }
464
535
 
465
536
  async function setKyselyTypes() {
@@ -518,7 +589,7 @@ async function setKyselyTypes() {
518
589
 
519
590
  text += `}`
520
591
 
521
- const file = Bun.file(path.projectStoragePath(`framework/core/orm/src/generated/types.ts`))
592
+ const file = Bun.file(path.frameworkPath(`orm/src/types.ts`))
522
593
 
523
594
  const writer = file.writer()
524
595
 
@@ -688,11 +759,13 @@ function getHiddenAttributes(attributes: Attributes | undefined): string[] {
688
759
  function getFillableAttributes(attributes: Attributes | undefined): string[] {
689
760
  if (attributes === undefined) return []
690
761
 
691
- return Object.keys(attributes).filter((key) => {
692
- if (attributes === undefined) return false
762
+ return Object.keys(attributes)
763
+ .filter((key) => {
764
+ if (attributes === undefined) return false
693
765
 
694
- return attributes[key]?.fillable === true
695
- })
766
+ return attributes[key]?.fillable === true
767
+ })
768
+ .map((attribute) => snakeCase(attribute))
696
769
  }
697
770
 
698
771
  async function generateModelString(
@@ -706,12 +779,16 @@ async function generateModelString(
706
779
 
707
780
  let fieldString = ''
708
781
  let constructorFields = ''
782
+ let jsonFields = '{\n'
709
783
  let declareFields = ''
710
784
  let whereStatements = ''
711
785
  let whereFunctionStatements = ''
712
786
  let relationMethods = ``
713
787
  let relationImports = ``
714
788
  let twoFactorStatements = ''
789
+ let mittCreateStatement = ``
790
+ let mittUpdateStatement = ``
791
+ let mittDeleteStatement = ``
715
792
 
716
793
  const relations = await getRelations(model, modelName)
717
794
 
@@ -719,6 +796,31 @@ async function generateModelString(
719
796
  relationImports += `import ${relationInstance.model} from './${relationInstance.model}'\n\n`
720
797
  }
721
798
 
799
+ const useTimestamps = model?.traits?.useTimestamps ?? model?.traits?.timestampable ?? true
800
+ const useSoftDeletes = model?.traits?.useSoftDeletes ?? model?.traits?.softDeletable ?? false
801
+ const observer = model?.traits?.observe
802
+
803
+ if (typeof observer === 'boolean') {
804
+ if (observer) {
805
+ mittCreateStatement += `dispatch('${formattedModelName}.created', model)`
806
+ mittUpdateStatement += `dispatch('${formattedModelName}.updated', model)`
807
+ mittDeleteStatement += `dispatch('${formattedModelName}.deleted', this)`
808
+ }
809
+ }
810
+
811
+ if (Array.isArray(observer)) {
812
+ // Iterate through the array and append statements based on its contents
813
+ if (observer.includes('create')) {
814
+ mittCreateStatement += `dispatch('${formattedModelName}.created', model);`
815
+ }
816
+ if (observer.includes('update')) {
817
+ mittUpdateStatement += `dispatch('${formattedModelName}.updated', model);`
818
+ }
819
+ if (observer.includes('delete')) {
820
+ mittDeleteStatement += `dispatch('${formattedModelName}.deleted', model);`
821
+ }
822
+ }
823
+
722
824
  for (const relation of relations) {
723
825
  const modelRelation = relation.model
724
826
  const foreignKeyRelation = relation.foreignKey
@@ -733,7 +835,9 @@ async function generateModelString(
733
835
  const relationName = relation.relationName || formattedModelName + modelRelation
734
836
  const throughRelation = relation.throughModel
735
837
 
736
- const formattedThroughRelation = relation.throughModel.toLowerCase()
838
+ if (relation.throughModel === undefined) continue
839
+
840
+ const formattedThroughRelation = relation?.throughModel?.toLowerCase()
737
841
  const throughTableRelation = throughRelation
738
842
  const foreignKeyThroughRelation = relation.throughForeignKey || `${formattedThroughRelation}_id`
739
843
 
@@ -824,9 +928,12 @@ async function generateModelString(
824
928
  .where('${foreignKeyRelation}', '=', this.id)
825
929
  .selectAll()
826
930
  .execute()
827
-
931
+
828
932
  const tableRelationIds = results.map(result => result.${singular(tableRelation)}_id)
829
933
 
934
+ if (! tableRelationIds.length)
935
+ throw new Error('Relation Error!')
936
+
830
937
  const relationResults = await ${modelRelation}.whereIn('id', tableRelationIds).get()
831
938
 
832
939
  return relationResults
@@ -862,20 +969,22 @@ async function generateModelString(
862
969
  `
863
970
  }
864
971
 
972
+ jsonFields += `\nid: this.id,\n`
865
973
  for (const attribute of attributes) {
866
974
  const entity = attribute.fieldArray?.entity === 'enum' ? 'string[]' : attribute.fieldArray?.entity
867
975
 
868
976
  fieldString += ` ${attribute.field}: ${entity}\n `
869
977
 
870
- declareFields += `public ${attribute.field}: ${entity} | undefined \n `
978
+ declareFields += `public ${snakeCase(attribute.field)}: ${entity} | undefined \n `
871
979
 
872
- constructorFields += `this.${attribute.field} = ${formattedModelName}?.${attribute.field}\n `
980
+ constructorFields += `this.${snakeCase(attribute.field)} = ${formattedModelName}?.${snakeCase(attribute.field)}\n `
981
+ jsonFields += `${snakeCase(attribute.field)}: this.${snakeCase(attribute.field)},\n `
873
982
 
874
983
  whereStatements += `static where${pascalCase(attribute.field)}(value: string | number | boolean | undefined | null): ${modelName}Model {
875
984
  const instance = new this(null)
876
985
 
877
986
  instance.query = instance.query.where('${attribute.field}', '=', value)
878
-
987
+
879
988
  return instance
880
989
  } \n\n`
881
990
 
@@ -888,6 +997,39 @@ async function generateModelString(
888
997
  } \n\n`
889
998
  }
890
999
 
1000
+ if (useTimestamps) {
1001
+ declareFields += `
1002
+ public created_at: Date | undefined
1003
+ public updated_at: Date | undefined
1004
+ `
1005
+
1006
+ constructorFields += `
1007
+ this.created_at = user?.created_at\n
1008
+ this.updated_at = user?.updated_at\n
1009
+ `
1010
+
1011
+ jsonFields += `
1012
+ created_at: this.created_at,\n
1013
+ updated_at: this.updated_at,\n
1014
+ `
1015
+ }
1016
+
1017
+ if (useSoftDeletes) {
1018
+ declareFields += `
1019
+ public deleted_at: Date | undefined
1020
+ `
1021
+
1022
+ constructorFields += `
1023
+ this.deleted_at = user?.deleted_at\n
1024
+ `
1025
+
1026
+ jsonFields += `
1027
+ deleted_at: this.deleted_at,\n
1028
+ `
1029
+ }
1030
+
1031
+ jsonFields += '}'
1032
+
891
1033
  const otherModelRelations = await fetchOtherModelRelations(model, modelName)
892
1034
 
893
1035
  for (const otherModelRelation of otherModelRelations) {
@@ -902,12 +1044,26 @@ async function generateModelString(
902
1044
  fieldString += `two_factor_secret: string \n`
903
1045
  }
904
1046
 
1047
+ if (useTimestamps) {
1048
+ fieldString += `
1049
+ created_at: ColumnType<Date, string | undefined, never>\n
1050
+ updated_at: ColumnType<Date, string | undefined, never>
1051
+ `
1052
+ }
1053
+
1054
+ if (useSoftDeletes) {
1055
+ fieldString += `
1056
+ deleted_at: ColumnType<Date, string | undefined, never>\n
1057
+ `
1058
+ }
1059
+
905
1060
  const hidden = JSON.stringify(getHiddenAttributes(model.attributes))
906
1061
  const fillable = JSON.stringify(getFillableAttributes(model.attributes))
907
1062
 
908
1063
  return `import type { ColumnType, Generated, Insertable, Selectable, Updateable } from 'kysely'
909
1064
  import { db } from '@stacksjs/database'
910
1065
  import { sql } from '@stacksjs/database'
1066
+ import { dispatch } from '@stacksjs/events'
911
1067
  import { generateTwoFactorSecret } from '@stacksjs/auth'
912
1068
  import { verifyTwoFactorCode } from '@stacksjs/auth'
913
1069
  ${relationImports}
@@ -918,9 +1074,6 @@ async function generateModelString(
918
1074
  export interface ${formattedTableName}Table {
919
1075
  id: Generated<number>
920
1076
  ${fieldString}
921
- created_at: ColumnType<Date, string | undefined, never>
922
- updated_at: ColumnType<Date, string | undefined, never>
923
- deleted_at: ColumnType<Date, string | undefined, never>
924
1077
  }
925
1078
 
926
1079
  interface ${modelName}Response {
@@ -950,16 +1103,15 @@ async function generateModelString(
950
1103
  offset?: number
951
1104
  page?: number
952
1105
  }
953
-
1106
+
954
1107
  export class ${modelName}Model {
955
- private ${formattedModelName}: Partial<${modelName}Type> | null
956
1108
  private hidden = ${hidden}
957
1109
  private fillable = ${fillable}
1110
+ private softDeletes = ${useSoftDeletes}
958
1111
  protected query: any
959
1112
  protected hasSelect: boolean
960
1113
  ${declareFields}
961
1114
  constructor(${formattedModelName}: Partial<${modelName}Type> | null) {
962
- this.${formattedModelName} = ${formattedModelName}
963
1115
  ${constructorFields}
964
1116
 
965
1117
  this.query = db.selectFrom('${tableName}')
@@ -979,8 +1131,8 @@ async function generateModelString(
979
1131
 
980
1132
  if (!model)
981
1133
  return undefined
982
-
983
- return this.parseResult(this)
1134
+
1135
+ return this.parseResult(new ${modelName}Model(model))
984
1136
  }
985
1137
 
986
1138
  // Method to find a ${modelName} by ID
@@ -988,7 +1140,7 @@ async function generateModelString(
988
1140
  let query = db.selectFrom('${tableName}').where('id', '=', id)
989
1141
 
990
1142
  const instance = new this(null)
991
-
1143
+
992
1144
  if (fields)
993
1145
  query = query.select(fields)
994
1146
  else
@@ -1002,18 +1154,34 @@ async function generateModelString(
1002
1154
  return instance.parseResult(new this(model))
1003
1155
  }
1004
1156
 
1005
- static async findOrFail(id: number, fields?: (keyof ${modelName}Type)[]): Promise<${modelName}Model> {
1157
+ static async all(): Promise<${modelName}Model[]> {
1158
+ let query = db.selectFrom('${tableName}').selectAll()
1159
+
1160
+ const instance = new this(null)
1161
+
1162
+ if (instance.softDeletes) {
1163
+ query = query.where('deleted_at', 'is', null)
1164
+ }
1165
+
1166
+ const results = await query.execute();
1167
+
1168
+ return results.map(modelItem => instance.parseResult(new ${modelName}Model(modelItem)));
1169
+ }
1170
+
1171
+
1172
+ static async findOrFail(id: number): Promise<${modelName}Model> {
1006
1173
  let query = db.selectFrom('${tableName}').where('id', '=', id)
1007
1174
 
1008
1175
  const instance = new this(null)
1009
1176
 
1010
- if (fields)
1011
- query = query.select(fields)
1012
- else
1013
- query = query.selectAll()
1177
+ if (instance.softDeletes) {
1178
+ query = query.where('deleted_at', 'is', null);
1179
+ }
1180
+
1181
+ query = query.selectAll()
1014
1182
 
1015
1183
  const model = await query.executeTakeFirst()
1016
-
1184
+
1017
1185
  if (!model)
1018
1186
  throw(\`No model results found for \${id}\ \`)
1019
1187
 
@@ -1021,19 +1189,18 @@ async function generateModelString(
1021
1189
  return instance.parseResult(new this(model))
1022
1190
  }
1023
1191
 
1024
- static async findMany(ids: number[], fields?: (keyof ${modelName}Type)[]): Promise<${modelName}Model[]> {
1192
+ static async findMany(ids: number[]): Promise<${modelName}Model[]> {
1025
1193
  let query = db.selectFrom('${tableName}').where('id', 'in', ids)
1026
1194
 
1027
1195
  const instance = new this(null)
1028
-
1029
- if (fields)
1030
- query = query.select(fields)
1031
- else
1032
- query = query.selectAll()
1033
1196
 
1034
- const model = await query.execute()
1197
+ if (instance.softDeletes) {
1198
+ query = query.where('deleted_at', 'is', null);
1199
+ }
1035
1200
 
1036
- instance.parseResult(new ${modelName}Model(modelItem))
1201
+ query = query.selectAll()
1202
+
1203
+ const model = await query.execute()
1037
1204
 
1038
1205
  return model.map(modelItem => instance.parseResult(new ${modelName}Model(modelItem)))
1039
1206
  }
@@ -1042,6 +1209,8 @@ async function generateModelString(
1042
1209
  static async fetch(criteria: Partial<${modelName}Type>, options: QueryOptions = {}): Promise<${modelName}Model[]> {
1043
1210
  let query = db.selectFrom('${tableName}')
1044
1211
 
1212
+ const instance = new this(null)
1213
+
1045
1214
  // Apply sorting from options
1046
1215
  if (options.sort)
1047
1216
  query = query.orderBy(options.sort.column, options.sort.order)
@@ -1053,35 +1222,59 @@ async function generateModelString(
1053
1222
  if (options.offset !== undefined)
1054
1223
  query = query.offset(options.offset)
1055
1224
 
1225
+ if (instance.softDeletes) {
1226
+ query = query.where('deleted_at', 'is', null);
1227
+ }
1228
+
1056
1229
  const model = await query.selectAll().execute()
1057
1230
  return model.map(modelItem => new ${modelName}Model(modelItem))
1058
1231
  }
1059
1232
 
1060
1233
  // Method to get a ${modelName} by criteria
1061
1234
  static async get(): Promise<${modelName}Model[]> {
1062
- const query = db.selectFrom('${tableName}')
1235
+ let query = db.selectFrom('${tableName}');
1063
1236
 
1064
- const model = await query.selectAll().execute()
1237
+ const instance = new this(null)
1065
1238
 
1066
- return model.map(modelItem => new ${modelName}Model(modelItem))
1239
+ // Check if soft deletes are enabled
1240
+ if (instance.softDeletes) {
1241
+ query = query.where('deleted_at', 'is', null);
1242
+ }
1243
+
1244
+ const model = await query.selectAll().execute();
1245
+
1246
+ return model.map(modelItem => new ${modelName}Model(modelItem));
1067
1247
  }
1068
1248
 
1069
1249
  // Method to get a ${modelName} by criteria
1070
1250
  async get(): Promise<${modelName}Model[]> {
1071
1251
  if (this.hasSelect) {
1252
+
1253
+ if (this.softDeletes) {
1254
+ this.query = this.query.where('deleted_at', 'is', null);
1255
+ }
1256
+
1072
1257
  const model = await this.query.execute()
1073
-
1258
+
1074
1259
  return model.map((modelItem: ${modelName}Model) => new ${modelName}Model(modelItem))
1075
1260
  }
1076
-
1261
+
1262
+ if (this.softDeletes) {
1263
+ this.query = this.query.where('deleted_at', 'is', null);
1264
+ }
1265
+
1077
1266
  const model = await this.query.selectAll().execute()
1078
-
1267
+
1079
1268
  return model.map((modelItem: ${modelName}Model) => new ${modelName}Model(modelItem))
1080
1269
  }
1081
1270
 
1082
1271
  static async count(): Promise<number> {
1083
1272
  const instance = new this(null)
1084
1273
 
1274
+ if (instance.softDeletes) {
1275
+ instance.query = instance.query.where('deleted_at', 'is', null);
1276
+ }
1277
+
1085
1278
  const results = await instance.query.selectAll().execute()
1086
1279
 
1087
1280
  return results.length
@@ -1089,6 +1282,11 @@ async function generateModelString(
1089
1282
 
1090
1283
  async count(): Promise<number> {
1091
1284
  if (this.hasSelect) {
1285
+
1286
+ if (this.softDeletes) {
1287
+ this.query = this.query.where('deleted_at', 'is', null);
1288
+ }
1289
+
1092
1290
  const results = await this.query.execute()
1093
1291
 
1094
1292
  return results.length
@@ -1112,7 +1310,7 @@ async function generateModelString(
1112
1310
  .selectAll()
1113
1311
  .orderBy('id', 'asc') // Assuming 'id' is used for cursor-based pagination
1114
1312
  .limit((options.limit ?? 10) + 1) // Fetch one extra record
1115
- .offset((options.page - 1) * (options.limit ?? 10))
1313
+ .offset(((options.page ?? 1) - 1) * (options.limit ?? 10)) // Ensure options.page is not undefined
1116
1314
  .execute()
1117
1315
 
1118
1316
  let nextCursor = null
@@ -1148,14 +1346,33 @@ async function generateModelString(
1148
1346
  .values(filteredValues)
1149
1347
  .executeTakeFirstOrThrow()
1150
1348
 
1151
- return await find(Number(result.insertId)) as ${modelName}Model
1349
+ const model = await find(Number(result.insertId)) as ${modelName}Model
1350
+
1351
+ ${mittCreateStatement}
1352
+
1353
+ return model
1152
1354
  }
1153
1355
 
1154
1356
  // Method to remove a ${modelName}
1155
1357
  static async remove(id: number): Promise<void> {
1156
- await db.deleteFrom('${tableName}')
1358
+ const instance = new this(null)
1359
+
1360
+ const model = await instance.find(id)
1361
+
1362
+ if (instance.softDeletes) {
1363
+ await db.updateTable('${tableName}')
1364
+ .set({
1365
+ deleted_at: sql.raw('CURRENT_TIMESTAMP')
1366
+ })
1157
1367
  .where('id', '=', id)
1158
- .execute()
1368
+ .execute();
1369
+ } else {
1370
+ await db.deleteFrom('${tableName}')
1371
+ .where('id', '=', id)
1372
+ .execute();
1373
+ }
1374
+
1375
+ ${mittDeleteStatement}
1159
1376
  }
1160
1377
 
1161
1378
  where(...args: (string | number | boolean | undefined | null)[]): ${modelName}Model {
@@ -1181,9 +1398,9 @@ async function generateModelString(
1181
1398
  let column: any
1182
1399
  let operator: any
1183
1400
  let value: any
1184
-
1401
+
1185
1402
  const instance = new this(null)
1186
-
1403
+
1187
1404
  if (args.length === 2) {
1188
1405
  [column, value] = args
1189
1406
  operator = '='
@@ -1192,9 +1409,9 @@ async function generateModelString(
1192
1409
  } else {
1193
1410
  throw new Error("Invalid number of arguments")
1194
1411
  }
1195
-
1412
+
1196
1413
  instance.query = instance.query.where(column, operator, value)
1197
-
1414
+
1198
1415
  return instance
1199
1416
  }
1200
1417
 
@@ -1202,9 +1419,9 @@ async function generateModelString(
1202
1419
 
1203
1420
  static whereIn(column: keyof ${modelName}Type, values: any[]): ${modelName}Model {
1204
1421
  const instance = new this(null)
1205
-
1422
+
1206
1423
  instance.query = instance.query.where(column, 'in', values)
1207
-
1424
+
1208
1425
  return instance
1209
1426
  }
1210
1427
 
@@ -1214,16 +1431,16 @@ async function generateModelString(
1214
1431
  if (! model) {
1215
1432
  return undefined
1216
1433
  }
1217
-
1218
- return new ${modelName}Model(model)
1434
+
1435
+ return this.parseResult(new ${modelName}Model(model))
1219
1436
  }
1220
1437
 
1221
1438
  async exists(): Promise<boolean> {
1222
1439
  const model = await this.query.selectAll().executeTakeFirst()
1223
-
1440
+
1224
1441
  return model !== null || model !== undefined
1225
1442
  }
1226
-
1443
+
1227
1444
  static async first(): Promise<${modelName}Type | undefined> {
1228
1445
  return await db.selectFrom('${tableName}')
1229
1446
  .selectAll()
@@ -1239,29 +1456,29 @@ async function generateModelString(
1239
1456
 
1240
1457
  static orderBy(column: keyof ${modelName}Type, order: 'asc' | 'desc'): ${modelName}Model {
1241
1458
  const instance = new this(null)
1242
-
1459
+
1243
1460
  instance.query = instance.orderBy(column, order)
1244
-
1461
+
1245
1462
  return instance
1246
1463
  }
1247
-
1464
+
1248
1465
  orderBy(column: keyof ${modelName}Type, order: 'asc' | 'desc'): ${modelName}Model {
1249
1466
  this.query = this.query.orderBy(column, order)
1250
-
1467
+
1251
1468
  return this
1252
1469
  }
1253
1470
 
1254
1471
  static orderByDesc(column: keyof ${modelName}Type): ${modelName}Model {
1255
1472
  const instance = new this(null)
1256
-
1473
+
1257
1474
  instance.query = instance.query.orderBy(column, 'desc')
1258
-
1475
+
1259
1476
  return instance
1260
1477
  }
1261
-
1478
+
1262
1479
  orderByDesc(column: keyof ${modelName}Type): ${modelName}Model {
1263
1480
  this.query = this.orderBy(column, 'desc')
1264
-
1481
+
1265
1482
  return this
1266
1483
  }
1267
1484
 
@@ -1280,23 +1497,27 @@ async function generateModelString(
1280
1497
  }
1281
1498
 
1282
1499
  // Method to update the ${tableName} instance
1283
- async update(${formattedModelName}: ${modelName}Update): Promise<${modelName}Model | null> {
1500
+ async update(${formattedModelName}: ${modelName}Update): Promise<${modelName}Model | undefined> {
1284
1501
  if (this.id === undefined)
1285
1502
  throw new Error('${modelName} ID is undefined')
1286
1503
 
1287
- const filteredValues = Object.keys(new${modelName})
1504
+ const filteredValues = Object.keys(${formattedModelName})
1288
1505
  .filter(key => this.fillable.includes(key))
1289
1506
  .reduce((obj, key) => {
1290
- obj[key] = new${modelName}[key];
1507
+ obj[key] = ${formattedModelName}[key]
1291
1508
  return obj;
1292
1509
  }, {});
1293
-
1510
+
1294
1511
  await db.updateTable('${tableName}')
1295
1512
  .set(filteredValues)
1296
1513
  .where('id', '=', this.id)
1297
1514
  .executeTakeFirst()
1298
-
1299
- return await this.find(Number(this.id))
1515
+
1516
+ const model = this.find(Number(this.id))
1517
+
1518
+ ${mittUpdateStatement}
1519
+
1520
+ return model
1300
1521
  }
1301
1522
 
1302
1523
  // Method to save (insert or update) the ${formattedModelName} instance
@@ -1316,43 +1537,57 @@ async function generateModelString(
1316
1537
  }
1317
1538
  }
1318
1539
 
1319
- // Method to delete the ${formattedModelName} instance
1540
+ // Method to delete (soft delete) the ${formattedModelName} instance
1320
1541
  async delete(): Promise<void> {
1321
- if (this.id === undefined)
1322
- throw new Error('${modelName} ID is undefined')
1323
-
1324
- await db.deleteFrom('${tableName}')
1325
- .where('id', '=', this.id)
1326
- .execute()
1542
+ if (this.id === undefined)
1543
+ throw new Error('${modelName} ID is undefined');
1544
+
1545
+ // Check if soft deletes are enabled
1546
+ if (this.softDeletes) {
1547
+ // Update the deleted_at column with the current timestamp
1548
+ await db.updateTable('${tableName}')
1549
+ .set({
1550
+ deleted_at: sql.raw('CURRENT_TIMESTAMP')
1551
+ })
1552
+ .where('id', '=', this.id)
1553
+ .execute();
1554
+ } else {
1555
+ // Perform a hard delete
1556
+ await db.deleteFrom('${tableName}')
1557
+ .where('id', '=', this.id)
1558
+ .execute();
1559
+ }
1560
+
1561
+ ${mittDeleteStatement}
1327
1562
  }
1328
1563
 
1329
1564
  ${relationMethods}
1330
1565
 
1331
1566
  distinct(column: keyof ${modelName}Type): ${modelName}Model {
1332
1567
  this.query = this.query.distinctOn(column)
1333
-
1568
+
1334
1569
  return this
1335
1570
  }
1336
-
1571
+
1337
1572
  static distinct(column: keyof ${modelName}Type): ${modelName}Model {
1338
1573
  const instance = new this(null)
1339
-
1574
+
1340
1575
  instance.query = instance.query.distinctOn(column)
1341
-
1576
+
1342
1577
  return instance
1343
1578
  }
1344
-
1579
+
1345
1580
  join(table: string, firstCol: string, secondCol: string): ${modelName}Model {
1346
1581
  this.query = this.query.innerJoin(table, firstCol, secondCol)
1347
-
1582
+
1348
1583
  return this
1349
1584
  }
1350
-
1585
+
1351
1586
  static join(table: string, firstCol: string, secondCol: string): ${modelName}Model {
1352
1587
  const instance = new this(null)
1353
-
1588
+
1354
1589
  instance.query = instance.query.innerJoin(table, firstCol, secondCol)
1355
-
1590
+
1356
1591
  return instance
1357
1592
  }
1358
1593
 
@@ -1361,7 +1596,7 @@ async function generateModelString(
1361
1596
  }
1362
1597
 
1363
1598
  toJSON() {
1364
- const output: Partial<${modelName}Type> = { ...this.${formattedModelName} }
1599
+ const output: Partial<${modelName}Type> = ${jsonFields}
1365
1600
 
1366
1601
  this.hidden.forEach((attr) => {
1367
1602
  if (attr in output)
@@ -1373,10 +1608,15 @@ async function generateModelString(
1373
1608
  return output as ${modelName}
1374
1609
  }
1375
1610
 
1376
- parseResult(model: any): ${modelName}Model {
1611
+ parseResult(model: ${modelName}Model): ${modelName}Model {
1612
+ delete model['query']
1613
+ delete model['fillable']
1614
+ delete model['two_factor_secret']
1615
+ delete model['hasSelect']
1616
+ delete model['softDeletes']
1617
+
1377
1618
  for (const hiddenAttribute of this.hidden) {
1378
1619
  delete model[hiddenAttribute]
1379
- delete model.${formattedModelName}[hiddenAttribute]
1380
1620
  }
1381
1621
 
1382
1622
  return model
@@ -1385,7 +1625,7 @@ async function generateModelString(
1385
1625
  ${twoFactorStatements}
1386
1626
  }
1387
1627
 
1388
- async function find(id: number, fields?: (keyof ${modelName}Type)[]): Promise<${modelName}Model | null> {
1628
+ async function find(id: number): Promise<${modelName}Model | null> {
1389
1629
  let query = db.selectFrom('${tableName}').where('id', '=', id)
1390
1630
 
1391
1631
  if (fields) query = query.select(fields)
@@ -1421,7 +1661,7 @@ async function generateModelString(
1421
1661
  .where('id', '=', id)
1422
1662
  .execute()
1423
1663
  }
1424
-
1664
+
1425
1665
  ${whereFunctionStatements}
1426
1666
 
1427
1667
  const ${modelName} = ${modelName}Model