@stacksjs/actions 0.59.10 → 0.61.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 +29 -0
- package/dist/build/component-libs.js +9 -0
- package/dist/build/core.js +21 -0
- package/dist/build/stacks.js +8 -0
- package/dist/build.js +317 -0
- package/dist/bump.js +16 -0
- package/dist/changelog.js +17 -0
- package/dist/clean.js +6 -0
- package/dist/commit.js +17 -0
- package/dist/copy-types.js +14 -0
- package/dist/database/seed.js +8 -0
- package/dist/deploy/index.js +31 -0
- package/dist/dev/components.js +11 -0
- package/dist/dev/docs.js +7 -0
- package/dist/dev/index.js +112 -0
- package/dist/examples.js +43 -0
- package/dist/fresh.js +19 -0
- package/dist/generate/component-meta.js +59 -0
- package/dist/generate/ide-helpers.js +96 -0
- package/dist/generate/index.js +251 -0
- package/dist/generate/lib-entries.js +175 -0
- package/dist/generate/vscode-custom-data.js +96 -0
- package/dist/helpers/component-meta.js +50 -0
- package/dist/helpers/lib-entries.js +166 -0
- package/dist/helpers/package-json.js +146 -0
- package/dist/helpers/utils.js +67 -0
- package/dist/helpers/vscode-custom-data.js +88 -0
- package/dist/index.js +676 -0
- package/dist/key-generate.js +10 -0
- package/dist/lint/fix.js +12 -0
- package/dist/lint/index.js +16 -0
- package/dist/make.js +319 -0
- package/dist/prepublish.js +17 -0
- package/dist/release.js +646 -0
- package/dist/test/coverage.js +9 -0
- package/dist/test/feature.js +9 -0
- package/dist/test/ui.js +6 -0
- package/dist/test/unit.js +6 -0
- package/dist/test.js +6 -0
- package/dist/typecheck.js +6 -0
- package/dist/types.js +21 -0
- package/dist/upgrade/dependencies.js +6 -0
- package/dist/upgrade/framework.js +1 -0
- package/dist/upgrade/index.js +78 -0
- package/dist/upgrade.js +46 -0
- package/package.json +5 -5
- package/src/action.ts +1 -0
- package/src/actions.ts +2 -2
- package/src/add.ts +3 -6
- package/src/ai/request-model-access.ts +10 -7
- package/src/build/component-libs.ts +5 -2
- package/src/build/core.ts +1 -2
- package/src/build/stacks.ts +8 -5
- package/src/build.ts +11 -25
- package/src/bump.ts +0 -2
- package/src/check/ports.ts +2 -3
- package/src/commit.ts +2 -2
- package/src/copy-types.ts +1 -1
- package/src/database/seed.ts +8 -4
- package/src/deploy/create-receipt-rule.ts +3 -7
- package/src/deploy/index.ts +2 -1
- package/src/dev/api.ts +4 -4
- package/src/dev/components.ts +3 -5
- package/src/dev/index.ts +2 -2
- package/src/domains/add.ts +8 -6
- package/src/domains/purchase.ts +26 -24
- package/src/domains/remove.ts +5 -7
- package/src/examples.ts +9 -13
- package/src/find-projects.ts +4 -6
- package/src/fresh.ts +2 -1
- package/src/generate/action-types.ts +5 -7
- package/src/generate/component-meta.ts +2 -5
- package/src/generate/custom-cli-file.ts +4 -3
- package/src/generate/env-files.ts +38 -43
- package/src/generate/ide-helpers.ts +1 -2
- package/src/generate/index.ts +16 -40
- package/src/generate/lib-entries.ts +4 -7
- package/src/generate/pkgx-file.ts +6 -11
- package/src/generate/vscode-custom-data.ts +1 -3
- package/src/helpers/component-meta.ts +6 -11
- package/src/helpers/index.ts +5 -0
- package/src/helpers/lib-entries.ts +30 -25
- package/src/helpers/package-json.ts +13 -16
- package/src/helpers/utils.ts +56 -13
- package/src/helpers/vscode-custom-data.ts +1 -2
- package/src/index.ts +2 -1
- package/src/key-generate.ts +3 -4
- package/src/lint/fix.ts +10 -2
- package/src/lint/index.ts +6 -10
- package/src/make.ts +79 -77
- package/src/migrate/database.ts +22 -1
- package/src/migrate/fresh.ts +20 -0
- package/src/orm/base.ts +0 -0
- package/src/orm/generate-model.ts +1177 -0
- package/src/{generate/model-classes.ts → orm/user.ts} +33 -48
- package/src/prepublish.ts +5 -2
- package/src/release.ts +15 -11
- package/src/test/coverage.ts +5 -2
- package/src/test/feature.ts +5 -2
- package/src/test/ui.ts +1 -1
- package/src/test/unit.ts +1 -1
- package/src/test.ts +1 -1
- package/src/typecheck.ts +1 -1
- package/src/types.ts +2 -2
- package/src/upgrade/binary.ts +3 -5
- package/src/upgrade/index.ts +4 -8
- package/src/upgrade/shell.ts +10 -10
- package/src/upgrade.ts +28 -25
- package/dist/action.d.ts +0 -36
- package/dist/add.d.ts +0 -6
- package/dist/build/component-libs.d.ts +0 -1
- package/dist/build/core.d.ts +0 -1
- package/dist/build/stacks.d.ts +0 -1
- package/dist/build.d.ts +0 -9
- package/dist/bump.d.ts +0 -1
- package/dist/changelog.d.ts +0 -1
- package/dist/clean.d.ts +0 -1
- package/dist/commit.d.ts +0 -3
- package/dist/copy-types.d.ts +0 -5
- package/dist/database/seed.d.ts +0 -1
- package/dist/deploy/index.d.ts +0 -1
- package/dist/dev/components.d.ts +0 -1
- package/dist/dev/docs.d.ts +0 -1
- package/dist/dev/index.d.ts +0 -10
- package/dist/examples.d.ts +0 -5
- package/dist/fresh.d.ts +0 -1
- package/dist/generate/component-meta.d.ts +0 -5
- package/dist/generate/ide-helpers.d.ts +0 -6
- package/dist/generate/index.d.ts +0 -13
- package/dist/generate/lib-entries.d.ts +0 -2
- package/dist/generate/vscode-custom-data.d.ts +0 -6
- package/dist/helpers/component-meta.d.ts +0 -22
- package/dist/helpers/lib-entries.d.ts +0 -13
- package/dist/helpers/package-json.d.ts +0 -3
- package/dist/helpers/utils.d.ts +0 -18
- package/dist/helpers/vscode-custom-data.d.ts +0 -2
- package/dist/index.d.ts +0 -7
- package/dist/key-generate.d.ts +0 -1
- package/dist/lint/fix.d.ts +0 -1
- package/dist/lint/index.d.ts +0 -1
- package/dist/make.d.ts +0 -20
- package/dist/prepublish.d.ts +0 -1
- package/dist/release.d.ts +0 -2
- package/dist/test/coverage.d.ts +0 -1
- package/dist/test/feature.d.ts +0 -1
- package/dist/test/ui.d.ts +0 -1
- package/dist/test/unit.d.ts +0 -1
- package/dist/test.d.ts +0 -1
- package/dist/tinker.d.ts +0 -1
- package/dist/typecheck.d.ts +0 -1
- package/dist/types.d.ts +0 -3
- package/dist/upgrade/dependencies.d.ts +0 -1
- package/dist/upgrade/framework.d.ts +0 -1
- package/dist/upgrade/index.d.ts +0 -1
- package/dist/upgrade.d.ts +0 -4
- package/src/database/fields.ts +0 -111
- package/src/database/migration-mysql.ts +0 -72
- package/src/database/migration-sqlite.ts +0 -67
- package/src/database/migration.ts +0 -10
- package/src/database/user-migration.ts +0 -20
- package/src/generate/model-generate.ts +0 -12
- package/src/migrate/dns.ts +0 -3
- package/src/tinker.ts +0 -10
|
@@ -0,0 +1,1177 @@
|
|
|
1
|
+
import { log } from '@stacksjs/logging'
|
|
2
|
+
import { modelTableName } from '@stacksjs/orm'
|
|
3
|
+
import { path } from '@stacksjs/path'
|
|
4
|
+
import { fs, glob } from '@stacksjs/storage'
|
|
5
|
+
import { pascalCase } from '@stacksjs/strings'
|
|
6
|
+
import type { Model, RelationConfig } from '@stacksjs/types'
|
|
7
|
+
import { isString } from '@stacksjs/validation'
|
|
8
|
+
|
|
9
|
+
export interface FieldArrayElement {
|
|
10
|
+
entity: string
|
|
11
|
+
charValue?: string | null
|
|
12
|
+
// Add other properties as needed
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ModelElement {
|
|
16
|
+
field: string
|
|
17
|
+
default: string | number | boolean | Date | undefined | null
|
|
18
|
+
unique: boolean
|
|
19
|
+
fieldArray: FieldArrayElement | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
await initiateModelGeneration()
|
|
23
|
+
await setKyselyTypes()
|
|
24
|
+
|
|
25
|
+
async function generateApiRoutes(model: Model) {
|
|
26
|
+
if (model.traits?.useApi) {
|
|
27
|
+
let routeString = `import { route } from '@stacksjs/router'\n\n\n`
|
|
28
|
+
const apiRoutes = model.traits?.useApi?.routes
|
|
29
|
+
|
|
30
|
+
if (apiRoutes?.length) {
|
|
31
|
+
for (const apiRoute of apiRoutes) {
|
|
32
|
+
await writeOrmActions(apiRoute, model)
|
|
33
|
+
routeString += await writeApiRoutes(apiRoute, model)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const file = Bun.file(path.projectStoragePath(`framework/orm/routes.ts`))
|
|
38
|
+
const writer = file.writer()
|
|
39
|
+
writer.write(routeString)
|
|
40
|
+
await writer.end()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function writeModelNames() {
|
|
45
|
+
const modelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
46
|
+
|
|
47
|
+
let fileString = `export type ModelNames = `
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < modelFiles.length; i++) {
|
|
50
|
+
const modeFileElement = modelFiles[i] as string
|
|
51
|
+
|
|
52
|
+
const model = (await import(modeFileElement)).default as Model
|
|
53
|
+
|
|
54
|
+
const typeFile = Bun.file(path.projectStoragePath(`framework/core/types/src/model-names.ts`))
|
|
55
|
+
|
|
56
|
+
fileString += `'${model.name}'`
|
|
57
|
+
|
|
58
|
+
if (i < modelFiles.length - 1) {
|
|
59
|
+
fileString += ' | '
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const writer = typeFile.writer()
|
|
63
|
+
|
|
64
|
+
writer.write(fileString)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function writeOrmActions(apiRoute: string, model: Model): Promise<void> {
|
|
69
|
+
const modelName = model.name
|
|
70
|
+
const formattedApiRoute = apiRoute.charAt(0).toUpperCase() + apiRoute.slice(1)
|
|
71
|
+
|
|
72
|
+
let actionString = `import { Action } from '@stacksjs/actions'\n`
|
|
73
|
+
actionString += `import ${modelName} from '../src/${modelName}'\n\n`
|
|
74
|
+
actionString += `import { request } from '@stacksjs/router'\n\n`
|
|
75
|
+
|
|
76
|
+
let handleString = ``
|
|
77
|
+
|
|
78
|
+
if (apiRoute === 'index') {
|
|
79
|
+
handleString += `handle() {
|
|
80
|
+
return ${modelName}.all()
|
|
81
|
+
},`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (apiRoute === 'show') {
|
|
85
|
+
handleString += `handle() {
|
|
86
|
+
return ${modelName}.find(1)
|
|
87
|
+
},`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (apiRoute === 'destroy') {
|
|
91
|
+
handleString += `handle() {
|
|
92
|
+
const model = ${modelName}.find(1)
|
|
93
|
+
|
|
94
|
+
model.delete()
|
|
95
|
+
|
|
96
|
+
return 'Model deleted!'
|
|
97
|
+
},`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (apiRoute === 'store') {
|
|
101
|
+
handleString += `handle() {
|
|
102
|
+
const model = ${modelName}.create(request.all())
|
|
103
|
+
|
|
104
|
+
return model
|
|
105
|
+
},`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (apiRoute === 'update') {
|
|
109
|
+
handleString += `handle() {
|
|
110
|
+
const id = request.get(req.params.id)
|
|
111
|
+
|
|
112
|
+
const model = ${modelName}.find(req.params.id)
|
|
113
|
+
|
|
114
|
+
return model.update(req.all())
|
|
115
|
+
},`
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
actionString += `export default new Action({
|
|
119
|
+
name: '${modelName} ${formattedApiRoute}',
|
|
120
|
+
description: '${modelName} ${formattedApiRoute} ORM Action',
|
|
121
|
+
|
|
122
|
+
${handleString}
|
|
123
|
+
})
|
|
124
|
+
`
|
|
125
|
+
|
|
126
|
+
const file = Bun.file(path.projectStoragePath(`framework/orm/Actions/${modelName}${formattedApiRoute}OrmAction.ts`))
|
|
127
|
+
|
|
128
|
+
const writer = file.writer()
|
|
129
|
+
|
|
130
|
+
writer.write(actionString)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function writeApiRoutes(apiRoute: string, model: Model): Promise<string> {
|
|
134
|
+
let routeString = ``
|
|
135
|
+
const tableName = await modelTableName(model)
|
|
136
|
+
const modelName = model.name
|
|
137
|
+
|
|
138
|
+
if (apiRoute === 'index') routeString += `await route.get('${tableName}', 'Actions/${modelName}IndexOrmAction')\n\n`
|
|
139
|
+
|
|
140
|
+
if (apiRoute === 'store') routeString += `await route.post('${tableName}', 'Actions/${modelName}StoreOrmAction')\n\n`
|
|
141
|
+
|
|
142
|
+
if (apiRoute === 'update')
|
|
143
|
+
routeString += `await route.patch('${tableName}/{id}', 'Actions/${modelName}UpdateOrmAction')\n\n`
|
|
144
|
+
|
|
145
|
+
if (apiRoute === 'show')
|
|
146
|
+
routeString += `await route.get('${tableName}/{id}', 'Actions/${modelName}ShowOrmAction')\n\n`
|
|
147
|
+
|
|
148
|
+
if (apiRoute === 'destroy')
|
|
149
|
+
routeString += `await route.delete('${tableName}/{id}', 'Actions/${modelName}DestroyOrmAction')\n\n`
|
|
150
|
+
|
|
151
|
+
return routeString
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function initiateModelGeneration(): Promise<void> {
|
|
155
|
+
await deleteExistingModels()
|
|
156
|
+
await deleteExistingOrmActions()
|
|
157
|
+
await deleteExistingModelNameTypes()
|
|
158
|
+
await writeModelNames()
|
|
159
|
+
|
|
160
|
+
const modelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
161
|
+
|
|
162
|
+
for (const modelFile of modelFiles) {
|
|
163
|
+
log.debug(`Processing model file: ${modelFile}`)
|
|
164
|
+
|
|
165
|
+
const model = (await import(modelFile)).default as Model
|
|
166
|
+
const tableName = await modelTableName(model)
|
|
167
|
+
const modelName = path.basename(modelFile, '.ts')
|
|
168
|
+
|
|
169
|
+
await generateApiRoutes(model)
|
|
170
|
+
|
|
171
|
+
const file = Bun.file(path.projectStoragePath(`framework/orm/src/models/${modelName}.ts`))
|
|
172
|
+
const fields = await extractFields(model, modelFile)
|
|
173
|
+
const classString = await generateModelString(tableName, model, fields)
|
|
174
|
+
|
|
175
|
+
const writer = file.writer()
|
|
176
|
+
writer.write(classString)
|
|
177
|
+
await writer.end()
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function getRelations(model: Model): Promise<RelationConfig[]> {
|
|
182
|
+
const relationsArray = ['hasOne', 'belongsTo', 'hasMany', 'belongsToMany', 'hasOneThrough']
|
|
183
|
+
|
|
184
|
+
const relationships = []
|
|
185
|
+
|
|
186
|
+
for (const relation of relationsArray) {
|
|
187
|
+
if (hasRelations(model, relation)) {
|
|
188
|
+
for (const relationInstance of model[relation]) {
|
|
189
|
+
let relationModel = relationInstance.model
|
|
190
|
+
|
|
191
|
+
if (isString(relationInstance)) {
|
|
192
|
+
relationModel = relationInstance
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const modelRelationPath = path.userModelsPath(`${relationModel}.ts`)
|
|
196
|
+
|
|
197
|
+
const modelRelation = (await import(modelRelationPath)).default as Model
|
|
198
|
+
|
|
199
|
+
const formattedModelName = model.name?.toLowerCase()
|
|
200
|
+
|
|
201
|
+
relationships.push({
|
|
202
|
+
relationship: relation,
|
|
203
|
+
model: relationModel,
|
|
204
|
+
table: modelRelation.table,
|
|
205
|
+
foreignKey: relationInstance.foreignKey || `${formattedModelName}_id`,
|
|
206
|
+
relationName: relationInstance.relationName || '',
|
|
207
|
+
throughModel: relationInstance.through || '',
|
|
208
|
+
throughForeignKey: relationInstance.throughForeignKey || '',
|
|
209
|
+
pivotTable: relationInstance?.pivotTable || `${formattedModelName}_${modelRelation.table}`,
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return relationships
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function hasRelations(obj: any, key: string): boolean {
|
|
219
|
+
return key in obj
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function deleteExistingModels() {
|
|
223
|
+
const modelPaths = glob.sync(path.projectStoragePath(`framework/orm/src/models*.ts`))
|
|
224
|
+
|
|
225
|
+
for (const modelPath of modelPaths) {
|
|
226
|
+
if (fs.existsSync(modelPath)) await Bun.$`rm ${modelPath}`
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const typePath = path.projectStoragePath(`framework/core/orm/src/generated/types.ts`)
|
|
230
|
+
|
|
231
|
+
if (fs.existsSync(typePath)) await Bun.$`rm ${typePath}`
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function deleteExistingOrmActions() {
|
|
235
|
+
const ormPaths = glob.sync(path.projectStoragePath(`framework/orm/Actions/*.ts`))
|
|
236
|
+
|
|
237
|
+
for (const ormPath of ormPaths) {
|
|
238
|
+
if (fs.existsSync(ormPath)) await Bun.$`rm ${ormPath}`
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function deleteExistingModelNameTypes() {
|
|
243
|
+
const typeFile = path.projectStoragePath(`framework/core/types/src/model-names.ts`)
|
|
244
|
+
|
|
245
|
+
if (fs.existsSync(typeFile)) await Bun.$`rm ${typeFile}`
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function setKyselyTypes() {
|
|
249
|
+
let text = ``
|
|
250
|
+
const modelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
251
|
+
|
|
252
|
+
for (const modelFile of modelFiles) {
|
|
253
|
+
const model = (await import(modelFile)).default as Model
|
|
254
|
+
const tableName = await modelTableName(model)
|
|
255
|
+
const modelName = model.name
|
|
256
|
+
|
|
257
|
+
const words = tableName.split('_')
|
|
258
|
+
|
|
259
|
+
const pivotFormatted = `${words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join('')}`
|
|
260
|
+
|
|
261
|
+
text += `import type { ${pivotFormatted}Table } from '../../../../orm/src/models/${modelName}'\n`
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
text += `import type { Generated } from 'kysely'\n\n`
|
|
265
|
+
|
|
266
|
+
let pivotFormatted = ''
|
|
267
|
+
for (const modelFile of modelFiles) {
|
|
268
|
+
const model = (await import(modelFile)).default as Model
|
|
269
|
+
const pivotTables = await getPivotTables(model)
|
|
270
|
+
|
|
271
|
+
for (const pivotTable of pivotTables) {
|
|
272
|
+
const words = pivotTable.table.split('_')
|
|
273
|
+
|
|
274
|
+
pivotFormatted = `${words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join('')}Table`
|
|
275
|
+
|
|
276
|
+
text += `export interface ${pivotFormatted} {
|
|
277
|
+
id: Generated<number>
|
|
278
|
+
${pivotTable.firstForeignKey}: number
|
|
279
|
+
${pivotTable.secondForeignKey}: number
|
|
280
|
+
}`
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
text += `\nexport interface Database {\n`
|
|
285
|
+
|
|
286
|
+
for (const modelFile of modelFiles) {
|
|
287
|
+
const model = (await import(modelFile)).default as Model
|
|
288
|
+
const tableName = await modelTableName(model)
|
|
289
|
+
const pivotTables = await getPivotTables(model)
|
|
290
|
+
|
|
291
|
+
for (const pivotTable of pivotTables) text += ` ${pivotTable.table}: ${pivotFormatted}\n`
|
|
292
|
+
|
|
293
|
+
const words = tableName.split('_')
|
|
294
|
+
|
|
295
|
+
const formattedTableName = `${words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join('')}Table`
|
|
296
|
+
|
|
297
|
+
text += ` ${tableName}: ${formattedTableName}\n`
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
text += `}`
|
|
301
|
+
|
|
302
|
+
const file = Bun.file(path.projectStoragePath(`framework/core/orm/src/generated/types.ts`))
|
|
303
|
+
|
|
304
|
+
const writer = file.writer()
|
|
305
|
+
|
|
306
|
+
writer.write(text)
|
|
307
|
+
|
|
308
|
+
await writer.end()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function extractFields(model: Model, modelFile: string): Promise<ModelElement[]> {
|
|
312
|
+
// TODO: we can improve this type
|
|
313
|
+
const fields: Record<string, any> = model.attributes
|
|
314
|
+
const fieldKeys = Object.keys(fields)
|
|
315
|
+
|
|
316
|
+
const rules: string[] = []
|
|
317
|
+
|
|
318
|
+
const file = Bun.file(modelFile) // Assuming Bun is imported properly
|
|
319
|
+
|
|
320
|
+
const code = await file.text()
|
|
321
|
+
|
|
322
|
+
const regex = /rule:.*$/gm
|
|
323
|
+
|
|
324
|
+
let match: RegExpExecArray | null
|
|
325
|
+
match = regex.exec(code)
|
|
326
|
+
|
|
327
|
+
while (match !== null) {
|
|
328
|
+
rules.push(match[0])
|
|
329
|
+
match = regex.exec(code)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const input: ModelElement[] = fieldKeys.map((field, index) => {
|
|
333
|
+
const fieldExist = fields[field]
|
|
334
|
+
let defaultValue = null
|
|
335
|
+
let uniqueValue = false
|
|
336
|
+
|
|
337
|
+
if (fieldExist) {
|
|
338
|
+
defaultValue = fieldExist || null
|
|
339
|
+
uniqueValue = fieldExist.unique || false
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
field,
|
|
344
|
+
default: defaultValue,
|
|
345
|
+
unique: uniqueValue,
|
|
346
|
+
fieldArray: parseRule(rules[index] ?? ''),
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
return input
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function parseRule(rule: string): FieldArrayElement | null {
|
|
354
|
+
const parts = rule.split('rule: schema.')
|
|
355
|
+
|
|
356
|
+
if (parts.length !== 2) return null
|
|
357
|
+
|
|
358
|
+
if (!parts[1]) parts[1] = ''
|
|
359
|
+
|
|
360
|
+
const extractedString = parts[1].replace(/,/g, '')
|
|
361
|
+
|
|
362
|
+
if (!extractedString) return null
|
|
363
|
+
|
|
364
|
+
const extractedParts = extractedString.split('.')
|
|
365
|
+
const regex = /\(([^)]+)\)/
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
extractedParts.map((input) => {
|
|
369
|
+
const match = regex.exec(input)
|
|
370
|
+
const value = match ? match[1] : null
|
|
371
|
+
const field = input.replace(regex, '').replace(/\(|\)/g, '')
|
|
372
|
+
return { entity: field, charValue: value }
|
|
373
|
+
})[0] || null
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function getRelationType(relation: string): string {
|
|
378
|
+
const belongToType = /belongs/
|
|
379
|
+
const hasType = /has/
|
|
380
|
+
const throughType = /Through/
|
|
381
|
+
|
|
382
|
+
if (throughType.test(relation)) return 'throughType'
|
|
383
|
+
|
|
384
|
+
if (belongToType.test(relation)) return 'belongsType'
|
|
385
|
+
|
|
386
|
+
if (hasType.test(relation)) return 'hasType'
|
|
387
|
+
|
|
388
|
+
return ''
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function getRelationCount(relation: string): string {
|
|
392
|
+
const singular = /One/
|
|
393
|
+
const plural = /Many/
|
|
394
|
+
|
|
395
|
+
if (plural.test(relation)) return 'many'
|
|
396
|
+
|
|
397
|
+
if (singular.test(relation)) return 'one'
|
|
398
|
+
|
|
399
|
+
return ''
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function getPivotTables(
|
|
403
|
+
model: Model,
|
|
404
|
+
): Promise<{ table: string; firstForeignKey?: string; secondForeignKey?: string }[]> {
|
|
405
|
+
const pivotTable = []
|
|
406
|
+
|
|
407
|
+
if ('belongsToMany' in model) {
|
|
408
|
+
const belongsToManyArr = model.belongsToMany || []
|
|
409
|
+
for (const belongsToManyRelation of belongsToManyArr) {
|
|
410
|
+
const modelRelationPath = path.userModelsPath(`${belongsToManyRelation}.ts`)
|
|
411
|
+
const modelRelation = (await import(modelRelationPath)).default as Model
|
|
412
|
+
const formattedModelName = model?.name?.toLowerCase()
|
|
413
|
+
|
|
414
|
+
const firstForeignKey =
|
|
415
|
+
belongsToManyRelation.firstForeignKey || `${model.name?.toLowerCase()}_${model.primaryKey}`
|
|
416
|
+
const secondForeignKey =
|
|
417
|
+
belongsToManyRelation.secondForeignKey || `${modelRelation.name?.toLowerCase()}_${model.primaryKey}`
|
|
418
|
+
|
|
419
|
+
pivotTable.push({
|
|
420
|
+
table: belongsToManyRelation?.pivotTable || `${formattedModelName}_${modelRelation.table}`,
|
|
421
|
+
firstForeignKey: firstForeignKey,
|
|
422
|
+
secondForeignKey: secondForeignKey,
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return pivotTable
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return []
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export async function fetchOtherModelRelations(model: Model): Promise<RelationConfig[]> {
|
|
433
|
+
const modelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
434
|
+
const modelRelations = []
|
|
435
|
+
|
|
436
|
+
for (let i = 0; i < modelFiles.length; i++) {
|
|
437
|
+
const modelFileElement = modelFiles[i] as string
|
|
438
|
+
const modelFile = await import(modelFileElement)
|
|
439
|
+
|
|
440
|
+
if (model.name === modelFile.default.name) continue
|
|
441
|
+
|
|
442
|
+
const relations = await getRelations(modelFile.default)
|
|
443
|
+
|
|
444
|
+
if (!relations.length) continue
|
|
445
|
+
|
|
446
|
+
const relation = relations.find((relation) => relation.model === model.name)
|
|
447
|
+
|
|
448
|
+
if (relation) modelRelations.push(relation)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return modelRelations
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function generateModelString(tableName: string, model: Model, attributes: ModelElement[]): Promise<string> {
|
|
455
|
+
const modelName = model.name
|
|
456
|
+
const formattedTableName = pascalCase(tableName) // users -> Users
|
|
457
|
+
const formattedModelName = modelName?.toLowerCase() // User -> user
|
|
458
|
+
|
|
459
|
+
let fieldString = ''
|
|
460
|
+
let relationMethods = ``
|
|
461
|
+
let relationImports = ``
|
|
462
|
+
|
|
463
|
+
const relations = await getRelations(model)
|
|
464
|
+
|
|
465
|
+
for (const relationInstance of relations) {
|
|
466
|
+
relationImports += `import ${relationInstance.model} from './${relationInstance.model}'\n\n`
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
for (const relation of relations) {
|
|
470
|
+
const modelRelation = relation.model
|
|
471
|
+
const foreignKeyRelation = relation.foreignKey
|
|
472
|
+
const tableRelation = relation.table || ''
|
|
473
|
+
const pivotTableRelation = relation.pivotTable
|
|
474
|
+
const formattedModelRelation = modelRelation.toLowerCase()
|
|
475
|
+
const capitalizeTableRelation = tableRelation.charAt(0).toUpperCase() + tableRelation.slice(1)
|
|
476
|
+
|
|
477
|
+
const relationType = getRelationType(relation.relationship)
|
|
478
|
+
const relationCount = getRelationCount(relation.relationship)
|
|
479
|
+
|
|
480
|
+
if (relationType === 'throughType') {
|
|
481
|
+
const relationName = relation.relationName || formattedModelName + modelRelation
|
|
482
|
+
const throughRelation = relation.throughModel
|
|
483
|
+
|
|
484
|
+
const formattedThroughRelation = relation.throughModel.toLowerCase()
|
|
485
|
+
const throughTableRelation = throughRelation
|
|
486
|
+
const foreignKeyThroughRelation = relation.throughForeignKey || `${formattedThroughRelation}_id`
|
|
487
|
+
|
|
488
|
+
relationMethods += `
|
|
489
|
+
async ${relationName}() {
|
|
490
|
+
if (this.${formattedModelName}.id === undefined)
|
|
491
|
+
throw new Error('Relation Error!')
|
|
492
|
+
|
|
493
|
+
const firstModel = await db.selectFrom('${throughTableRelation}')
|
|
494
|
+
.where('${foreignKeyRelation}', '=', this.${formattedModelName}.id)
|
|
495
|
+
.selectAll()
|
|
496
|
+
.executeTakeFirst()
|
|
497
|
+
|
|
498
|
+
if (! firstModel)
|
|
499
|
+
throw new Error('Model Relation Not Found!')
|
|
500
|
+
|
|
501
|
+
const finalModel = await db.selectFrom('${tableRelation}')
|
|
502
|
+
.where('${foreignKeyThroughRelation}', '=', firstModel.id)
|
|
503
|
+
.selectAll()
|
|
504
|
+
.executeTakeFirst()
|
|
505
|
+
|
|
506
|
+
return new ${modelRelation}.modelInstance(finalModel)
|
|
507
|
+
}\n\n`
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (relationType === 'hasType' && relationCount === 'many') {
|
|
511
|
+
const relationName = relation.relationName || tableRelation
|
|
512
|
+
|
|
513
|
+
relationMethods += `
|
|
514
|
+
async ${relationName}() {
|
|
515
|
+
if (this.${formattedModelName}.id === undefined)
|
|
516
|
+
throw new Error('Relation Error!')
|
|
517
|
+
|
|
518
|
+
const results = await db.selectFrom('${tableRelation}')
|
|
519
|
+
.where('${foreignKeyRelation}', '=', this.${formattedModelName}.id)
|
|
520
|
+
.selectAll()
|
|
521
|
+
.execute()
|
|
522
|
+
|
|
523
|
+
return results
|
|
524
|
+
}\n\n`
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (relationType === 'hasType' && relationCount === 'one') {
|
|
528
|
+
const relationName = relation.relationName || formattedModelRelation
|
|
529
|
+
relationMethods += `
|
|
530
|
+
async ${relationName}() {
|
|
531
|
+
if (this.${formattedModelName}.id === undefined)
|
|
532
|
+
throw new Error('Relation Error!')
|
|
533
|
+
|
|
534
|
+
const model = await db.selectFrom('${tableRelation}')
|
|
535
|
+
.where('${foreignKeyRelation}', '=', this.${formattedModelName}.id)
|
|
536
|
+
.selectAll()
|
|
537
|
+
.executeTakeFirst()
|
|
538
|
+
|
|
539
|
+
if (! model)
|
|
540
|
+
throw new Error('Model Relation Not Found!')
|
|
541
|
+
|
|
542
|
+
return new ${modelRelation}.modelInstance(model)
|
|
543
|
+
}\n\n`
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (relationType === 'belongsType' && !relationCount) {
|
|
547
|
+
const relationName = relation.relationName || formattedModelRelation
|
|
548
|
+
|
|
549
|
+
relationMethods += `
|
|
550
|
+
async ${relationName}() {
|
|
551
|
+
if (this.${foreignKeyRelation} === undefined)
|
|
552
|
+
throw new Error('Relation Error!')
|
|
553
|
+
|
|
554
|
+
const model = await db.selectFrom('${tableRelation}')
|
|
555
|
+
.where('id', '=', ${foreignKeyRelation})
|
|
556
|
+
.selectAll()
|
|
557
|
+
.executeTakeFirst()
|
|
558
|
+
|
|
559
|
+
if (! model)
|
|
560
|
+
throw new Error('Model Relation Not Found!')
|
|
561
|
+
|
|
562
|
+
return new ${modelRelation}.modelInstance(model)
|
|
563
|
+
}\n\n`
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (relationType === 'belongsType' && relationCount === 'many') {
|
|
567
|
+
const pivotTable = pivotTableRelation || tableRelation
|
|
568
|
+
const relationName = relation.relationName || formattedModelName + capitalizeTableRelation
|
|
569
|
+
|
|
570
|
+
relationMethods += `
|
|
571
|
+
async ${relationName}() {
|
|
572
|
+
if (this.${formattedModelName}.id === undefined)
|
|
573
|
+
throw new Error('Relation Error!')
|
|
574
|
+
|
|
575
|
+
const results = await db.selectFrom('${pivotTable}')
|
|
576
|
+
.where('${foreignKeyRelation}', '=', this.${formattedModelName}.id)
|
|
577
|
+
.selectAll()
|
|
578
|
+
.execute()
|
|
579
|
+
|
|
580
|
+
return results
|
|
581
|
+
}\n\n`
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
for (const attribute of attributes) fieldString += ` ${attribute.field}: ${attribute.fieldArray?.entity}\n `
|
|
586
|
+
|
|
587
|
+
const otherModelRelations = await fetchOtherModelRelations(model)
|
|
588
|
+
|
|
589
|
+
for (const otherModelRelation of otherModelRelations) fieldString += ` ${otherModelRelation.foreignKey}: number`
|
|
590
|
+
|
|
591
|
+
return `import type { ColumnType, Generated, Insertable, Selectable, Updateable } from 'kysely'
|
|
592
|
+
import type { Result } from '@stacksjs/error-handling'
|
|
593
|
+
import { err, handleError, ok } from '@stacksjs/error-handling'
|
|
594
|
+
import { db } from '@stacksjs/database'
|
|
595
|
+
${relationImports}
|
|
596
|
+
// import { Kysely, MysqlDialect, PostgresDialect } from 'kysely'
|
|
597
|
+
// import { Pool } from 'pg'
|
|
598
|
+
|
|
599
|
+
// TODO: we need an action that auto-generates these table interfaces
|
|
600
|
+
export interface ${formattedTableName}Table {
|
|
601
|
+
id: Generated<number>
|
|
602
|
+
${fieldString}
|
|
603
|
+
created_at: ColumnType<Date, string | undefined, never>
|
|
604
|
+
updated_at: ColumnType<Date, string | undefined, never>
|
|
605
|
+
deleted_at: ColumnType<Date, string | undefined, never>
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
interface ${modelName}Response {
|
|
609
|
+
data: ${formattedTableName}
|
|
610
|
+
paging: {
|
|
611
|
+
total_records: number
|
|
612
|
+
page: number
|
|
613
|
+
total_pages: number
|
|
614
|
+
}
|
|
615
|
+
next_cursor: number | null
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export type ${modelName}Type = Selectable<${formattedTableName}Table>
|
|
619
|
+
export type New${modelName} = Insertable<${formattedTableName}Table>
|
|
620
|
+
export type ${modelName}Update = Updateable<${formattedTableName}Table>
|
|
621
|
+
export type ${formattedTableName} = ${modelName}Type[]
|
|
622
|
+
|
|
623
|
+
export type ${modelName}Column = ${formattedTableName}
|
|
624
|
+
export type ${modelName}Columns = Array<keyof ${formattedTableName}>
|
|
625
|
+
|
|
626
|
+
type SortDirection = 'asc' | 'desc'
|
|
627
|
+
interface SortOptions { column: ${modelName}Type, order: SortDirection }
|
|
628
|
+
// Define a type for the options parameter
|
|
629
|
+
interface QueryOptions {
|
|
630
|
+
sort?: SortOptions
|
|
631
|
+
limit?: number
|
|
632
|
+
offset?: number
|
|
633
|
+
page?: number
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
export class ${modelName}Model {
|
|
637
|
+
private ${formattedModelName}: Partial<${modelName}Type>
|
|
638
|
+
private results: Partial<${modelName}Type>[]
|
|
639
|
+
private hidden = ['password'] // TODO: this hidden functionality needs to be implemented still
|
|
640
|
+
|
|
641
|
+
constructor(${formattedModelName}: Partial<${modelName}Type>) {
|
|
642
|
+
this.${formattedModelName} = ${formattedModelName}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Method to find a ${formattedModelName} by ID
|
|
646
|
+
static async find(id: number, fields?: (keyof ${modelName}Type)[]) {
|
|
647
|
+
let query = db.selectFrom('${tableName}').where('id', '=', id)
|
|
648
|
+
|
|
649
|
+
if (fields)
|
|
650
|
+
query = query.select(fields)
|
|
651
|
+
else
|
|
652
|
+
query = query.selectAll()
|
|
653
|
+
|
|
654
|
+
const model = await query.executeTakeFirst()
|
|
655
|
+
|
|
656
|
+
if (!model)
|
|
657
|
+
return null
|
|
658
|
+
|
|
659
|
+
return new ${modelName}Model(model)
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
static async findMany(ids: number[], fields?: (keyof ${modelName}Type)[]) {
|
|
663
|
+
let query = db.selectFrom('${tableName}').where('id', 'in', ids)
|
|
664
|
+
|
|
665
|
+
if (fields)
|
|
666
|
+
query = query.select(fields)
|
|
667
|
+
else
|
|
668
|
+
query = query.selectAll()
|
|
669
|
+
|
|
670
|
+
const model = await query.execute()
|
|
671
|
+
|
|
672
|
+
return model.map(modelItem => new ${modelName}Model(modelItem))
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Method to get a ${formattedModelName} by criteria
|
|
676
|
+
static async get(criteria: Partial<${modelName}Type>, options: QueryOptions = {}): Promise<${modelName}Model[]> {
|
|
677
|
+
let query = db.selectFrom('${tableName}')
|
|
678
|
+
|
|
679
|
+
// Apply sorting from options
|
|
680
|
+
if (options.sort)
|
|
681
|
+
query = query.orderBy(options.sort.column, options.sort.order)
|
|
682
|
+
|
|
683
|
+
// Apply limit and offset from options
|
|
684
|
+
if (options.limit !== undefined)
|
|
685
|
+
query = query.limit(options.limit)
|
|
686
|
+
|
|
687
|
+
if (options.offset !== undefined)
|
|
688
|
+
query = query.offset(options.offset)
|
|
689
|
+
|
|
690
|
+
const model = await query.selectAll().execute()
|
|
691
|
+
return model.map(modelItem => new ${modelName}Model(modelItem))
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Method to get all ${tableName}
|
|
695
|
+
static async all(options: QueryOptions = { limit: 10, offset: 0, page: 1 }): Promise<${modelName}Response> {
|
|
696
|
+
const totalRecordsResult = await db.selectFrom('${tableName}')
|
|
697
|
+
.select(db.fn.count('id').as('total')) // Use 'id' or another actual column name
|
|
698
|
+
.executeTakeFirst()
|
|
699
|
+
|
|
700
|
+
const totalRecords = Number(totalRecordsResult?.total) || 0
|
|
701
|
+
const totalPages = Math.ceil(totalRecords / (options.limit ?? 10))
|
|
702
|
+
|
|
703
|
+
const ${tableName}WithExtra = await db.selectFrom('${tableName}')
|
|
704
|
+
.selectAll()
|
|
705
|
+
.orderBy('id', 'asc') // Assuming 'id' is used for cursor-based pagination
|
|
706
|
+
.limit((options.limit ?? 10) + 1) // Fetch one extra record
|
|
707
|
+
.offset((options.page - 1) * (options.limit ?? 10))
|
|
708
|
+
.execute()
|
|
709
|
+
|
|
710
|
+
let nextCursor = null
|
|
711
|
+
if (${tableName}WithExtra.length > (options.limit ?? 10))
|
|
712
|
+
nextCursor = ${tableName}WithExtra.pop()!.id // Use the ID of the extra record as the next cursor
|
|
713
|
+
|
|
714
|
+
return {
|
|
715
|
+
data: ${tableName}WithExtra,
|
|
716
|
+
paging: {
|
|
717
|
+
total_records: totalRecords,
|
|
718
|
+
page: options.page,
|
|
719
|
+
total_pages: totalPages,
|
|
720
|
+
},
|
|
721
|
+
next_cursor: nextCursor,
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Method to create a new ${formattedModelName}
|
|
726
|
+
static async create(new${modelName}: New${modelName}): Promise<${modelName}Model> {
|
|
727
|
+
const model = await db.insertInto('${tableName}')
|
|
728
|
+
.values(new${modelName})
|
|
729
|
+
.returningAll()
|
|
730
|
+
.executeTakeFirstOrThrow()
|
|
731
|
+
|
|
732
|
+
return new ${modelName}Model(model)
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Method to update a ${formattedModelName}
|
|
736
|
+
static async update(id: number, ${formattedModelName}Update: ${modelName}Update): Promise<${modelName}Model> {
|
|
737
|
+
const model = await db.updateTable('${tableName}')
|
|
738
|
+
.set(${formattedModelName}Update)
|
|
739
|
+
.where('id', '=', id)
|
|
740
|
+
.returningAll()
|
|
741
|
+
.executeTakeFirstOrThrow()
|
|
742
|
+
|
|
743
|
+
return new ${modelName}Model(model)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Method to remove a ${formattedModelName}
|
|
747
|
+
static async remove(id: number): Promise<${modelName}Model> {
|
|
748
|
+
const model = await db.deleteFrom('${tableName}')
|
|
749
|
+
.where('id', '=', id)
|
|
750
|
+
.returningAll()
|
|
751
|
+
.executeTakeFirstOrThrow()
|
|
752
|
+
|
|
753
|
+
return new ${modelName}Model(model)
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
async where(column: string, operator = '=', value: any) {
|
|
757
|
+
let query = db.selectFrom('${tableName}')
|
|
758
|
+
|
|
759
|
+
query = query.where(column, operator, value)
|
|
760
|
+
|
|
761
|
+
return await query.selectAll().execute()
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
async whereIs(criteria: Partial<${modelName}Type>, options: QueryOptions = {}) {
|
|
765
|
+
let query = db.selectFrom('${tableName}')
|
|
766
|
+
|
|
767
|
+
// Existing criteria checks
|
|
768
|
+
if (criteria.id)
|
|
769
|
+
query = query.where('id', '=', criteria.id) // Kysely is immutable, we must re-assign
|
|
770
|
+
|
|
771
|
+
if (criteria.email)
|
|
772
|
+
query = query.where('email', '=', criteria.email)
|
|
773
|
+
|
|
774
|
+
if (criteria.name !== undefined) {
|
|
775
|
+
query = query.where(
|
|
776
|
+
'name',
|
|
777
|
+
criteria.name === null ? 'is' : '=',
|
|
778
|
+
criteria.name,
|
|
779
|
+
)
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (criteria.password)
|
|
783
|
+
query = query.where('password', '=', criteria.password)
|
|
784
|
+
|
|
785
|
+
if (criteria.created_at)
|
|
786
|
+
query = query.where('created_at', '=', criteria.created_at)
|
|
787
|
+
|
|
788
|
+
if (criteria.updated_at)
|
|
789
|
+
query = query.where('updated_at', '=', criteria.updated_at)
|
|
790
|
+
|
|
791
|
+
if (criteria.deleted_at)
|
|
792
|
+
query = query.where('deleted_at', '=', criteria.deleted_at)
|
|
793
|
+
|
|
794
|
+
// Apply sorting from options
|
|
795
|
+
if (options.sort)
|
|
796
|
+
query = query.orderBy(options.sort.column, options.sort.order)
|
|
797
|
+
|
|
798
|
+
// Apply pagination from options
|
|
799
|
+
if (options.limit !== undefined)
|
|
800
|
+
query = query.limit(options.limit)
|
|
801
|
+
|
|
802
|
+
if (options.offset !== undefined)
|
|
803
|
+
query = query.offset(options.offset)
|
|
804
|
+
|
|
805
|
+
return await query.selectAll().execute()
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
async whereIn(column: keyof ${modelName}Type, values: any[], options: QueryOptions = {}) {
|
|
809
|
+
let query = db.selectFrom('${tableName}')
|
|
810
|
+
|
|
811
|
+
query = query.where(column, 'in', values)
|
|
812
|
+
|
|
813
|
+
// Apply sorting from options
|
|
814
|
+
if (options.sort)
|
|
815
|
+
query = query.orderBy(options.sort.column, options.sort.order)
|
|
816
|
+
|
|
817
|
+
// Apply pagination from options
|
|
818
|
+
if (options.limit !== undefined)
|
|
819
|
+
query = query.limit(options.limit)
|
|
820
|
+
|
|
821
|
+
if (options.offset !== undefined)
|
|
822
|
+
query = query.offset(options.offset)
|
|
823
|
+
|
|
824
|
+
return await query.selectAll().execute()
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
async first() {
|
|
828
|
+
return await db.selectFrom('${tableName}')
|
|
829
|
+
.selectAll()
|
|
830
|
+
.executeTakeFirst()
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
async last() {
|
|
834
|
+
return await db.selectFrom('${tableName}')
|
|
835
|
+
.selectAll()
|
|
836
|
+
.orderBy('id', 'desc')
|
|
837
|
+
.executeTakeFirst()
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async orderBy(column: keyof ${modelName}Type, order: 'asc' | 'desc') {
|
|
841
|
+
return await db.selectFrom('${tableName}')
|
|
842
|
+
.selectAll()
|
|
843
|
+
.orderBy(column, order)
|
|
844
|
+
.execute()
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
async orderByDesc(column: keyof ${modelName}Type) {
|
|
848
|
+
return await db.selectFrom('${tableName}')
|
|
849
|
+
.selectAll()
|
|
850
|
+
.orderBy(column, 'desc')
|
|
851
|
+
.execute()
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
async orderByAsc(column: keyof ${modelName}Type) {
|
|
855
|
+
return await db.selectFrom('${tableName}')
|
|
856
|
+
.selectAll()
|
|
857
|
+
.orderBy(column, 'asc')
|
|
858
|
+
.execute()
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Method to get the ${formattedModelName} instance itself
|
|
862
|
+
self() {
|
|
863
|
+
return this
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Method to get the ${formattedModelName} instance data
|
|
867
|
+
get() {
|
|
868
|
+
return this.${formattedModelName}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Method to update the ${formattedModelName} instance
|
|
872
|
+
async update(${formattedModelName}: ${modelName}Update): Promise<Result<${modelName}Type, Error>> {
|
|
873
|
+
if (this.${formattedModelName}.id === undefined)
|
|
874
|
+
return err(handleError('${modelName} ID is undefined'))
|
|
875
|
+
|
|
876
|
+
const updatedModel = await db.updateTable('${tableName}')
|
|
877
|
+
.set(${formattedModelName})
|
|
878
|
+
.where('id', '=', this.${formattedModelName}.id)
|
|
879
|
+
.returningAll()
|
|
880
|
+
.executeTakeFirst()
|
|
881
|
+
|
|
882
|
+
if (!updatedModel)
|
|
883
|
+
return err(handleError('${modelName} not found'))
|
|
884
|
+
|
|
885
|
+
this.${formattedModelName} = updatedModel
|
|
886
|
+
|
|
887
|
+
return ok(updatedModel)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Method to save (insert or update) the ${formattedModelName} instance
|
|
891
|
+
async save(): Promise<void> {
|
|
892
|
+
if (!this.${formattedModelName})
|
|
893
|
+
throw new Error('${modelName} data is undefined')
|
|
894
|
+
|
|
895
|
+
if (this.${formattedModelName}.id === undefined) {
|
|
896
|
+
// Insert new ${formattedModelName}
|
|
897
|
+
const newModel = await db.insertInto('${tableName}')
|
|
898
|
+
.values(this.${formattedModelName} as New${modelName})
|
|
899
|
+
.returningAll()
|
|
900
|
+
.executeTakeFirstOrThrow()
|
|
901
|
+
this.${formattedModelName} = newModel
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
// Update existing ${formattedModelName}
|
|
905
|
+
await this.update(this.${formattedModelName})
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Method to delete the ${formattedModelName} instance
|
|
910
|
+
async delete(): Promise<void> {
|
|
911
|
+
if (this.${formattedModelName}.id === undefined)
|
|
912
|
+
throw new Error('${modelName} ID is undefined')
|
|
913
|
+
|
|
914
|
+
await db.deleteFrom('${tableName}')
|
|
915
|
+
.where('id', '=', this.${formattedModelName}.id)
|
|
916
|
+
.execute()
|
|
917
|
+
|
|
918
|
+
this.${formattedModelName} = {}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Method to refresh the ${formattedModelName} instance data from the database
|
|
922
|
+
async refresh(): Promise<void> {
|
|
923
|
+
if (this.${formattedModelName}.id === undefined)
|
|
924
|
+
throw new Error('${modelName} ID is undefined')
|
|
925
|
+
|
|
926
|
+
const refreshedModel = await db.selectFrom('${tableName}')
|
|
927
|
+
.where('id', '=', this.${formattedModelName}.id)
|
|
928
|
+
.selectAll()
|
|
929
|
+
.executeTakeFirst()
|
|
930
|
+
|
|
931
|
+
if (!refreshedModel)
|
|
932
|
+
throw new Error('${modelName} not found')
|
|
933
|
+
|
|
934
|
+
this.${formattedModelName} = refreshedModel
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
${relationMethods}
|
|
938
|
+
|
|
939
|
+
toJSON() {
|
|
940
|
+
const output: Partial<${modelName}Type> = { ...this.${formattedModelName} }
|
|
941
|
+
|
|
942
|
+
this.hidden.forEach((attr) => {
|
|
943
|
+
if (attr in output)
|
|
944
|
+
delete output[attr as keyof Partial<${modelName}Type>]
|
|
945
|
+
})
|
|
946
|
+
|
|
947
|
+
type ${modelName} = Omit<${modelName}Type, 'password'>
|
|
948
|
+
|
|
949
|
+
return output as ${modelName}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const Model = ${modelName}Model
|
|
954
|
+
|
|
955
|
+
// starting here, ORM functions
|
|
956
|
+
export async function find(id: number, fields?: (keyof ${modelName}Type)[]) {
|
|
957
|
+
let query = db.selectFrom('${tableName}').where('id', '=', id)
|
|
958
|
+
|
|
959
|
+
if (fields)
|
|
960
|
+
query = query.select(fields)
|
|
961
|
+
else
|
|
962
|
+
query = query.selectAll()
|
|
963
|
+
|
|
964
|
+
const model = await query.executeTakeFirst()
|
|
965
|
+
|
|
966
|
+
if (!model)
|
|
967
|
+
return null
|
|
968
|
+
|
|
969
|
+
return new ${modelName}Model(model)
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
export async function findMany(ids: number[], fields?: (keyof ${modelName}Type)[]) {
|
|
973
|
+
let query = db.selectFrom('${tableName}').where('id', 'in', ids)
|
|
974
|
+
|
|
975
|
+
if (fields)
|
|
976
|
+
query = query.select(fields)
|
|
977
|
+
else
|
|
978
|
+
query = query.selectAll()
|
|
979
|
+
|
|
980
|
+
const model = await query.execute()
|
|
981
|
+
|
|
982
|
+
return model.map(modelItem => new ${modelName}Model(modelItem))
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
export async function count() {
|
|
986
|
+
const results = await db.selectFrom('${tableName}')
|
|
987
|
+
.selectAll()
|
|
988
|
+
.execute()
|
|
989
|
+
|
|
990
|
+
return results.length
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
export async function get(criteria: Partial<${modelName}Type>, sort: { column: keyof ${modelName}Type, order: 'asc' | 'desc' } = { column: 'created_at', order: 'desc' }) {
|
|
994
|
+
let query = db.selectFrom('${tableName}')
|
|
995
|
+
|
|
996
|
+
if (criteria.id)
|
|
997
|
+
query = query.where('id', '=', criteria.id) // Kysely is immutable, we must re-assign
|
|
998
|
+
|
|
999
|
+
if (criteria.email)
|
|
1000
|
+
query = query.where('email', '=', criteria.email)
|
|
1001
|
+
|
|
1002
|
+
if (criteria.name !== undefined) {
|
|
1003
|
+
query = query.where(
|
|
1004
|
+
'name',
|
|
1005
|
+
criteria.name === null ? 'is' : '=',
|
|
1006
|
+
criteria.name,
|
|
1007
|
+
)
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
if (criteria.password)
|
|
1011
|
+
query = query.where('password', '=', criteria.password)
|
|
1012
|
+
|
|
1013
|
+
if (criteria.created_at)
|
|
1014
|
+
query = query.where('created_at', '=', criteria.created_at)
|
|
1015
|
+
|
|
1016
|
+
if (criteria.updated_at)
|
|
1017
|
+
query = query.where('updated_at', '=', criteria.updated_at)
|
|
1018
|
+
|
|
1019
|
+
if (criteria.deleted_at)
|
|
1020
|
+
query = query.where('deleted_at', '=', criteria.deleted_at)
|
|
1021
|
+
|
|
1022
|
+
// Apply sorting based on the 'sort' parameter
|
|
1023
|
+
query = query.orderBy(sort.column, sort.order)
|
|
1024
|
+
|
|
1025
|
+
return await query.selectAll().execute()
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
export async function all(limit: number = 10, offset: number = 0) {
|
|
1029
|
+
return await db.selectFrom('${tableName}')
|
|
1030
|
+
.selectAll()
|
|
1031
|
+
.orderBy('created_at', 'desc')
|
|
1032
|
+
.limit(limit)
|
|
1033
|
+
.offset(offset)
|
|
1034
|
+
.execute()
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
export async function create(new${modelName}: New${modelName}) {
|
|
1038
|
+
return await db.insertInto('${tableName}')
|
|
1039
|
+
.values(new${modelName})
|
|
1040
|
+
.returningAll()
|
|
1041
|
+
.executeTakeFirstOrThrow()
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
export async function first() {
|
|
1045
|
+
return await db.selectFrom('${tableName}')
|
|
1046
|
+
.selectAll()
|
|
1047
|
+
.executeTakeFirst()
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
export async function recent(limit: number) {
|
|
1051
|
+
return await db.selectFrom('${tableName}')
|
|
1052
|
+
.selectAll()
|
|
1053
|
+
.limit(limit)
|
|
1054
|
+
.execute()
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
export async function last(limit: number) {
|
|
1058
|
+
return await db.selectFrom('${tableName}')
|
|
1059
|
+
.selectAll()
|
|
1060
|
+
.orderBy('id', 'desc')
|
|
1061
|
+
.limit(limit)
|
|
1062
|
+
.execute()
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
export async function update(id: number, ${formattedModelName}Update: ${modelName}Update) {
|
|
1066
|
+
return await db.updateTable('${tableName}')
|
|
1067
|
+
.set(${formattedModelName}Update)
|
|
1068
|
+
.where('id', '=', id)
|
|
1069
|
+
.execute()
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
export async function remove(id: number) {
|
|
1073
|
+
return await db.deleteFrom('${tableName}')
|
|
1074
|
+
.where('id', '=', id)
|
|
1075
|
+
.returningAll()
|
|
1076
|
+
.executeTakeFirst()
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
export async function where(column: string, operator = '=', value: any) {
|
|
1080
|
+
let query = db.selectFrom('${tableName}')
|
|
1081
|
+
|
|
1082
|
+
query = query.where(column, operator, value)
|
|
1083
|
+
|
|
1084
|
+
return await query.selectAll().execute()
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
export async function whereIs(
|
|
1088
|
+
criteria: Partial<${modelName}Type>,
|
|
1089
|
+
options: QueryOptions = {},
|
|
1090
|
+
) {
|
|
1091
|
+
let query = db.selectFrom('${tableName}')
|
|
1092
|
+
|
|
1093
|
+
// Apply criteria
|
|
1094
|
+
if (criteria.id)
|
|
1095
|
+
query = query.where('id', '=', criteria.id)
|
|
1096
|
+
|
|
1097
|
+
if (criteria.email)
|
|
1098
|
+
query = query.where('email', '=', criteria.email)
|
|
1099
|
+
|
|
1100
|
+
if (criteria.name !== undefined) {
|
|
1101
|
+
query = query.where(
|
|
1102
|
+
'name',
|
|
1103
|
+
criteria.name === null ? 'is' : '=',
|
|
1104
|
+
criteria.name,
|
|
1105
|
+
)
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
if (criteria.password)
|
|
1109
|
+
query = query.where('password', '=', criteria.password)
|
|
1110
|
+
|
|
1111
|
+
if (criteria.created_at)
|
|
1112
|
+
query = query.where('created_at', '=', criteria.created_at)
|
|
1113
|
+
|
|
1114
|
+
if (criteria.updated_at)
|
|
1115
|
+
query = query.where('updated_at', '=', criteria.updated_at)
|
|
1116
|
+
|
|
1117
|
+
if (criteria.deleted_at)
|
|
1118
|
+
query = query.where('deleted_at', '=', criteria.deleted_at)
|
|
1119
|
+
|
|
1120
|
+
// Apply sorting from options
|
|
1121
|
+
if (options.sort)
|
|
1122
|
+
query = query.orderBy(options.sort.column, options.sort.order)
|
|
1123
|
+
|
|
1124
|
+
// Apply pagination from options
|
|
1125
|
+
if (options.limit !== undefined)
|
|
1126
|
+
query = query.limit(options.limit)
|
|
1127
|
+
|
|
1128
|
+
if (options.offset !== undefined)
|
|
1129
|
+
query = query.offset(options.offset)
|
|
1130
|
+
|
|
1131
|
+
return await query.selectAll().execute()
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
export async function whereIn(
|
|
1135
|
+
column: keyof ${modelName}Type,
|
|
1136
|
+
values: any[],
|
|
1137
|
+
options: QueryOptions = {},
|
|
1138
|
+
) {
|
|
1139
|
+
let query = db.selectFrom('${tableName}')
|
|
1140
|
+
|
|
1141
|
+
query = query.where(column, 'in', values)
|
|
1142
|
+
|
|
1143
|
+
// Apply sorting from options
|
|
1144
|
+
if (options.sort)
|
|
1145
|
+
query = query.orderBy(options.sort.column, options.sort.order)
|
|
1146
|
+
|
|
1147
|
+
// Apply pagination from options
|
|
1148
|
+
if (options.limit !== undefined)
|
|
1149
|
+
query = query.limit(options.limit)
|
|
1150
|
+
|
|
1151
|
+
if (options.offset !== undefined)
|
|
1152
|
+
query = query.offset(options.offset)
|
|
1153
|
+
|
|
1154
|
+
return await query.selectAll().execute()
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
export const ${modelName} = {
|
|
1158
|
+
find,
|
|
1159
|
+
findMany,
|
|
1160
|
+
get,
|
|
1161
|
+
count,
|
|
1162
|
+
all,
|
|
1163
|
+
create,
|
|
1164
|
+
update,
|
|
1165
|
+
remove,
|
|
1166
|
+
Model,
|
|
1167
|
+
first,
|
|
1168
|
+
last,
|
|
1169
|
+
recent,
|
|
1170
|
+
where,
|
|
1171
|
+
whereIn,
|
|
1172
|
+
model: ${modelName}Model
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
export default ${modelName}
|
|
1176
|
+
`
|
|
1177
|
+
}
|