@stacksjs/database 0.64.5 → 0.65.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/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@stacksjs/database",
3
3
  "type": "module",
4
- "version": "0.64.5",
4
+ "version": "0.65.0",
5
5
  "description": "The Stacks database integration.",
6
6
  "author": "Chris Breuer",
7
+ "contributors": ["Chris Breuer <chris@stacksjs.org>"],
7
8
  "license": "MIT",
8
9
  "funding": "https://github.com/sponsors/chrisbbreuer",
9
10
  "homepage": "https://github.com/stacksjs/stacks/tree/main/storage/framework/core/database#readme",
@@ -36,37 +37,30 @@
36
37
  },
37
38
  "module": "dist/index.js",
38
39
  "types": "dist/index.d.ts",
39
- "contributors": [
40
- "Chris Breuer <chris@stacksjs.org>"
41
- ],
42
- "files": [
43
- "README.md",
44
- "dist",
45
- "src"
46
- ],
40
+ "files": ["README.md", "dist", "src"],
47
41
  "scripts": {
48
- "build": "bun --bun build.ts",
49
- "typecheck": "bun --bun tsc --noEmit",
42
+ "build": "bun build.ts",
43
+ "typecheck": "bun tsc --noEmit",
50
44
  "prepublishOnly": "bun run build"
51
45
  },
52
46
  "dependencies": {
53
- "@stacksjs/cli": "latest",
54
- "@stacksjs/config": "latest",
55
- "@stacksjs/faker": "latest",
56
- "@stacksjs/path": "latest",
57
- "@stacksjs/query-builder": "latest",
58
- "@stacksjs/storage": "latest",
59
- "@stacksjs/strings": "latest",
60
- "@stacksjs/utils": "latest",
47
+ "@stacksjs/cli": "0.64.6",
48
+ "@stacksjs/config": "0.64.6",
49
+ "@stacksjs/faker": "0.64.6",
50
+ "@stacksjs/path": "0.64.6",
51
+ "@stacksjs/query-builder": "0.64.6",
52
+ "@stacksjs/storage": "0.64.6",
53
+ "@stacksjs/strings": "0.64.6",
54
+ "@stacksjs/utils": "0.64.6",
61
55
  "kysely-bun-worker": "^0.6.3"
62
56
  },
63
57
  "optionalDependencies": {
64
- "mysql2": "^3.11.0"
58
+ "mysql2": "^3.11.3"
65
59
  },
66
60
  "devDependencies": {
67
- "@stacksjs/development": "latest",
61
+ "@stacksjs/development": "0.64.6",
68
62
  "@types/tar": "^6.1.13",
69
- "debug": "^4.3.6",
63
+ "debug": "^4.3.7",
70
64
  "mkdirp": "^3.0.1",
71
65
  "q": "^1.5.1",
72
66
  "tar": "^7.4.3"
@@ -1,11 +1,11 @@
1
+ import type { Attribute, Attributes, Model, VineType } from '@stacksjs/types'
1
2
  import { log } from '@stacksjs/cli'
2
3
  import { db } from '@stacksjs/database'
3
- import { getModelName, getTableName } from '@stacksjs/orm'
4
+ import { handleError } from '@stacksjs/error-handling'
5
+ import { getTableName } from '@stacksjs/orm'
4
6
  import { path } from '@stacksjs/path'
5
- import { fs, glob } from '@stacksjs/storage'
6
- import { plural, singular, snakeCase } from '@stacksjs/strings'
7
- import type { Attributes, Model, RelationConfig, VineType } from '@stacksjs/types'
8
- import { isString } from '@stacksjs/validation'
7
+ import { fs, globSync } from '@stacksjs/storage'
8
+ import { plural, snakeCase } from '@stacksjs/strings'
9
9
 
10
10
  export * from './mysql'
11
11
  export * from './postgres'
@@ -21,8 +21,9 @@ export async function getLastMigrationFields(modelName: string): Promise<Attribu
21
21
  const model = (await import(oldModelPath)).default as Model
22
22
  let fields = {} as Attributes
23
23
 
24
- if (typeof model.attributes === 'object') fields = model.attributes
25
- else fields = JSON.parse(model.attributes) as Attributes
24
+ if (typeof model.attributes === 'object')
25
+ fields = model.attributes
26
+ else fields = JSON.parse(model.attributes || '{}') as Attributes
26
27
 
27
28
  return fields
28
29
  }
@@ -35,18 +36,21 @@ export async function modelTableName(model: Model | string): Promise<string> {
35
36
  return model.table ?? snakeCase(plural(model?.name || ''))
36
37
  }
37
38
 
