@stacksjs/actions 0.61.24 → 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.
- package/dist/add.js +3 -0
- package/dist/add.js.map +10 -0
- package/dist/build/component-libs.js +3 -0
- package/dist/build/component-libs.js.map +10 -0
- package/dist/build/core.js +3 -0
- package/dist/build/core.js.map +10 -0
- package/dist/build/stacks.js +3 -0
- package/dist/build/stacks.js.map +10 -0
- package/dist/build.js +6 -4
- package/dist/build.js.map +14 -0
- package/dist/bump.js +7 -4
- package/dist/bump.js.map +10 -0
- package/dist/changelog.js +3 -0
- package/dist/changelog.js.map +10 -0
- package/dist/clean.js +3 -0
- package/dist/clean.js.map +10 -0
- package/dist/commit.js +3 -0
- package/dist/commit.js.map +10 -0
- package/dist/copy-types.js +3 -0
- package/dist/copy-types.js.map +10 -0
- package/dist/database/seed.js +3 -0
- package/dist/database/seed.js.map +10 -0
- package/dist/deploy/index.js +14 -5
- package/dist/deploy/index.js.map +10 -0
- package/dist/dev/components.js +3 -0
- package/dist/dev/components.js.map +10 -0
- package/dist/dev/docs.js +3 -0
- package/dist/dev/docs.js.map +10 -0
- package/dist/dev/index.js +4 -2
- package/dist/dev/index.js.map +11 -0
- package/dist/examples.js +3 -0
- package/dist/examples.js.map +10 -0
- package/dist/fresh.js +3 -0
- package/dist/fresh.js.map +10 -0
- package/dist/generate/component-meta.js +3 -0
- package/dist/generate/component-meta.js.map +11 -0
- package/dist/generate/ide-helpers.js +5 -2
- package/dist/generate/ide-helpers.js.map +12 -0
- package/dist/generate/index.js +6 -4
- package/dist/generate/index.js.map +13 -0
- package/dist/generate/lib-entries.js +3 -0
- package/dist/generate/lib-entries.js.map +12 -0
- package/dist/generate/vscode-custom-data.js +5 -2
- package/dist/generate/vscode-custom-data.js.map +12 -0
- package/dist/helpers/component-meta.js +3 -0
- package/dist/helpers/component-meta.js.map +10 -0
- package/dist/helpers/lib-entries.js +3 -0
- package/dist/helpers/lib-entries.js.map +11 -0
- package/dist/helpers/package-json.js +3 -0
- package/dist/helpers/package-json.js.map +11 -0
- package/dist/helpers/utils.js +4 -2
- package/dist/helpers/utils.js.map +10 -0
- package/dist/helpers/vscode-custom-data.js +5 -2
- package/dist/helpers/vscode-custom-data.js.map +11 -0
- package/dist/index.js +21 -5
- package/dist/index.js.map +18 -0
- package/dist/key-generate.js +3 -0
- package/dist/key-generate.js.map +10 -0
- package/dist/lint/fix.js +3 -0
- package/dist/lint/fix.js.map +10 -0
- package/dist/lint/index.js +3 -0
- package/dist/lint/index.js.map +10 -0
- package/dist/make.js +3 -0
- package/dist/make.js.map +10 -0
- package/dist/prepublish.js +3 -0
- package/dist/prepublish.js.map +10 -0
- package/dist/release.js +21 -5
- package/dist/release.js.map +19 -0
- package/dist/test/coverage.js +3 -0
- package/dist/test/coverage.js.map +10 -0
- package/dist/test/feature.js +3 -0
- package/dist/test/feature.js.map +10 -0
- package/dist/test/ui.js +3 -0
- package/dist/test/ui.js.map +10 -0
- package/dist/test/unit.js +3 -0
- package/dist/test/unit.js.map +10 -0
- package/dist/test.js +3 -0
- package/dist/test.js.map +10 -0
- package/dist/typecheck.js +3 -0
- package/dist/typecheck.js.map +10 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +10 -0
- package/dist/upgrade/dependencies.js +3 -0
- package/dist/upgrade/dependencies.js.map +9 -0
- package/dist/upgrade/framework.js +3 -0
- package/dist/upgrade/framework.js.map +9 -0
- package/dist/upgrade/index.js +4 -2
- package/dist/upgrade/index.js.map +11 -0
- package/dist/upgrade.js +4 -1
- package/dist/upgrade.js.map +10 -0
- package/package.json +3 -3
- package/src/action.ts +18 -9
- package/src/build/server.ts +1 -1
- package/src/bump.ts +5 -5
- package/src/cdn/invalidate-cache.ts +1 -1
- package/src/deploy/index.ts +14 -6
- package/src/files/watch.ts +8 -0
- package/src/helpers/utils.ts +4 -5
- package/src/orm/generate-model.ts +814 -467
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { log } from '@stacksjs/logging'
|
|
2
|
+
import { getModelName, getTableName } from '@stacksjs/orm'
|
|
2
3
|
import { path } from '@stacksjs/path'
|
|
3
4
|
import { fs, glob } from '@stacksjs/storage'
|
|
4
|
-
import { camelCase, pascalCase } from '@stacksjs/strings'
|
|
5
|
-
import type { Model, RelationConfig } from '@stacksjs/types'
|
|
6
|
-
import { isString
|
|
7
|
-
|
|
5
|
+
import { camelCase, pascalCase, plural, singular, snakeCase } from '@stacksjs/strings'
|
|
6
|
+
import type { Attributes, Model, RelationConfig } from '@stacksjs/types'
|
|
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.
|
|
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,12 +37,14 @@ 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) {
|
|
42
45
|
for (let i = 0; i < middlewares.length; i++) {
|
|
43
46
|
middlewareString += `'${middlewares[i]}'`
|
|
44
|
-
|
|
47
|
+
|
|
45
48
|
if (i < middlewares.length - 1) {
|
|
46
49
|
middlewareString += ','
|
|
47
50
|
}
|
|
@@ -51,24 +54,37 @@ async function generateApiRoutes(modelFiles: string[]) {
|
|
|
51
54
|
middlewareString += `])`
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
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 = ''
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
routeString += `await route.get('${tableName}', 'Actions/${modelName}IndexOrmAction')${middlewareString}\n\n`
|
|
66
|
+
await writeOrmActions(apiRoute as string, modelName)
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
routeString += `await route.post('${tableName}', 'Actions/${modelName}StoreOrmAction')${middlewareString}\n\n`
|
|
68
|
+
path = `${apiRoutes[apiRoute]}.ts`
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
if (!path.includes('/')) {
|
|
71
|
+
path = await lookupFile(path)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!path) {
|
|
75
|
+
throw { message: 'Action Not Found!' }
|
|
76
|
+
}
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
routeString += `await route.get('${tableName}/{id}', 'Actions/${modelName}ShowOrmAction')${middlewareString}\n\n`
|
|
78
|
+
if (apiRoute === 'index') routeString += `await route.get('${uri}', '${path}')\n\n`
|
|
69
79
|
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
if (apiRoute === 'show') routeString += `await route.get('${uri}/{id}', '${path}')\n\n`
|
|
81
|
+
|
|
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
|
|
|
@@ -103,59 +137,157 @@ async function writeModelNames() {
|
|
|
103
137
|
}
|
|
104
138
|
}
|
|
105
139
|
|
|
106
|
-
async function
|
|
140
|
+
async function writeModelRequest() {
|
|
107
141
|
const modelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
142
|
+
const requestD = Bun.file(path.frameworkPath('types/requests.d.ts'))
|
|
143
|
+
|
|
144
|
+
let importTypes = ``
|
|
145
|
+
let importTypesString = ``
|
|
146
|
+
let typeString = `import { Request } from '../core/router/src/request'\n\n`
|
|
147
|
+
|
|
148
|
+
typeString += `interface ValidationType {
|
|
149
|
+
rule: VineType;
|
|
150
|
+
message: { [key: string]: string };
|
|
151
|
+
}\n\n`
|
|
152
|
+
|
|
153
|
+
typeString += `interface ValidationField {
|
|
154
|
+
[key: string]: string | ValidationType;
|
|
155
|
+
validation: ValidationType;
|
|
156
|
+
}\n\n`
|
|
157
|
+
|
|
158
|
+
typeString += `interface CustomAttributes {
|
|
159
|
+
[key: string]: ValidationField
|
|
160
|
+
}\n\n`
|
|
108
161
|
|
|
109
162
|
for (let i = 0; i < modelFiles.length; i++) {
|
|
163
|
+
let fieldStringType = ``
|
|
110
164
|
let fieldString = ``
|
|
111
165
|
let fieldStringInt = ``
|
|
112
|
-
let fileString = `import { Request } from '@stacksjs/router'\nimport { validateField } from '@stacksjs/validation'\nimport
|
|
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`
|
|
113
167
|
|
|
114
168
|
const modeFileElement = modelFiles[i] as string
|
|
115
|
-
|
|
116
169
|
const model = (await import(modeFileElement)).default as Model
|
|
117
170
|
const modelName = getModelName(model, modeFileElement)
|
|
118
|
-
|
|
171
|
+
const useTimestamps = model?.traits?.useTimestamps ?? model?.traits?.timestampable ?? true
|
|
172
|
+
const useSoftDeletes = model?.traits?.useSoftDeletes ?? model?.traits?.softDeletable ?? false
|
|
119
173
|
const attributes = await extractFields(model, modeFileElement)
|
|
120
|
-
|
|
174
|
+
|
|
175
|
+
fieldString += ` id?: number\n`
|
|
176
|
+
fieldStringInt += `public id = 1\n`
|
|
177
|
+
fieldStringType += `'id' |`
|
|
178
|
+
let keyCounter = 0
|
|
179
|
+
let keyCounterForeign = 0
|
|
180
|
+
|
|
181
|
+
const otherModelRelations = await fetchOtherModelRelations(model, modelName)
|
|
182
|
+
|
|
121
183
|
for (const attribute of attributes) {
|
|
184
|
+
const entity = attribute.fieldArray?.entity === 'enum' ? 'string[]' : attribute.fieldArray?.entity
|
|
122
185
|
let defaultValue: any = `''`
|
|
123
|
-
const entity = attribute.fieldArray?.entity === 'enum' ? 'string' : attribute.fieldArray?.entity
|
|
124
|
-
|
|
125
|
-
if (attribute.fieldArray?.entity === 'boolean')
|
|
126
|
-
defaultValue = false
|
|
127
186
|
|
|
128
|
-
if (attribute.fieldArray?.entity === '
|
|
129
|
-
|
|
187
|
+
if (attribute.fieldArray?.entity === 'boolean') defaultValue = false
|
|
188
|
+
if (attribute.fieldArray?.entity === 'number') defaultValue = 0
|
|
130
189
|
|
|
131
190
|
fieldString += ` ${attribute.field}: ${entity}\n `
|
|
191
|
+
fieldStringType += `'${attribute.field}'`
|
|
192
|
+
|
|
193
|
+
if (keyCounter < attributes.length - 1) fieldStringType += ' |'
|
|
132
194
|
|
|
133
195
|
fieldStringInt += `public ${attribute.field} = ${defaultValue}\n`
|
|
134
|
-
|
|
196
|
+
keyCounter++
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const otherModel of otherModelRelations) {
|
|
200
|
+
fieldString += ` ${otherModel.foreignKey}: number\n `
|
|
201
|
+
|
|
202
|
+
if (keyCounter >= attributes.length - 1) fieldStringType += ' |'
|
|
203
|
+
|
|
204
|
+
fieldStringType += `'${otherModel.foreignKey}'`
|
|
205
|
+
|
|
206
|
+
// if (keyCounterForeign < otherModelRelations.length - 1)
|
|
207
|
+
// fieldStringType += ' |'
|
|
208
|
+
|
|
209
|
+
fieldStringInt += `public ${otherModel.foreignKey} = 0\n`
|
|
210
|
+
keyCounterForeign++
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (useTimestamps) {
|
|
214
|
+
fieldStringInt += `public created_at = ''
|
|
215
|
+
public updated_at = ''
|
|
216
|
+
`
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (useSoftDeletes) {
|
|
220
|
+
fieldStringInt += `
|
|
221
|
+
public deleted_at = ''
|
|
222
|
+
`
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
fieldString += `created_at?: string
|
|
226
|
+
updated_at?: string
|
|
227
|
+
deleted_at?: string`
|
|
135
228
|
|
|
136
229
|
const modelLowerCase = camelCase(modelName)
|
|
137
230
|
|
|
138
|
-
const requestFile = Bun.file(path.
|
|
231
|
+
const requestFile = Bun.file(path.frameworkPath(`requests/${modelName}Request.ts`))
|
|
232
|
+
|
|
233
|
+
importTypes = `${modelName}RequestType`
|
|
234
|
+
importTypesString += `${importTypes}`
|
|
235
|
+
|
|
236
|
+
if (i < modelFiles.length - 1) importTypesString += ` | `
|
|
237
|
+
|
|
238
|
+
fileString += `import type { ${importTypes} } from '../types/requests'\n\n`
|
|
239
|
+
|
|
240
|
+
fileString += `interface ValidationType {
|
|
241
|
+
rule: VineType;
|
|
242
|
+
message: { [key: string]: string };
|
|
243
|
+
}\n\n`
|
|
244
|
+
|
|
245
|
+
fileString += `interface ValidationField {
|
|
246
|
+
[key: string]: string | ValidationType;
|
|
247
|
+
validation: ValidationType;
|
|
248
|
+
}\n\n`
|
|
249
|
+
|
|
250
|
+
fileString += `interface CustomAttributes {
|
|
251
|
+
[key: string]: ValidationField
|
|
252
|
+
}`
|
|
139
253
|
|
|
140
|
-
|
|
141
|
-
validate(): void
|
|
254
|
+
const types = `export interface ${modelName}RequestType extends Request {
|
|
255
|
+
validate(attributes?: CustomAttributes): void
|
|
256
|
+
get(key: ${fieldStringType}): string | number | undefined;
|
|
142
257
|
${fieldString}
|
|
143
258
|
}\n\n`
|
|
144
|
-
|
|
145
|
-
|
|
259
|
+
|
|
260
|
+
typeString += `interface RequestData${modelName} {
|
|
261
|
+
${fieldString}
|
|
262
|
+
}\n`
|
|
263
|
+
|
|
264
|
+
typeString += types
|
|
265
|
+
|
|
266
|
+
fileString += `export class ${modelName}Request extends Request implements ${modelName}RequestType {
|
|
146
267
|
${fieldStringInt}
|
|
147
|
-
public validate(): void {
|
|
148
|
-
|
|
268
|
+
public async validate(attributes?: CustomAttributes): Promise<void> {
|
|
269
|
+
if (attributes === undefined || attributes === null) {
|
|
270
|
+
await validateField('${modelName}', this.all())
|
|
271
|
+
} else {
|
|
272
|
+
await customValidate(attributes, this.all())
|
|
273
|
+
}
|
|
274
|
+
|
|
149
275
|
}
|
|
150
276
|
}
|
|
151
|
-
|
|
152
|
-
export const
|
|
277
|
+
|
|
278
|
+
export const request = new ${modelName}Request()
|
|
153
279
|
`
|
|
154
280
|
|
|
155
281
|
const writer = requestFile.writer()
|
|
156
282
|
|
|
157
283
|
writer.write(fileString)
|
|
158
284
|
}
|
|
285
|
+
|
|
286
|
+
typeString += `export type ModelRequest = ${importTypesString}`
|
|
287
|
+
|
|
288
|
+
const requestWrite = requestD.writer()
|
|
289
|
+
|
|
290
|
+
requestWrite.write(typeString)
|
|
159
291
|
}
|
|
160
292
|
|
|
161
293
|
async function writeOrmActions(apiRoute: string, modelName: String): Promise<void> {
|
|
@@ -166,8 +298,7 @@ async function writeOrmActions(apiRoute: string, modelName: String): Promise<voi
|
|
|
166
298
|
|
|
167
299
|
let handleString = ``
|
|
168
300
|
|
|
169
|
-
|
|
170
|
-
actionString += ` import type { ${modelName}RequestType } from '../../requests/${modelName}Request'\n\n`
|
|
301
|
+
actionString += ` import type { ${modelName}RequestType } from '../../types/requests'\n\n`
|
|
171
302
|
|
|
172
303
|
if (apiRoute === 'index') {
|
|
173
304
|
handleString += `async handle(request: ${modelName}RequestType) {
|
|
@@ -181,7 +312,7 @@ async function writeOrmActions(apiRoute: string, modelName: String): Promise<voi
|
|
|
181
312
|
handleString += `async handle(request: ${modelName}RequestType) {
|
|
182
313
|
const id = await request.getParam('id')
|
|
183
314
|
|
|
184
|
-
return ${modelName}.findOrFail(Number(id))
|
|
315
|
+
return await ${modelName}.findOrFail(Number(id))
|
|
185
316
|
},`
|
|
186
317
|
|
|
187
318
|
method = 'GET'
|
|
@@ -203,6 +334,7 @@ async function writeOrmActions(apiRoute: string, modelName: String): Promise<voi
|
|
|
203
334
|
|
|
204
335
|
if (apiRoute === 'store') {
|
|
205
336
|
handleString += `async handle(request: ${modelName}RequestType) {
|
|
337
|
+
await request.validate()
|
|
206
338
|
const model = await ${modelName}.create(request.all())
|
|
207
339
|
|
|
208
340
|
return model
|
|
@@ -213,6 +345,8 @@ async function writeOrmActions(apiRoute: string, modelName: String): Promise<voi
|
|
|
213
345
|
|
|
214
346
|
if (apiRoute === 'update') {
|
|
215
347
|
handleString += `async handle(request: ${modelName}RequestType) {
|
|
348
|
+
await request.validate()
|
|
349
|
+
|
|
216
350
|
const id = request.getParam('id')
|
|
217
351
|
|
|
218
352
|
const model = await ${modelName}.findOrFail(Number(id))
|
|
@@ -231,34 +365,37 @@ async function writeOrmActions(apiRoute: string, modelName: String): Promise<voi
|
|
|
231
365
|
})
|
|
232
366
|
`
|
|
233
367
|
|
|
234
|
-
const file = Bun.file(path.
|
|
368
|
+
const file = Bun.file(path.builtUserActionsPath(`src/${modelName}${formattedApiRoute}OrmAction.ts`))
|
|
235
369
|
|
|
236
370
|
const writer = file.writer()
|
|
237
371
|
|
|
238
372
|
writer.write(actionString)
|
|
239
373
|
}
|
|
240
374
|
|
|
241
|
-
async function initiateModelGeneration(): Promise<void> {
|
|
242
|
-
await deleteExistingModels()
|
|
243
|
-
await deleteExistingOrmActions()
|
|
375
|
+
async function initiateModelGeneration(modelStringFile?: string): Promise<void> {
|
|
376
|
+
await deleteExistingModels(modelStringFile)
|
|
377
|
+
await deleteExistingOrmActions(modelStringFile)
|
|
244
378
|
await deleteExistingModelNameTypes()
|
|
245
|
-
await
|
|
379
|
+
await deleteExistingModelRequest(modelStringFile)
|
|
380
|
+
await deleteExistingOrmRoute()
|
|
246
381
|
|
|
247
382
|
await writeModelNames()
|
|
248
|
-
await
|
|
383
|
+
await writeModelRequest()
|
|
249
384
|
|
|
250
385
|
const modelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
251
386
|
|
|
252
387
|
await generateApiRoutes(modelFiles)
|
|
253
388
|
|
|
254
389
|
for (const modelFile of modelFiles) {
|
|
390
|
+
if (modelStringFile && modelStringFile !== modelFile) continue
|
|
391
|
+
|
|
255
392
|
log.debug(`Processing model file: ${modelFile}`)
|
|
256
393
|
|
|
257
394
|
const model = (await import(modelFile)).default as Model
|
|
258
395
|
const tableName = await getTableName(model, modelFile)
|
|
259
396
|
const modelName = getModelName(model, modelFile)
|
|
260
397
|
|
|
261
|
-
const file = Bun.file(path.
|
|
398
|
+
const file = Bun.file(path.frameworkPath(`orm/src/models/${modelName}.ts`))
|
|
262
399
|
const fields = await extractFields(model, modelFile)
|
|
263
400
|
const classString = await generateModelString(tableName, modelName, model, fields)
|
|
264
401
|
|
|
@@ -296,7 +433,9 @@ async function getRelations(model: Model, modelName: string): Promise<RelationCo
|
|
|
296
433
|
relationName: relationInstance.relationName || '',
|
|
297
434
|
throughModel: relationInstance.through || '',
|
|
298
435
|
throughForeignKey: relationInstance.throughForeignKey || '',
|
|
299
|
-
pivotTable:
|
|
436
|
+
pivotTable:
|
|
437
|
+
relationInstance?.pivotTable ||
|
|
438
|
+
getPivotTableName(plural(formattedModelName), plural(modelRelation.table || '')),
|
|
300
439
|
})
|
|
301
440
|
}
|
|
302
441
|
}
|
|
@@ -305,47 +444,95 @@ async function getRelations(model: Model, modelName: string): Promise<RelationCo
|
|
|
305
444
|
return relationships
|
|
306
445
|
}
|
|
307
446
|
|
|
447
|
+
function getPivotTableName(formattedModelName: string, modelRelationTable: string): string {
|
|
448
|
+
// Create an array of the model names
|
|
449
|
+
const models = [formattedModelName, modelRelationTable]
|
|
450
|
+
|
|
451
|
+
// Sort the array alphabetically
|
|
452
|
+
models.sort()
|
|
453
|
+
|
|
454
|
+
models[0] = singular(models[0] || '')
|
|
455
|
+
|
|
456
|
+
// Join the sorted array with an underscore
|
|
457
|
+
const pivotTableName = models.join('_')
|
|
458
|
+
|
|
459
|
+
return pivotTableName
|
|
460
|
+
}
|
|
461
|
+
|
|
308
462
|
function hasRelations(obj: any, key: string): boolean {
|
|
309
463
|
return key in obj
|
|
310
464
|
}
|
|
311
465
|
|
|
312
|
-
async function deleteExistingModels() {
|
|
313
|
-
const
|
|
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`))
|
|
314
480
|
|
|
315
481
|
for (const modelPath of modelPaths) {
|
|
316
482
|
if (fs.existsSync(modelPath)) await Bun.$`rm ${modelPath}`
|
|
317
483
|
}
|
|
484
|
+
}
|
|
318
485
|
|
|
319
|
-
|
|
486
|
+
async function deleteExistingOrmActions(modelStringFile?: string) {
|
|
487
|
+
const routes = path.frameworkPath(`orm/routes`)
|
|
488
|
+
if (fs.existsSync(routes)) await Bun.$`rm ${routes}`
|
|
320
489
|
|
|
321
|
-
if (
|
|
322
|
-
}
|
|
490
|
+
if (modelStringFile) {
|
|
491
|
+
const ormPath = path.builtUserActionsPath(`src/${modelStringFile}.ts`)
|
|
323
492
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
493
|
+
if (fs.existsSync(ormPath)) await Bun.$`rm ${ormPath}`
|
|
494
|
+
|
|
495
|
+
return
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const ormPaths = glob.sync(path.builtUserActionsPath(`**/*.ts`))
|
|
327
499
|
|
|
328
500
|
for (const ormPath of ormPaths) {
|
|
329
501
|
if (fs.existsSync(ormPath)) await Bun.$`rm ${ormPath}`
|
|
330
502
|
}
|
|
331
|
-
|
|
332
|
-
if (fs.existsSync(routes)) await Bun.$`rm ${routes}`
|
|
333
503
|
}
|
|
334
504
|
|
|
335
505
|
async function deleteExistingModelNameTypes() {
|
|
336
|
-
const typeFile = path.
|
|
506
|
+
const typeFile = path.corePath(`types/src/model-names.ts`)
|
|
337
507
|
|
|
338
508
|
if (fs.existsSync(typeFile)) await Bun.$`rm ${typeFile}`
|
|
339
509
|
}
|
|
340
510
|
|
|
341
|
-
async function
|
|
342
|
-
const
|
|
511
|
+
async function deleteExistingModelRequest(modelStringFile?: string) {
|
|
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`))
|
|
343
524
|
|
|
344
525
|
for (const requestFile of requestFiles) {
|
|
345
526
|
if (fs.existsSync(requestFile)) await Bun.$`rm ${requestFile}`
|
|
346
527
|
}
|
|
347
528
|
}
|
|
348
529
|
|
|
530
|
+
async function deleteExistingOrmRoute() {
|
|
531
|
+
const ormRoute = path.projectStoragePath('framework/orm/routes.ts')
|
|
532
|
+
|
|
533
|
+
if (fs.existsSync(ormRoute)) await Bun.$`rm ${ormRoute}`
|
|
534
|
+
}
|
|
535
|
+
|
|
349
536
|
async function setKyselyTypes() {
|
|
350
537
|
let text = ``
|
|
351
538
|
const modelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
@@ -369,7 +556,7 @@ async function setKyselyTypes() {
|
|
|
369
556
|
const model = (await import(modelFile)).default as Model
|
|
370
557
|
const modelName = getModelName(model, modelFile)
|
|
371
558
|
const pivotTables = await getPivotTables(model, modelName)
|
|
372
|
-
|
|
559
|
+
|
|
373
560
|
for (const pivotTable of pivotTables) {
|
|
374
561
|
const words = pivotTable.table.split('_')
|
|
375
562
|
|
|
@@ -402,7 +589,7 @@ async function setKyselyTypes() {
|
|
|
402
589
|
|
|
403
590
|
text += `}`
|
|
404
591
|
|
|
405
|
-
const file = Bun.file(path.
|
|
592
|
+
const file = Bun.file(path.frameworkPath(`orm/src/types.ts`))
|
|
406
593
|
|
|
407
594
|
const writer = file.writer()
|
|
408
595
|
|
|
@@ -413,7 +600,10 @@ async function setKyselyTypes() {
|
|
|
413
600
|
|
|
414
601
|
async function extractFields(model: Model, modelFile: string): Promise<ModelElement[]> {
|
|
415
602
|
// TODO: we can improve this type
|
|
416
|
-
|
|
603
|
+
let fields: Record<string, any> | undefined = model.attributes
|
|
604
|
+
|
|
605
|
+
if (!fields) fields = {}
|
|
606
|
+
|
|
417
607
|
const fieldKeys = Object.keys(fields)
|
|
418
608
|
|
|
419
609
|
const rules: string[] = []
|
|
@@ -504,7 +694,7 @@ function getRelationCount(relation: string): string {
|
|
|
504
694
|
|
|
505
695
|
async function getPivotTables(
|
|
506
696
|
model: Model,
|
|
507
|
-
modelName: string
|
|
697
|
+
modelName: string,
|
|
508
698
|
): Promise<{ table: string; firstForeignKey?: string; secondForeignKey?: string }[]> {
|
|
509
699
|
const pivotTable = []
|
|
510
700
|
|
|
@@ -515,8 +705,7 @@ async function getPivotTables(
|
|
|
515
705
|
const modelRelation = (await import(modelRelationPath)).default as Model
|
|
516
706
|
const formattedModelName = modelName.toLowerCase()
|
|
517
707
|
|
|
518
|
-
const firstForeignKey =
|
|
519
|
-
belongsToManyRelation.firstForeignKey || `${modelName.toLowerCase()}_${model.primaryKey}`
|
|
708
|
+
const firstForeignKey = belongsToManyRelation.firstForeignKey || `${modelName.toLowerCase()}_${model.primaryKey}`
|
|
520
709
|
const secondForeignKey =
|
|
521
710
|
belongsToManyRelation.secondForeignKey || `${modelRelation.name?.toLowerCase()}_${model.primaryKey}`
|
|
522
711
|
|
|
@@ -540,7 +729,7 @@ export async function fetchOtherModelRelations(model: Model, modelName: string):
|
|
|
540
729
|
for (let i = 0; i < modelFiles.length; i++) {
|
|
541
730
|
const modelFileElement = modelFiles[i] as string
|
|
542
731
|
const modelFile = await import(modelFileElement)
|
|
543
|
-
|
|
732
|
+
|
|
544
733
|
if (modelName === modelFile.default.name) continue
|
|
545
734
|
|
|
546
735
|
const otherModelName = getModelName(modelFile, modelFileElement)
|
|
@@ -557,13 +746,49 @@ export async function fetchOtherModelRelations(model: Model, modelName: string):
|
|
|
557
746
|
return modelRelations
|
|
558
747
|
}
|
|
559
748
|
|
|
560
|
-
|
|
749
|
+
function getHiddenAttributes(attributes: Attributes | undefined): string[] {
|
|
750
|
+
if (attributes === undefined) return []
|
|
751
|
+
|
|
752
|
+
return Object.keys(attributes).filter((key) => {
|
|
753
|
+
if (attributes === undefined) return false
|
|
754
|
+
|
|
755
|
+
return attributes[key]?.hidden === true
|
|
756
|
+
})
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function getFillableAttributes(attributes: Attributes | undefined): string[] {
|
|
760
|
+
if (attributes === undefined) return []
|
|
761
|
+
|
|
762
|
+
return Object.keys(attributes)
|
|
763
|
+
.filter((key) => {
|
|
764
|
+
if (attributes === undefined) return false
|
|
765
|
+
|
|
766
|
+
return attributes[key]?.fillable === true
|
|
767
|
+
})
|
|
768
|
+
.map((attribute) => snakeCase(attribute))
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
async function generateModelString(
|
|
772
|
+
tableName: string,
|
|
773
|
+
modelName: string,
|
|
774
|
+
model: Model,
|
|
775
|
+
attributes: ModelElement[],
|
|
776
|
+
): Promise<string> {
|
|
561
777
|
const formattedTableName = pascalCase(tableName) // users -> Users
|
|
562
778
|
const formattedModelName = modelName.toLowerCase() // User -> user
|
|
563
779
|
|
|
564
780
|
let fieldString = ''
|
|
781
|
+
let constructorFields = ''
|
|
782
|
+
let jsonFields = '{\n'
|
|
783
|
+
let declareFields = ''
|
|
784
|
+
let whereStatements = ''
|
|
785
|
+
let whereFunctionStatements = ''
|
|
565
786
|
let relationMethods = ``
|
|
566
787
|
let relationImports = ``
|
|
788
|
+
let twoFactorStatements = ''
|
|
789
|
+
let mittCreateStatement = ``
|
|
790
|
+
let mittUpdateStatement = ``
|
|
791
|
+
let mittDeleteStatement = ``
|
|
567
792
|
|
|
568
793
|
const relations = await getRelations(model, modelName)
|
|
569
794
|
|
|
@@ -571,13 +796,37 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
571
796
|
relationImports += `import ${relationInstance.model} from './${relationInstance.model}'\n\n`
|
|
572
797
|
}
|
|
573
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
|
+
|
|
574
824
|
for (const relation of relations) {
|
|
575
825
|
const modelRelation = relation.model
|
|
576
826
|
const foreignKeyRelation = relation.foreignKey
|
|
577
827
|
const tableRelation = relation.table || ''
|
|
578
828
|
const pivotTableRelation = relation.pivotTable
|
|
579
829
|
const formattedModelRelation = modelRelation.toLowerCase()
|
|
580
|
-
const capitalizeTableRelation = tableRelation.charAt(0).toUpperCase() + tableRelation.slice(1)
|
|
581
830
|
|
|
582
831
|
const relationType = getRelationType(relation.relationship)
|
|
583
832
|
const relationCount = getRelationCount(relation.relationship)
|
|
@@ -586,27 +835,28 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
586
835
|
const relationName = relation.relationName || formattedModelName + modelRelation
|
|
587
836
|
const throughRelation = relation.throughModel
|
|
588
837
|
|
|
589
|
-
|
|
838
|
+
if (relation.throughModel === undefined) continue
|
|
839
|
+
|
|
840
|
+
const formattedThroughRelation = relation?.throughModel?.toLowerCase()
|
|
590
841
|
const throughTableRelation = throughRelation
|
|
591
842
|
const foreignKeyThroughRelation = relation.throughForeignKey || `${formattedThroughRelation}_id`
|
|
592
843
|
|
|
593
844
|
relationMethods += `
|
|
594
845
|
async ${relationName}() {
|
|
595
|
-
if (this
|
|
846
|
+
if (this.id === undefined)
|
|
596
847
|
throw new Error('Relation Error!')
|
|
597
848
|
|
|
598
849
|
const firstModel = await db.selectFrom('${throughTableRelation}')
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
850
|
+
.where('${foreignKeyRelation}', '=', this.id)
|
|
851
|
+
.selectAll()
|
|
852
|
+
.executeTakeFirst()
|
|
602
853
|
|
|
603
854
|
if (! firstModel)
|
|
604
855
|
throw new Error('Model Relation Not Found!')
|
|
605
856
|
|
|
606
|
-
const finalModel =
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
.executeTakeFirst()
|
|
857
|
+
const finalModel = ${modelRelation}
|
|
858
|
+
.where('${foreignKeyThroughRelation}', '=', firstModel.id)
|
|
859
|
+
.first()
|
|
610
860
|
|
|
611
861
|
return new ${modelRelation}.modelInstance(finalModel)
|
|
612
862
|
}\n\n`
|
|
@@ -617,11 +867,11 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
617
867
|
|
|
618
868
|
relationMethods += `
|
|
619
869
|
async ${relationName}() {
|
|
620
|
-
if (this
|
|
870
|
+
if (this.id === undefined)
|
|
621
871
|
throw new Error('Relation Error!')
|
|
622
872
|
|
|
623
873
|
const results = await db.selectFrom('${tableRelation}')
|
|
624
|
-
.where('${foreignKeyRelation}', '=', this
|
|
874
|
+
.where('${foreignKeyRelation}', '=', this.id)
|
|
625
875
|
.selectAll()
|
|
626
876
|
.execute()
|
|
627
877
|
|
|
@@ -633,18 +883,16 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
633
883
|
const relationName = relation.relationName || formattedModelRelation
|
|
634
884
|
relationMethods += `
|
|
635
885
|
async ${relationName}() {
|
|
636
|
-
if (this
|
|
886
|
+
if (this.id === undefined)
|
|
637
887
|
throw new Error('Relation Error!')
|
|
638
888
|
|
|
639
|
-
const model =
|
|
640
|
-
.where('${foreignKeyRelation}', '=', this
|
|
641
|
-
.selectAll()
|
|
642
|
-
.executeTakeFirst()
|
|
889
|
+
const model = ${modelRelation}
|
|
890
|
+
.where('${foreignKeyRelation}', '=', this.id).first()
|
|
643
891
|
|
|
644
892
|
if (! model)
|
|
645
893
|
throw new Error('Model Relation Not Found!')
|
|
646
894
|
|
|
647
|
-
return
|
|
895
|
+
return model
|
|
648
896
|
}\n\n`
|
|
649
897
|
}
|
|
650
898
|
|
|
@@ -656,47 +904,168 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
656
904
|
if (this.${foreignKeyRelation} === undefined)
|
|
657
905
|
throw new Error('Relation Error!')
|
|
658
906
|
|
|
659
|
-
const model = await
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
.executeTakeFirst()
|
|
907
|
+
const model = await ${modelRelation}
|
|
908
|
+
.where('id', '=', ${foreignKeyRelation})
|
|
909
|
+
.first()
|
|
663
910
|
|
|
664
911
|
if (! model)
|
|
665
912
|
throw new Error('Model Relation Not Found!')
|
|
666
913
|
|
|
667
|
-
return
|
|
914
|
+
return model
|
|
668
915
|
}\n\n`
|
|
669
916
|
}
|
|
670
917
|
|
|
671
918
|
if (relationType === 'belongsType' && relationCount === 'many') {
|
|
672
919
|
const pivotTable = pivotTableRelation || tableRelation
|
|
673
|
-
const relationName = relation.relationName || formattedModelName +
|
|
920
|
+
const relationName = relation.relationName || formattedModelName + plural(pascalCase(modelRelation))
|
|
674
921
|
|
|
675
922
|
relationMethods += `
|
|
676
923
|
async ${relationName}() {
|
|
677
|
-
if (this
|
|
924
|
+
if (this.id === undefined)
|
|
678
925
|
throw new Error('Relation Error!')
|
|
679
926
|
|
|
680
927
|
const results = await db.selectFrom('${pivotTable}')
|
|
681
|
-
.where('${foreignKeyRelation}', '=', this
|
|
928
|
+
.where('${foreignKeyRelation}', '=', this.id)
|
|
682
929
|
.selectAll()
|
|
683
930
|
.execute()
|
|
684
931
|
|
|
685
|
-
|
|
932
|
+
const tableRelationIds = results.map(result => result.${singular(tableRelation)}_id)
|
|
933
|
+
|
|
934
|
+
if (! tableRelationIds.length)
|
|
935
|
+
throw new Error('Relation Error!')
|
|
936
|
+
|
|
937
|
+
const relationResults = await ${modelRelation}.whereIn('id', tableRelationIds).get()
|
|
938
|
+
|
|
939
|
+
return relationResults
|
|
686
940
|
}\n\n`
|
|
687
941
|
}
|
|
688
942
|
}
|
|
689
943
|
|
|
690
|
-
|
|
944
|
+
declareFields += `public id: number | undefined \n `
|
|
945
|
+
|
|
946
|
+
constructorFields += `this.id = ${formattedModelName}?.id\n `
|
|
947
|
+
|
|
948
|
+
const useTwoFactor = model.traits?.useAuth?.useTwoFactor
|
|
949
|
+
|
|
950
|
+
if (useTwoFactor) {
|
|
951
|
+
declareFields += `public two_factor_secret: string | undefined \n`
|
|
952
|
+
constructorFields += `this.two_factor_secret = ${formattedModelName}?.two_factor_secret\n `
|
|
953
|
+
|
|
954
|
+
twoFactorStatements += `
|
|
955
|
+
async generateTwoFactorForModel() {
|
|
956
|
+
const secret = generateTwoFactorSecret()
|
|
957
|
+
|
|
958
|
+
await this.update({ 'two_factor_secret': secret })
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
verifyTwoFactorCode(code: string): boolean {
|
|
962
|
+
if (! this.${formattedModelName}) return false
|
|
963
|
+
|
|
964
|
+
const modelTwoFactorSecret = this.${formattedModelName}.two_factor_secret
|
|
965
|
+
const isValid = verifyTwoFactorCode(code, modelTwoFactorSecret)
|
|
966
|
+
|
|
967
|
+
return isValid
|
|
968
|
+
}
|
|
969
|
+
`
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
jsonFields += `\nid: this.id,\n`
|
|
973
|
+
for (const attribute of attributes) {
|
|
974
|
+
const entity = attribute.fieldArray?.entity === 'enum' ? 'string[]' : attribute.fieldArray?.entity
|
|
975
|
+
|
|
976
|
+
fieldString += ` ${attribute.field}: ${entity}\n `
|
|
977
|
+
|
|
978
|
+
declareFields += `public ${snakeCase(attribute.field)}: ${entity} | undefined \n `
|
|
979
|
+
|
|
980
|
+
constructorFields += `this.${snakeCase(attribute.field)} = ${formattedModelName}?.${snakeCase(attribute.field)}\n `
|
|
981
|
+
jsonFields += `${snakeCase(attribute.field)}: this.${snakeCase(attribute.field)},\n `
|
|
982
|
+
|
|
983
|
+
whereStatements += `static where${pascalCase(attribute.field)}(value: string | number | boolean | undefined | null): ${modelName}Model {
|
|
984
|
+
const instance = new this(null)
|
|
985
|
+
|
|
986
|
+
instance.query = instance.query.where('${attribute.field}', '=', value)
|
|
987
|
+
|
|
988
|
+
return instance
|
|
989
|
+
} \n\n`
|
|
990
|
+
|
|
991
|
+
whereFunctionStatements += `export async function where${pascalCase(attribute.field)}(value: string | number | boolean | undefined | null): Promise<${modelName}Model[]> {
|
|
992
|
+
const query = db.selectFrom('${tableName}').where('${attribute.field}', '=', value)
|
|
993
|
+
|
|
994
|
+
const results = await query.execute()
|
|
995
|
+
|
|
996
|
+
return results.map(modelItem => new ${modelName}Model(modelItem))
|
|
997
|
+
} \n\n`
|
|
998
|
+
}
|
|
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 += '}'
|
|
691
1032
|
|
|
692
1033
|
const otherModelRelations = await fetchOtherModelRelations(model, modelName)
|
|
693
1034
|
|
|
694
|
-
for (const otherModelRelation of otherModelRelations)
|
|
1035
|
+
for (const otherModelRelation of otherModelRelations) {
|
|
1036
|
+
fieldString += ` ${otherModelRelation.foreignKey}: number \n`
|
|
1037
|
+
|
|
1038
|
+
declareFields += `public ${otherModelRelation.foreignKey}: number | undefined \n `
|
|
1039
|
+
|
|
1040
|
+
constructorFields += `this.${otherModelRelation.foreignKey} = ${formattedModelName}?.${otherModelRelation.foreignKey}\n `
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
if (useTwoFactor) {
|
|
1044
|
+
fieldString += `two_factor_secret: string \n`
|
|
1045
|
+
}
|
|
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
|
+
|
|
1060
|
+
const hidden = JSON.stringify(getHiddenAttributes(model.attributes))
|
|
1061
|
+
const fillable = JSON.stringify(getFillableAttributes(model.attributes))
|
|
695
1062
|
|
|
696
1063
|
return `import type { ColumnType, Generated, Insertable, Selectable, Updateable } from 'kysely'
|
|
697
|
-
import type { Result } from '@stacksjs/error-handling'
|
|
698
|
-
import { err, handleError, ok } from '@stacksjs/error-handling'
|
|
699
1064
|
import { db } from '@stacksjs/database'
|
|
1065
|
+
import { sql } from '@stacksjs/database'
|
|
1066
|
+
import { dispatch } from '@stacksjs/events'
|
|
1067
|
+
import { generateTwoFactorSecret } from '@stacksjs/auth'
|
|
1068
|
+
import { verifyTwoFactorCode } from '@stacksjs/auth'
|
|
700
1069
|
${relationImports}
|
|
701
1070
|
// import { Kysely, MysqlDialect, PostgresDialect } from 'kysely'
|
|
702
1071
|
// import { Pool } from 'pg'
|
|
@@ -705,9 +1074,6 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
705
1074
|
export interface ${formattedTableName}Table {
|
|
706
1075
|
id: Generated<number>
|
|
707
1076
|
${fieldString}
|
|
708
|
-
created_at: ColumnType<Date, string | undefined, never>
|
|
709
|
-
updated_at: ColumnType<Date, string | undefined, never>
|
|
710
|
-
deleted_at: ColumnType<Date, string | undefined, never>
|
|
711
1077
|
}
|
|
712
1078
|
|
|
713
1079
|
interface ${modelName}Response {
|
|
@@ -739,16 +1105,21 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
739
1105
|
}
|
|
740
1106
|
|
|
741
1107
|
export class ${modelName}Model {
|
|
742
|
-
private ${
|
|
743
|
-
private
|
|
744
|
-
private
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1108
|
+
private hidden = ${hidden}
|
|
1109
|
+
private fillable = ${fillable}
|
|
1110
|
+
private softDeletes = ${useSoftDeletes}
|
|
1111
|
+
protected query: any
|
|
1112
|
+
protected hasSelect: boolean
|
|
1113
|
+
${declareFields}
|
|
1114
|
+
constructor(${formattedModelName}: Partial<${modelName}Type> | null) {
|
|
1115
|
+
${constructorFields}
|
|
1116
|
+
|
|
1117
|
+
this.query = db.selectFrom('${tableName}')
|
|
1118
|
+
this.hasSelect = false
|
|
748
1119
|
}
|
|
749
1120
|
|
|
750
|
-
// Method to find a ${
|
|
751
|
-
|
|
1121
|
+
// Method to find a ${modelName} by ID
|
|
1122
|
+
async find(id: number, fields?: (keyof ${modelName}Type)[]): Promise<${modelName}Model | undefined> {
|
|
752
1123
|
let query = db.selectFrom('${tableName}').where('id', '=', id)
|
|
753
1124
|
|
|
754
1125
|
if (fields)
|
|
@@ -759,14 +1130,17 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
759
1130
|
const model = await query.executeTakeFirst()
|
|
760
1131
|
|
|
761
1132
|
if (!model)
|
|
762
|
-
return
|
|
1133
|
+
return undefined
|
|
763
1134
|
|
|
764
|
-
return new ${modelName}Model(model)
|
|
1135
|
+
return this.parseResult(new ${modelName}Model(model))
|
|
765
1136
|
}
|
|
766
1137
|
|
|
767
|
-
|
|
1138
|
+
// Method to find a ${modelName} by ID
|
|
1139
|
+
static async find(id: number, fields?: (keyof ${modelName}Type)[]): Promise<${modelName}Model | undefined> {
|
|
768
1140
|
let query = db.selectFrom('${tableName}').where('id', '=', id)
|
|
769
1141
|
|
|
1142
|
+
const instance = new this(null)
|
|
1143
|
+
|
|
770
1144
|
if (fields)
|
|
771
1145
|
query = query.select(fields)
|
|
772
1146
|
else
|
|
@@ -774,29 +1148,69 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
774
1148
|
|
|
775
1149
|
const model = await query.executeTakeFirst()
|
|
776
1150
|
|
|
1151
|
+
if (!model)
|
|
1152
|
+
return undefined
|
|
1153
|
+
|
|
1154
|
+
return instance.parseResult(new this(model))
|
|
1155
|
+
}
|
|
1156
|
+
|
|
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> {
|
|
1173
|
+
let query = db.selectFrom('${tableName}').where('id', '=', id)
|
|
1174
|
+
|
|
1175
|
+
const instance = new this(null)
|
|
1176
|
+
|
|
1177
|
+
if (instance.softDeletes) {
|
|
1178
|
+
query = query.where('deleted_at', 'is', null);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
query = query.selectAll()
|
|
1182
|
+
|
|
1183
|
+
const model = await query.executeTakeFirst()
|
|
1184
|
+
|
|
777
1185
|
if (!model)
|
|
778
1186
|
throw(\`No model results found for \${id}\ \`)
|
|
779
1187
|
|
|
780
|
-
|
|
1188
|
+
|
|
1189
|
+
return instance.parseResult(new this(model))
|
|
781
1190
|
}
|
|
782
1191
|
|
|
783
|
-
static async findMany(ids: number[]
|
|
1192
|
+
static async findMany(ids: number[]): Promise<${modelName}Model[]> {
|
|
784
1193
|
let query = db.selectFrom('${tableName}').where('id', 'in', ids)
|
|
785
1194
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
query = query.
|
|
1195
|
+
const instance = new this(null)
|
|
1196
|
+
|
|
1197
|
+
if (instance.softDeletes) {
|
|
1198
|
+
query = query.where('deleted_at', 'is', null);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
query = query.selectAll()
|
|
790
1202
|
|
|
791
1203
|
const model = await query.execute()
|
|
792
1204
|
|
|
793
|
-
return model.map(modelItem => new ${modelName}Model(modelItem))
|
|
1205
|
+
return model.map(modelItem => instance.parseResult(new ${modelName}Model(modelItem)))
|
|
794
1206
|
}
|
|
795
1207
|
|
|
796
|
-
// Method to get a ${
|
|
797
|
-
static async
|
|
1208
|
+
// Method to get a ${modelName} by criteria
|
|
1209
|
+
static async fetch(criteria: Partial<${modelName}Type>, options: QueryOptions = {}): Promise<${modelName}Model[]> {
|
|
798
1210
|
let query = db.selectFrom('${tableName}')
|
|
799
1211
|
|
|
1212
|
+
const instance = new this(null)
|
|
1213
|
+
|
|
800
1214
|
// Apply sorting from options
|
|
801
1215
|
if (options.sort)
|
|
802
1216
|
query = query.orderBy(options.sort.column, options.sort.order)
|
|
@@ -808,12 +1222,83 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
808
1222
|
if (options.offset !== undefined)
|
|
809
1223
|
query = query.offset(options.offset)
|
|
810
1224
|
|
|
1225
|
+
if (instance.softDeletes) {
|
|
1226
|
+
query = query.where('deleted_at', 'is', null);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
811
1229
|
const model = await query.selectAll().execute()
|
|
812
1230
|
return model.map(modelItem => new ${modelName}Model(modelItem))
|
|
813
1231
|
}
|
|
814
1232
|
|
|
1233
|
+
// Method to get a ${modelName} by criteria
|
|
1234
|
+
static async get(): Promise<${modelName}Model[]> {
|
|
1235
|
+
let query = db.selectFrom('${tableName}');
|
|
1236
|
+
|
|
1237
|
+
const instance = new this(null)
|
|
1238
|
+
|
|
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));
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Method to get a ${modelName} by criteria
|
|
1250
|
+
async get(): Promise<${modelName}Model[]> {
|
|
1251
|
+
if (this.hasSelect) {
|
|
1252
|
+
|
|
1253
|
+
if (this.softDeletes) {
|
|
1254
|
+
this.query = this.query.where('deleted_at', 'is', null);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
const model = await this.query.execute()
|
|
1258
|
+
|
|
1259
|
+
return model.map((modelItem: ${modelName}Model) => new ${modelName}Model(modelItem))
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
if (this.softDeletes) {
|
|
1263
|
+
this.query = this.query.where('deleted_at', 'is', null);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const model = await this.query.selectAll().execute()
|
|
1267
|
+
|
|
1268
|
+
return model.map((modelItem: ${modelName}Model) => new ${modelName}Model(modelItem))
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
static async count(): Promise<number> {
|
|
1272
|
+
const instance = new this(null)
|
|
1273
|
+
|
|
1274
|
+
if (instance.softDeletes) {
|
|
1275
|
+
instance.query = instance.query.where('deleted_at', 'is', null);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
const results = await instance.query.selectAll().execute()
|
|
1279
|
+
|
|
1280
|
+
return results.length
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
async count(): Promise<number> {
|
|
1284
|
+
if (this.hasSelect) {
|
|
1285
|
+
|
|
1286
|
+
if (this.softDeletes) {
|
|
1287
|
+
this.query = this.query.where('deleted_at', 'is', null);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
const results = await this.query.execute()
|
|
1291
|
+
|
|
1292
|
+
return results.length
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
const results = await this.query.selectAll().execute()
|
|
1296
|
+
|
|
1297
|
+
return results.length
|
|
1298
|
+
}
|
|
1299
|
+
|
|
815
1300
|
// Method to get all ${tableName}
|
|
816
|
-
static async
|
|
1301
|
+
static async paginate(options: QueryOptions = { limit: 10, offset: 0, page: 1 }): Promise<${modelName}Response> {
|
|
817
1302
|
const totalRecordsResult = await db.selectFrom('${tableName}')
|
|
818
1303
|
.select(db.fn.count('id').as('total')) // Use 'id' or another actual column name
|
|
819
1304
|
.executeTakeFirst()
|
|
@@ -825,7 +1310,7 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
825
1310
|
.selectAll()
|
|
826
1311
|
.orderBy('id', 'asc') // Assuming 'id' is used for cursor-based pagination
|
|
827
1312
|
.limit((options.limit ?? 10) + 1) // Fetch one extra record
|
|
828
|
-
.offset((options.page - 1) * (options.limit ?? 10))
|
|
1313
|
+
.offset(((options.page ?? 1) - 1) * (options.limit ?? 10)) // Ensure options.page is not undefined
|
|
829
1314
|
.execute()
|
|
830
1315
|
|
|
831
1316
|
let nextCursor = null
|
|
@@ -844,24 +1329,53 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
844
1329
|
}
|
|
845
1330
|
|
|
846
1331
|
// Method to create a new ${formattedModelName}
|
|
847
|
-
static async create(new${modelName}: New${modelName}): Promise<${modelName}Model> {
|
|
1332
|
+
static async create(new${modelName}: New${modelName}): Promise<${modelName}Model | undefined> {
|
|
1333
|
+
const instance = new this(null)
|
|
1334
|
+
const filteredValues = Object.keys(new${modelName})
|
|
1335
|
+
.filter(key => instance.fillable.includes(key))
|
|
1336
|
+
.reduce((obj: any, key) => {
|
|
1337
|
+
obj[key] = new${modelName}[key];
|
|
1338
|
+
return obj
|
|
1339
|
+
}, {})
|
|
1340
|
+
|
|
1341
|
+
if (Object.keys(filteredValues).length === 0) {
|
|
1342
|
+
return undefined
|
|
1343
|
+
}
|
|
1344
|
+
|
|
848
1345
|
const result = await db.insertInto('${tableName}')
|
|
849
|
-
.values(
|
|
1346
|
+
.values(filteredValues)
|
|
850
1347
|
.executeTakeFirstOrThrow()
|
|
851
1348
|
|
|
852
|
-
|
|
1349
|
+
const model = await find(Number(result.insertId)) as ${modelName}Model
|
|
1350
|
+
|
|
1351
|
+
${mittCreateStatement}
|
|
1352
|
+
|
|
1353
|
+
return model
|
|
853
1354
|
}
|
|
854
1355
|
|
|
855
|
-
// Method to remove a ${
|
|
856
|
-
static async remove(id: number): Promise
|
|
857
|
-
const
|
|
1356
|
+
// Method to remove a ${modelName}
|
|
1357
|
+
static async remove(id: number): Promise<void> {
|
|
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
|
+
})
|
|
858
1367
|
.where('id', '=', id)
|
|
859
|
-
.
|
|
1368
|
+
.execute();
|
|
1369
|
+
} else {
|
|
1370
|
+
await db.deleteFrom('${tableName}')
|
|
1371
|
+
.where('id', '=', id)
|
|
1372
|
+
.execute();
|
|
1373
|
+
}
|
|
860
1374
|
|
|
861
|
-
|
|
1375
|
+
${mittDeleteStatement}
|
|
862
1376
|
}
|
|
863
1377
|
|
|
864
|
-
|
|
1378
|
+
where(...args: (string | number | boolean | undefined | null)[]): ${modelName}Model {
|
|
865
1379
|
let column: any
|
|
866
1380
|
let operator: any
|
|
867
1381
|
let value: any
|
|
@@ -875,135 +1389,135 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
875
1389
|
throw new Error("Invalid number of arguments")
|
|
876
1390
|
}
|
|
877
1391
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
query = query.where(column, operator, value)
|
|
1392
|
+
this.query = this.query.where(column, operator, value)
|
|
881
1393
|
|
|
882
|
-
return
|
|
1394
|
+
return this
|
|
883
1395
|
}
|
|
884
1396
|
|
|
885
|
-
|
|
886
|
-
let
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
if (criteria.id)
|
|
890
|
-
query = query.where('id', '=', criteria.id) // Kysely is immutable, we must re-assign
|
|
1397
|
+
static where(...args: (string | number | boolean | undefined | null)[]): ${modelName}Model {
|
|
1398
|
+
let column: any
|
|
1399
|
+
let operator: any
|
|
1400
|
+
let value: any
|
|
891
1401
|
|
|
892
|
-
|
|
893
|
-
query = query.where('email', '=', criteria.email)
|
|
1402
|
+
const instance = new this(null)
|
|
894
1403
|
|
|
895
|
-
if (
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1404
|
+
if (args.length === 2) {
|
|
1405
|
+
[column, value] = args
|
|
1406
|
+
operator = '='
|
|
1407
|
+
} else if (args.length === 3) {
|
|
1408
|
+
[column, operator, value] = args
|
|
1409
|
+
} else {
|
|
1410
|
+
throw new Error("Invalid number of arguments")
|
|
901
1411
|
}
|
|
902
1412
|
|
|
903
|
-
|
|
904
|
-
query = query.where('password', '=', criteria.password)
|
|
905
|
-
|
|
906
|
-
if (criteria.created_at)
|
|
907
|
-
query = query.where('created_at', '=', criteria.created_at)
|
|
908
|
-
|
|
909
|
-
if (criteria.updated_at)
|
|
910
|
-
query = query.where('updated_at', '=', criteria.updated_at)
|
|
1413
|
+
instance.query = instance.query.where(column, operator, value)
|
|
911
1414
|
|
|
912
|
-
|
|
913
|
-
|
|
1415
|
+
return instance
|
|
1416
|
+
}
|
|
914
1417
|
|
|
915
|
-
|
|
916
|
-
if (options.sort)
|
|
917
|
-
query = query.orderBy(options.sort.column, options.sort.order)
|
|
1418
|
+
${whereStatements}
|
|
918
1419
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
query = query.limit(options.limit)
|
|
1420
|
+
static whereIn(column: keyof ${modelName}Type, values: any[]): ${modelName}Model {
|
|
1421
|
+
const instance = new this(null)
|
|
922
1422
|
|
|
923
|
-
|
|
924
|
-
query = query.offset(options.offset)
|
|
1423
|
+
instance.query = instance.query.where(column, 'in', values)
|
|
925
1424
|
|
|
926
|
-
return
|
|
1425
|
+
return instance
|
|
927
1426
|
}
|
|
928
1427
|
|
|
929
|
-
async
|
|
930
|
-
|
|
931
|
-
let query = db.selectFrom('${tableName}')
|
|
932
|
-
|
|
933
|
-
query = query.where(column, 'in', values)
|
|
1428
|
+
async first(): Promise<${modelName}Model | undefined> {
|
|
1429
|
+
const model = await this.query.selectAll().executeTakeFirst()
|
|
934
1430
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1431
|
+
if (! model) {
|
|
1432
|
+
return undefined
|
|
1433
|
+
}
|
|
938
1434
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
query = query.limit(options.limit)
|
|
1435
|
+
return this.parseResult(new ${modelName}Model(model))
|
|
1436
|
+
}
|
|
942
1437
|
|
|
943
|
-
|
|
944
|
-
|
|
1438
|
+
async exists(): Promise<boolean> {
|
|
1439
|
+
const model = await this.query.selectAll().executeTakeFirst()
|
|
945
1440
|
|
|
946
|
-
return
|
|
1441
|
+
return model !== null || model !== undefined
|
|
947
1442
|
}
|
|
948
1443
|
|
|
949
|
-
async first(): Promise<${modelName}Type> {
|
|
1444
|
+
static async first(): Promise<${modelName}Type | undefined> {
|
|
950
1445
|
return await db.selectFrom('${tableName}')
|
|
951
1446
|
.selectAll()
|
|
952
1447
|
.executeTakeFirst()
|
|
953
1448
|
}
|
|
954
1449
|
|
|
955
|
-
async last(): Promise<${modelName}Type> {
|
|
1450
|
+
async last(): Promise<${modelName}Type | undefined> {
|
|
956
1451
|
return await db.selectFrom('${tableName}')
|
|
957
1452
|
.selectAll()
|
|
958
1453
|
.orderBy('id', 'desc')
|
|
959
1454
|
.executeTakeFirst()
|
|
960
1455
|
}
|
|
961
1456
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1457
|
+
static orderBy(column: keyof ${modelName}Type, order: 'asc' | 'desc'): ${modelName}Model {
|
|
1458
|
+
const instance = new this(null)
|
|
1459
|
+
|
|
1460
|
+
instance.query = instance.orderBy(column, order)
|
|
1461
|
+
|
|
1462
|
+
return instance
|
|
967
1463
|
}
|
|
968
1464
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
.execute()
|
|
1465
|
+
orderBy(column: keyof ${modelName}Type, order: 'asc' | 'desc'): ${modelName}Model {
|
|
1466
|
+
this.query = this.query.orderBy(column, order)
|
|
1467
|
+
|
|
1468
|
+
return this
|
|
974
1469
|
}
|
|
975
1470
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1471
|
+
static orderByDesc(column: keyof ${modelName}Type): ${modelName}Model {
|
|
1472
|
+
const instance = new this(null)
|
|
1473
|
+
|
|
1474
|
+
instance.query = instance.query.orderBy(column, 'desc')
|
|
1475
|
+
|
|
1476
|
+
return instance
|
|
981
1477
|
}
|
|
982
1478
|
|
|
983
|
-
|
|
984
|
-
|
|
1479
|
+
orderByDesc(column: keyof ${modelName}Type): ${modelName}Model {
|
|
1480
|
+
this.query = this.orderBy(column, 'desc')
|
|
1481
|
+
|
|
985
1482
|
return this
|
|
986
1483
|
}
|
|
987
1484
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1485
|
+
static orderByAsc(column: keyof ${modelName}Type): ${modelName}Model {
|
|
1486
|
+
const instance = new this(null)
|
|
1487
|
+
|
|
1488
|
+
instance.query = instance.query.orderBy(column, 'desc')
|
|
1489
|
+
|
|
1490
|
+
return instance
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
orderByAsc(column: keyof ${modelName}Type): ${modelName}Model {
|
|
1494
|
+
this.query = this.query.orderBy(column, 'desc')
|
|
1495
|
+
|
|
1496
|
+
return this
|
|
991
1497
|
}
|
|
992
1498
|
|
|
993
|
-
// Method to update the ${
|
|
994
|
-
async update(${formattedModelName}: ${modelName}Update): Promise
|
|
995
|
-
if (this
|
|
996
|
-
|
|
1499
|
+
// Method to update the ${tableName} instance
|
|
1500
|
+
async update(${formattedModelName}: ${modelName}Update): Promise<${modelName}Model | undefined> {
|
|
1501
|
+
if (this.id === undefined)
|
|
1502
|
+
throw new Error('${modelName} ID is undefined')
|
|
1503
|
+
|
|
1504
|
+
const filteredValues = Object.keys(${formattedModelName})
|
|
1505
|
+
.filter(key => this.fillable.includes(key))
|
|
1506
|
+
.reduce((obj, key) => {
|
|
1507
|
+
obj[key] = ${formattedModelName}[key]
|
|
1508
|
+
return obj;
|
|
1509
|
+
}, {});
|
|
997
1510
|
|
|
998
|
-
|
|
999
|
-
.set(
|
|
1000
|
-
.where('id', '=', this
|
|
1511
|
+
await db.updateTable('${tableName}')
|
|
1512
|
+
.set(filteredValues)
|
|
1513
|
+
.where('id', '=', this.id)
|
|
1001
1514
|
.executeTakeFirst()
|
|
1002
1515
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1516
|
+
const model = this.find(Number(this.id))
|
|
1517
|
+
|
|
1518
|
+
${mittUpdateStatement}
|
|
1005
1519
|
|
|
1006
|
-
return
|
|
1520
|
+
return model
|
|
1007
1521
|
}
|
|
1008
1522
|
|
|
1009
1523
|
// Method to save (insert or update) the ${formattedModelName} instance
|
|
@@ -1023,38 +1537,66 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
1023
1537
|
}
|
|
1024
1538
|
}
|
|
1025
1539
|
|
|
1026
|
-
// Method to delete the ${formattedModelName} instance
|
|
1540
|
+
// Method to delete (soft delete) the ${formattedModelName} instance
|
|
1027
1541
|
async delete(): Promise<void> {
|
|
1028
|
-
|
|
1029
|
-
|
|
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
|
+
}
|
|
1030
1560
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
.execute()
|
|
1561
|
+
${mittDeleteStatement}
|
|
1562
|
+
}
|
|
1034
1563
|
|
|
1035
|
-
|
|
1564
|
+
${relationMethods}
|
|
1565
|
+
|
|
1566
|
+
distinct(column: keyof ${modelName}Type): ${modelName}Model {
|
|
1567
|
+
this.query = this.query.distinctOn(column)
|
|
1568
|
+
|
|
1569
|
+
return this
|
|
1036
1570
|
}
|
|
1037
1571
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
if (this.${formattedModelName}.id === undefined)
|
|
1041
|
-
throw new Error('${modelName} ID is undefined')
|
|
1572
|
+
static distinct(column: keyof ${modelName}Type): ${modelName}Model {
|
|
1573
|
+
const instance = new this(null)
|
|
1042
1574
|
|
|
1043
|
-
|
|
1044
|
-
.where('id', '=', this.${formattedModelName}.id)
|
|
1045
|
-
.selectAll()
|
|
1046
|
-
.executeTakeFirst()
|
|
1575
|
+
instance.query = instance.query.distinctOn(column)
|
|
1047
1576
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1577
|
+
return instance
|
|
1578
|
+
}
|
|
1050
1579
|
|
|
1051
|
-
|
|
1580
|
+
join(table: string, firstCol: string, secondCol: string): ${modelName}Model {
|
|
1581
|
+
this.query = this.query.innerJoin(table, firstCol, secondCol)
|
|
1582
|
+
|
|
1583
|
+
return this
|
|
1052
1584
|
}
|
|
1053
1585
|
|
|
1054
|
-
${
|
|
1586
|
+
static join(table: string, firstCol: string, secondCol: string): ${modelName}Model {
|
|
1587
|
+
const instance = new this(null)
|
|
1588
|
+
|
|
1589
|
+
instance.query = instance.query.innerJoin(table, firstCol, secondCol)
|
|
1590
|
+
|
|
1591
|
+
return instance
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
static async rawQuery(rawQuery: string): Promise<any> {
|
|
1595
|
+
return await sql\`\${rawQuery}\`\.execute(db)
|
|
1596
|
+
}
|
|
1055
1597
|
|
|
1056
1598
|
toJSON() {
|
|
1057
|
-
const output: Partial<${modelName}Type> = {
|
|
1599
|
+
const output: Partial<${modelName}Type> = ${jsonFields}
|
|
1058
1600
|
|
|
1059
1601
|
this.hidden.forEach((attr) => {
|
|
1060
1602
|
if (attr in output)
|
|
@@ -1065,259 +1607,64 @@ async function generateModelString(tableName: string, modelName: string, model:
|
|
|
1065
1607
|
|
|
1066
1608
|
return output as ${modelName}
|
|
1067
1609
|
}
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
const Model = ${modelName}Model
|
|
1071
|
-
|
|
1072
|
-
// starting here, ORM functions
|
|
1073
|
-
export async function find(id: number, fields?: (keyof ${modelName}Type)[]) {
|
|
1074
|
-
let query = db.selectFrom('${tableName}').where('id', '=', id)
|
|
1075
1610
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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']
|
|
1080
1617
|
|
|
1081
|
-
|
|
1618
|
+
for (const hiddenAttribute of this.hidden) {
|
|
1619
|
+
delete model[hiddenAttribute]
|
|
1620
|
+
}
|
|
1082
1621
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1622
|
+
return model
|
|
1623
|
+
}
|
|
1085
1624
|
|
|
1086
|
-
|
|
1625
|
+
${twoFactorStatements}
|
|
1087
1626
|
}
|
|
1088
1627
|
|
|
1089
|
-
|
|
1628
|
+
async function find(id: number): Promise<${modelName}Model | null> {
|
|
1090
1629
|
let query = db.selectFrom('${tableName}').where('id', '=', id)
|
|
1091
1630
|
|
|
1092
|
-
if (fields)
|
|
1093
|
-
|
|
1094
|
-
else
|
|
1095
|
-
query = query.selectAll()
|
|
1631
|
+
if (fields) query = query.select(fields)
|
|
1632
|
+
else query = query.selectAll()
|
|
1096
1633
|
|
|
1097
1634
|
const model = await query.executeTakeFirst()
|
|
1098
1635
|
|
|
1099
|
-
if (!model)
|
|
1100
|
-
throw(\`No model results found for \${id}\ \`)
|
|
1636
|
+
if (!model) return null
|
|
1101
1637
|
|
|
1102
1638
|
return new ${modelName}Model(model)
|
|
1103
1639
|
}
|
|
1104
1640
|
|
|
1105
|
-
export async function
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
if (fields)
|
|
1109
|
-
query = query.select(fields)
|
|
1110
|
-
else
|
|
1111
|
-
query = query.selectAll()
|
|
1112
|
-
|
|
1113
|
-
const model = await query.execute()
|
|
1641
|
+
export async function count(): Promise<number> {
|
|
1642
|
+
const results = await ${modelName}Model.count()
|
|
1114
1643
|
|
|
1115
|
-
return
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
export async function count(): Number {
|
|
1119
|
-
const results = await db.selectFrom('${tableName}')
|
|
1120
|
-
.selectAll()
|
|
1121
|
-
.execute()
|
|
1122
|
-
|
|
1123
|
-
return results.length
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
export async function get(criteria: Partial<${modelName}Type>, sort: { column: keyof ${modelName}Type, order: 'asc' | 'desc' } = { column: 'created_at', order: 'desc' }) {
|
|
1127
|
-
let query = db.selectFrom('${tableName}')
|
|
1128
|
-
|
|
1129
|
-
if (criteria.id)
|
|
1130
|
-
query = query.where('id', '=', criteria.id) // Kysely is immutable, we must re-assign
|
|
1131
|
-
|
|
1132
|
-
if (criteria.email)
|
|
1133
|
-
query = query.where('email', '=', criteria.email)
|
|
1134
|
-
|
|
1135
|
-
if (criteria.name !== undefined) {
|
|
1136
|
-
query = query.where(
|
|
1137
|
-
'name',
|
|
1138
|
-
criteria.name === null ? 'is' : '=',
|
|
1139
|
-
criteria.name,
|
|
1140
|
-
)
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
if (criteria.password)
|
|
1144
|
-
query = query.where('password', '=', criteria.password)
|
|
1145
|
-
|
|
1146
|
-
if (criteria.created_at)
|
|
1147
|
-
query = query.where('created_at', '=', criteria.created_at)
|
|
1148
|
-
|
|
1149
|
-
if (criteria.updated_at)
|
|
1150
|
-
query = query.where('updated_at', '=', criteria.updated_at)
|
|
1151
|
-
|
|
1152
|
-
if (criteria.deleted_at)
|
|
1153
|
-
query = query.where('deleted_at', '=', criteria.deleted_at)
|
|
1154
|
-
|
|
1155
|
-
// Apply sorting based on the 'sort' parameter
|
|
1156
|
-
query = query.orderBy(sort.column, sort.order)
|
|
1157
|
-
|
|
1158
|
-
return await query.selectAll().execute()
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
export async function all(limit: number = 10, offset: number = 0): Promise<${modelName}Type[]> {
|
|
1162
|
-
return await db.selectFrom('${tableName}')
|
|
1163
|
-
.selectAll()
|
|
1164
|
-
.orderBy('created_at', 'desc')
|
|
1165
|
-
.limit(limit)
|
|
1166
|
-
.offset(offset)
|
|
1167
|
-
.execute()
|
|
1644
|
+
return results
|
|
1168
1645
|
}
|
|
1169
1646
|
|
|
1170
1647
|
export async function create(new${modelName}: New${modelName}): Promise<${modelName}Model> {
|
|
1171
1648
|
const result = await db.insertInto('${tableName}')
|
|
1172
|
-
|
|
1173
|
-
|
|
1649
|
+
.values(new${modelName})
|
|
1650
|
+
.executeTakeFirstOrThrow()
|
|
1174
1651
|
|
|
1175
|
-
return await find(Number(result.insertId))
|
|
1652
|
+
return await find(Number(result.insertId)) as ${modelName}Model
|
|
1176
1653
|
}
|
|
1177
1654
|
|
|
1178
|
-
export async function
|
|
1179
|
-
|
|
1180
|
-
.selectAll()
|
|
1181
|
-
.executeTakeFirst()
|
|
1655
|
+
export async function rawQuery(rawQuery: string): Promise<any> {
|
|
1656
|
+
return await sql\`\${rawQuery}\`\.execute(db)
|
|
1182
1657
|
}
|
|
1183
1658
|
|
|
1184
|
-
export async function
|
|
1185
|
-
|
|
1186
|
-
.selectAll()
|
|
1187
|
-
.limit(limit)
|
|
1188
|
-
.execute()
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
export async function last(limit: number): Promise<${modelName}Type> {
|
|
1192
|
-
return await db.selectFrom('${tableName}')
|
|
1193
|
-
.selectAll()
|
|
1194
|
-
.orderBy('id', 'desc')
|
|
1195
|
-
.limit(limit)
|
|
1196
|
-
.execute()
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
export async function update(id: number, ${formattedModelName}Update: ${modelName}Update) {
|
|
1200
|
-
return await db.updateTable('${tableName}')
|
|
1201
|
-
.set(${formattedModelName}Update)
|
|
1659
|
+
export async function remove(id: number): Promise<void> {
|
|
1660
|
+
await db.deleteFrom('${tableName}')
|
|
1202
1661
|
.where('id', '=', id)
|
|
1203
1662
|
.execute()
|
|
1204
1663
|
}
|
|
1205
1664
|
|
|
1206
|
-
|
|
1207
|
-
return await db.deleteFrom('${tableName}')
|
|
1208
|
-
.where('id', '=', id)
|
|
1209
|
-
.executeTakeFirst()
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
export async function where(...args: (string | number)[]) {
|
|
1213
|
-
let column: any
|
|
1214
|
-
let operator: any
|
|
1215
|
-
let value: any
|
|
1216
|
-
|
|
1217
|
-
if (args.length === 2) {
|
|
1218
|
-
[column, value] = args
|
|
1219
|
-
operator = '='
|
|
1220
|
-
} else if (args.length === 3) {
|
|
1221
|
-
[column, operator, value] = args
|
|
1222
|
-
} else {
|
|
1223
|
-
throw new Error("Invalid number of arguments")
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
let query = db.selectFrom('${tableName}')
|
|
1227
|
-
|
|
1228
|
-
query = query.where(column, operator, value)
|
|
1229
|
-
|
|
1230
|
-
return await query.selectAll().execute()
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
export async function whereIs(
|
|
1234
|
-
criteria: Partial<${modelName}Type>,
|
|
1235
|
-
options: QueryOptions = {},
|
|
1236
|
-
) {
|
|
1237
|
-
let query = db.selectFrom('${tableName}')
|
|
1238
|
-
|
|
1239
|
-
// Apply criteria
|
|
1240
|
-
if (criteria.id)
|
|
1241
|
-
query = query.where('id', '=', criteria.id)
|
|
1242
|
-
|
|
1243
|
-
if (criteria.email)
|
|
1244
|
-
query = query.where('email', '=', criteria.email)
|
|
1245
|
-
|
|
1246
|
-
if (criteria.name !== undefined) {
|
|
1247
|
-
query = query.where(
|
|
1248
|
-
'name',
|
|
1249
|
-
criteria.name === null ? 'is' : '=',
|
|
1250
|
-
criteria.name,
|
|
1251
|
-
)
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
if (criteria.password)
|
|
1255
|
-
query = query.where('password', '=', criteria.password)
|
|
1256
|
-
|
|
1257
|
-
if (criteria.created_at)
|
|
1258
|
-
query = query.where('created_at', '=', criteria.created_at)
|
|
1259
|
-
|
|
1260
|
-
if (criteria.updated_at)
|
|
1261
|
-
query = query.where('updated_at', '=', criteria.updated_at)
|
|
1262
|
-
|
|
1263
|
-
if (criteria.deleted_at)
|
|
1264
|
-
query = query.where('deleted_at', '=', criteria.deleted_at)
|
|
1265
|
-
|
|
1266
|
-
// Apply sorting from options
|
|
1267
|
-
if (options.sort)
|
|
1268
|
-
query = query.orderBy(options.sort.column, options.sort.order)
|
|
1665
|
+
${whereFunctionStatements}
|
|
1269
1666
|
|
|
1270
|
-
|
|
1271
|
-
if (options.limit !== undefined)
|
|
1272
|
-
query = query.limit(options.limit)
|
|
1273
|
-
|
|
1274
|
-
if (options.offset !== undefined)
|
|
1275
|
-
query = query.offset(options.offset)
|
|
1276
|
-
|
|
1277
|
-
return await query.selectAll().execute()
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
export async function whereIn(
|
|
1281
|
-
column: keyof ${modelName}Type,
|
|
1282
|
-
values: any[],
|
|
1283
|
-
options: QueryOptions = {},
|
|
1284
|
-
) {
|
|
1285
|
-
let query = db.selectFrom('${tableName}')
|
|
1286
|
-
|
|
1287
|
-
query = query.where(column, 'in', values)
|
|
1288
|
-
|
|
1289
|
-
// Apply sorting from options
|
|
1290
|
-
if (options.sort)
|
|
1291
|
-
query = query.orderBy(options.sort.column, options.sort.order)
|
|
1292
|
-
|
|
1293
|
-
// Apply pagination from options
|
|
1294
|
-
if (options.limit !== undefined)
|
|
1295
|
-
query = query.limit(options.limit)
|
|
1296
|
-
|
|
1297
|
-
if (options.offset !== undefined)
|
|
1298
|
-
query = query.offset(options.offset)
|
|
1299
|
-
|
|
1300
|
-
return await query.selectAll().execute()
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
export const ${modelName} = {
|
|
1304
|
-
find,
|
|
1305
|
-
findOrFail,
|
|
1306
|
-
findMany,
|
|
1307
|
-
get,
|
|
1308
|
-
count,
|
|
1309
|
-
all,
|
|
1310
|
-
create,
|
|
1311
|
-
update,
|
|
1312
|
-
remove,
|
|
1313
|
-
Model,
|
|
1314
|
-
first,
|
|
1315
|
-
last,
|
|
1316
|
-
recent,
|
|
1317
|
-
where,
|
|
1318
|
-
whereIn,
|
|
1319
|
-
model: ${modelName}Model
|
|
1320
|
-
}
|
|
1667
|
+
const ${modelName} = ${modelName}Model
|
|
1321
1668
|
|
|
1322
1669
|
export default ${modelName}
|
|
1323
1670
|
`
|