38
- export async function hasTableBeenMigrated(tableName: string) {
39
+ export async function hasTableBeenMigrated(tableName: string): Promise<boolean> {
39
40
  log.debug(`hasTableBeenMigrated for table: ${tableName}`)
40
41
 
41
42
  const results = await getExecutedMigrations()
42
43
 
43
- return results.some((migration) => migration.name.includes(tableName))
44
+ return results.some(migration => migration.name.includes(tableName))
44
45
  }
45
46
 
46
- export async function getExecutedMigrations() {
47
+ export async function getExecutedMigrations(): Promise<{ name: string }[]> {
47
48
  try {
48
49
  return await db.selectFrom('migrations').select('name').execute()
49
- } catch (error) {
50
+ }
51
+
52
+ catch (error) {
53
+ handleError(error, { shouldExitProcess: false })
50
54
  return []
51
55
  }
52
56
  }
@@ -55,13 +59,17 @@ function hasFunction(rule: VineType, functionName: string): boolean {
55
59
  return typeof rule[functionName] === 'function'
56
60
  }
57
61
 
58
- export function mapFieldTypeToColumnType(rule: VineType): string {
62
+ export function mapFieldTypeToColumnType(rule: VineType, driver = 'mysql'): string {
59
63
  if (hasFunction(rule, 'getChoices')) {
64
+ if (driver === 'sqlite') {
65
+ return `'text'`
66
+ }
67
+
60
68
  // Condition checker if an attribute is enum, could not think any conditions atm
61
69
  const enumChoices = rule.getChoices() as string[]
62
70
 
63
71
  // Convert each string value to its corresponding string structure
64
- const enumStructure = enumChoices.map((value) => `'${value}'`).join(', ')
72
+ const enumStructure = enumChoices.map(value => `'${value}'`).join(', ')
65
73
 
66
74
  // Construct the ENUM definition
67
75
  const enumDefinition = `sql\`enum(${enumStructure})\``
@@ -73,11 +81,12 @@ export function mapFieldTypeToColumnType(rule: VineType): string {
73
81
  // Default column type for strings
74
82
  return prepareTextColumnType(rule)
75
83
 
76
- if (rule[Symbol.for('schema_name')].includes('number')) return `'integer'`
77
-
78
- if (rule[Symbol.for('schema_name')].includes('boolean')) return `'boolean'`
79
-
80
- if (rule[Symbol.for('schema_name')].includes('date')) return `'date'`
84
+ if (rule[Symbol.for('schema_name')].includes('number'))
85
+ return `'integer'`
86
+ if (rule[Symbol.for('schema_name')].includes('boolean'))
87
+ return `'boolean'`
88
+ if (rule[Symbol.for('schema_name')].includes('date'))
89
+ return `'date'`
81
90
 
82
91
  // need to now handle all other types
83
92
 
@@ -116,12 +125,13 @@ export function prepareTextColumnType(rule: VineType) {
116
125
 
117
126
  // If there's only a min length validation and no max, consider using text
118
127
  // This is a simplistic approach; adjust based on your actual requirements
119
- if (minLengthValidation && !maxLengthValidation) columnType = 'text'
128
+ if (minLengthValidation && !maxLengthValidation)
129
+ columnType = 'text'
120
130
 
121
131
  return `'${columnType}'`
122
132
  }
123
133
 
124
- export function findCharacterLength(rule: VineType): { min: number; max: number } | undefined {
134
+ export function findCharacterLength(rule: VineType): { min: number, max: number } | undefined {
125
135
  const result: any = {}
126
136
 
127
137
  // Find min and max length validations
@@ -133,7 +143,8 @@ export function findCharacterLength(rule: VineType): { min: number; max: number
133
143
  }
134
144
 
135
145
  for (const key of ['min', 'max']) {
136
- if (maxLengthValidation.options[key] === undefined && minLengthValidation.options[key] === undefined) continue
146
+ if (maxLengthValidation.options[key] === undefined && minLengthValidation.options[key] === undefined)
147
+ continue
137
148
 
138
149
  result.max = maxLengthValidation.options[key]
139
150
  result.min = minLengthValidation.options[key]
@@ -164,147 +175,9 @@ export async function checkPivotMigration(dynamicPart: string): Promise<boolean>
164
175
  })
165
176
  }
166
177
 
167
- export async function getRelations(model: Model, modelPath: string): Promise<RelationConfig[]> {
168
- const relationsArray = ['hasOne', 'hasMany', 'belongsToMany', 'hasOneThrough']
169
- const relationships = []
170
-
171
- for (const relation of relationsArray) {
172
- if (hasRelations(model, relation)) {
173
- for (const relationInstance of model[relation]) {
174
- let relationModel = relationInstance.model
175
-
176
- if (isString(relationInstance)) {
177
- relationModel = relationInstance
178
- }
179
-
180
- const modelRelationPath = path.userModelsPath(relationModel)
181
- const modelRelation = (await import(modelRelationPath)).default
182
- const modelName = getModelName(model, modelPath)
183
- const formattedModelName = modelName.toLowerCase()
184
- const tableName = getTableName(model, modelPath)
185
- const modelRelationTable = getModelName(modelRelation, modelRelationPath)
186
-
187
- relationships.push({
188
- relationship: relation,
189
- model: relationModel,
190
- table: modelRelationTable,
191
- relationModel: modelName,
192
- relationTable: tableName,
193
- foreignKey: relationInstance.foreignKey || `${formattedModelName}_id`,
194
- relationName: relationInstance.relationName || '',
195
- throughModel: relationInstance.through || '',
196
- throughForeignKey: relationInstance.throughForeignKey || '',
197
- pivotTable: relationInstance?.pivotTable || getPivotTableName(formattedModelName, modelRelationTable),
198
- })
199
- }
200
- }
201
- }
202
-
203
- return relationships
204
- }
205
-
206
- export async function fetchOtherModelRelations(model: Model, modelPath: string): Promise<RelationConfig[]> {
207
- const modelFiles = glob.sync(path.userModelsPath('*.ts'))
208
- const modelRelations = []
209
-
210
- for (let i = 0; i < modelFiles.length; i++) {
211
- const modelFileElement = modelFiles[i] as string
212
- const modelFile = (await import(modelFileElement)).default as Model
213
- const tableName = getTableName(model, modelPath)
214
- const modelName = getModelName(model, modelPath)
215
- const modelFileName = getTableName(modelFile, modelFileElement)
216
-
217
- if (tableName === modelFileName) continue
218
-
219
- const relations = await getRelations(modelFile, modelFileElement)
220
-
221
- if (!relations.length) continue
222
-
223
- const relation = relations.find((relation) => relation.model === modelName)
224
-
225
- if (relation) modelRelations.push(relation)
226
- }
227
-
228
- return modelRelations
229
- }
230
-
231
- export async function getPivotTables(
232
- model: Model,
233
- modelPath: string,
234
- ): Promise<{ table: string; firstForeignKey: string | undefined; secondForeignKey: string | undefined }[]> {
235
- const pivotTable = []
236
-
237
- const tableName = getTableName(model, modelPath)
238
-
239
- let firstForeignKey = ''
240
- let secondForeignKey = ''
241
- let table = ''
242
- let modelRelationPath = ''
243
-
244
- if (model.belongsToMany) {
245
- if ('belongsToMany' in model) {
246
- for (const belongsToManyRelation of model.belongsToMany) {
247
- if (typeof belongsToManyRelation === 'string') {
248
- modelRelationPath = path.userModelsPath(`${belongsToManyRelation}.ts`)
249
- } else {
250
- modelRelationPath = path.userModelsPath(`${belongsToManyRelation.model}.ts`)
251
- }
252
-
253
- const modelRelation = (await import(modelRelationPath)).default
254
- const formattedTableName = tableName.toLowerCase()
255
-
256
- const modelRelationTable = getTableName(modelRelation, modelRelationPath)
257
- // const modelRelationModelName = getModelName(modelRelation, modelRelationPath)
258
-
259
- if (typeof belongsToManyRelation === 'string') {
260
- firstForeignKey = `${singular(tableName.toLowerCase())}_${model.primaryKey}`
261
- secondForeignKey = `${singular(modelRelationTable)}_${model.primaryKey}`
262
- table = getPivotTableName(formattedTableName, modelRelationTable)
263
- } else {
264
- firstForeignKey =
265
- belongsToManyRelation.firstForeignKey || `${singular(tableName.toLowerCase())}_${model.primaryKey}`
266
- secondForeignKey =
267
- belongsToManyRelation.secondForeignKey || `${singular(modelRelationTable)}_${model.primaryKey}`
268
- table = belongsToManyRelation?.pivotTable || getPivotTableName(formattedTableName, modelRelationTable)
269
- }
270
-
271
- pivotTable.push({
272
- table,
273
- firstForeignKey,
274
- secondForeignKey,
275
- })
276
- }
277
-
278
- return pivotTable
279
- }
280
- }
281
-
282
- return []
283
- }
284
-
285
- function getPivotTableName(formattedModelName: string, modelRelationTable: string): string {
286
- // Create an array of the model names
287
- const models = [formattedModelName, modelRelationTable]
288
-
289
- // Sort the array alphabetically
290
- models.sort()
291
-
292
- models[0] = singular(models[0] || '')
293
- models[1] = plural(models[1] || '')
294
-
295
- // Join the sorted array with an underscore
296
- const pivotTableName = models.join('_')
297
-
298
- return pivotTableName
299
- }
300
-
301
- function hasRelations(obj: any, key: string): boolean {
302
- return key in obj
303
- }
304
-
305
- export function pluckChanges(array1: string[], array2: string[]): { added: string[]; removed: string[] } | null {
306
- const removed = array1.filter((item) => !array2.includes(item))
307
- const added = array2.filter((item) => !array1.includes(item))
178
+ export function pluckChanges(array1: string[], array2: string[]): { added: string[], removed: string[] } | null {
179
+ const removed = array1.filter(item => !array2.includes(item))
180
+ const added = array2.filter(item => !array1.includes(item))
308
181
 
309
182
  if (removed.length === 0 && added.length === 0) {
310
183
  return null
@@ -313,18 +186,22 @@ export function pluckChanges(array1: string[], array2: string[]): { added: strin
313
186
  return { added, removed }
314
187
  }
315
188
 
316
- export function arrangeColumns(attributes: Attributes | undefined) {
317
- if (!attributes) return []
189
+ export function arrangeColumns(attributes: Attributes | undefined): Array<[string, Attribute]> {
190
+ if (!attributes)
191
+ return []
318
192
 
319
193
  const entries = Object.entries(attributes)
320
194
 
195
+ // Sort the entries based on the 'order' property
196
+
197
+ // eslint-disable-next-line unused-imports/no-unused-vars
321
198
  entries.sort(([keyA, valueA], [keyB, valueB]) => {
322
199
  const orderA = valueA.order ?? Number.POSITIVE_INFINITY
323
200
  const orderB = valueB.order ?? Number.POSITIVE_INFINITY
324
201
  return orderA - orderB
325
202
  })
326
203
 
327
- return entries
204
+ return entries // Return the sorted key-value pairs
328
205
  }
329
206
 
330
207
  export function isArrayEqual(arr1: (number | undefined)[], arr2: (number | undefined)[]): boolean {
@@ -332,17 +209,19 @@ export function isArrayEqual(arr1: (number | undefined)[], arr2: (number | undef
332
209
  return false
333
210
  }
334
211
 
335
- if (arr1.length !== arr2.length) return false
212
+ if (arr1.length !== arr2.length)
213
+ return false
336
214
 
337
215
  for (let i = 0; i < arr1.length; i++) {
338
- if (arr1[i] !== arr2[i]) return false
216
+ if (arr1[i] !== arr2[i])
217
+ return false
339
218
  }
340
219
 
341
220
  return true
342
221
  }
343
222
 
344
- export function findDifferingKeys(obj1: any, obj2: any): { key: string; max: number; min: number }[] {
345
- const differingKeys: { key: string; max: number; min: number }[] = []
223
+ export function findDifferingKeys(obj1: any, obj2: any): { key: string, max: number, min: number }[] {
224
+ const differingKeys: { key: string, max: number, min: number }[] = []
346
225
 
347
226
  for (const key in obj1) {
348
227
  if (Object.prototype.hasOwnProperty.call(obj1, key) && Object.prototype.hasOwnProperty.call(obj2, key)) {
@@ -351,8 +230,8 @@ export function findDifferingKeys(obj1: any, obj2: any): { key: string; max: num
351
230
 
352
231
  if (lastCharacterLength !== undefined && latestCharacterLength !== undefined) {
353
232
  if (
354
- lastCharacterLength.max !== latestCharacterLength.max ||
355
- lastCharacterLength.min !== latestCharacterLength.min
233
+ lastCharacterLength.max !== latestCharacterLength.max
234
+ || lastCharacterLength.min !== latestCharacterLength.min
356
235
  ) {
357
236
  differingKeys.push({ key, max: latestCharacterLength.max, min: latestCharacterLength.min })
358
237
  }
@@ -362,3 +241,18 @@ export function findDifferingKeys(obj1: any, obj2: any): { key: string; max: num
362
241
 
363
242
  return differingKeys
364
243
  }
244
+
245
+ export async function fetchTables(): Promise<string[]> {
246
+ const modelFiles = globSync(path.userModelsPath('*.ts'), { absolute: true })
247
+
248
+ const tables: string[] = []
249
+
250
+ for (const modelPath of modelFiles) {
251
+ const model = (await import(modelPath)).default as Model
252
+ const tableName = getTableName(model, modelPath)
253
+
254
+ tables.push(tableName)
255
+ }
256
+
257
+ return tables
258
+ }