@stacksjs/database 0.70.108 → 0.70.109

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.
@@ -1,3 +1,4 @@
1
+ import type { Model } from '@stacksjs/types';
1
2
  /**
2
3
  * Glob, but resilient to "directory doesn't exist yet" (the userland
3
4
  * `app/Models/` directory in particular often doesn't exist when the
@@ -5,6 +6,8 @@
5
6
  * created any models yet). Avoids the Bun Glob ENOENT.
6
7
  */
7
8
  export declare function safeGlob(pattern: string): string[];
9
+ /** Build the same FK shapes as bun-query-builder's model migration plan. */
10
+ export declare function getDeclaredFKsFromModels(models: Model[]): DeclaredFK[];
8
11
  /**
9
12
  * Walk every model file (user + framework defaults) and return the
10
13
  * full list of declared `belongsTo` foreign keys.
package/dist/fk-audit.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
- import { plural, singular, snakeCase } from "@stacksjs/strings";
3
+ import { plural, snakeCase } from "@stacksjs/strings";
4
4
  import { path } from "@stacksjs/path";
5
5
  import { globSync } from "@stacksjs/storage";
6
6
  export function safeGlob(pattern) {
@@ -13,11 +13,60 @@ export function safeGlob(pattern) {
13
13
  return [];
14
14
  }
15
15
  }
16
+ function modelTable(model) {
17
+ return model.table || plural(snakeCase(model.name || ""));
18
+ }
19
+ function modelNameForColumn(column) {
20
+ return column.replace(/_id$/, "").split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
21
+ }
22
+ export function getDeclaredFKsFromModels(models) {
23
+ const meta = new Map(models.map((model) => [String(model.name ?? ""), {
24
+ primaryKey: String(model.primaryKey ?? "id"),
25
+ table: modelTable(model)
26
+ }])), declared = [], seen = new Set, push = (fk) => {
27
+ const key = `${fk.fromTable}.${fk.fromColumn}\u2192${fk.toTable}.${fk.toColumn}`.toLowerCase();
28
+ if (seen.has(key))
29
+ return;
30
+ seen.add(key);
31
+ declared.push(fk);
32
+ };
33
+ for (const model of models) {
34
+ const fromTable = modelTable(model), modelName = String(model.name ?? ""), attributes = model.attributes ?? {};
35
+ for (const [attributeName, attribute] of Object.entries(attributes)) {
36
+ const fromColumn = snakeCase(attributeName);
37
+ if (!fromColumn.endsWith("_id") || attribute.foreignKey === !1)
38
+ continue;
39
+ if (attribute.foreignKey && typeof attribute.foreignKey === "object") {
40
+ push({
41
+ fromTable,
42
+ fromColumn,
43
+ toTable: attribute.foreignKey.table,
44
+ toColumn: attribute.foreignKey.column ?? "id",
45
+ model: modelName
46
+ });
47
+ continue;
48
+ }
49
+ const related = meta.get(modelNameForColumn(fromColumn));
50
+ if (related)
51
+ push({ fromTable, fromColumn, toTable: related.table, toColumn: related.primaryKey, model: modelName });
52
+ }
53
+ const belongsTo = model.belongsTo, relations = Array.isArray(belongsTo) ? belongsTo : belongsTo && typeof belongsTo === "object" ? Object.keys(belongsTo).map((name) => ({ model: name })) : [];
54
+ for (const entry of relations) {
55
+ const relatedName = typeof entry === "string" ? entry : String(entry.model ?? "");
56
+ if (!relatedName)
57
+ continue;
58
+ const fromColumn = typeof entry === "object" && entry.foreignKey ? String(entry.foreignKey) : `${snakeCase(relatedName)}_id`;
59
+ if (Object.keys(attributes).some((attribute) => snakeCase(attribute) === fromColumn))
60
+ continue;
61
+ const related = meta.get(relatedName);
62
+ if (related)
63
+ push({ fromTable, fromColumn, toTable: related.table, toColumn: related.primaryKey, model: modelName });
64
+ }
65
+ }
66
+ return declared;
67
+ }
16
68
  export async function getDeclaredFKs() {
17
- const modelFiles = [
18
- ...safeGlob(path.userModelsPath("*.ts")),
19
- ...safeGlob(path.storagePath("framework/defaults/app/Models/**/*.ts"))
20
- ], declared = [];
69
+ const userModelFiles = safeGlob(path.userModelsPath("*.ts")), modelFiles = userModelFiles.length > 0 ? userModelFiles : safeGlob(path.storagePath("framework/defaults/app/Models/**/*.ts")), models = [];
21
70
  for (const modelFile of modelFiles) {
22
71
  let model;
23
72
  try {
@@ -25,36 +74,10 @@ export async function getDeclaredFKs() {
25
74
  } catch {
26
75
  continue;
27
76
  }
28
- if (!model || typeof model !== "object")
29
- continue;
30
- const fromTable = model.table || plural(snakeCase(model.name || "")), belongsTo = model.belongsTo;
31
- if (Array.isArray(belongsTo))
32
- for (const entry of belongsTo) {
33
- const related = typeof entry === "string" ? entry : entry?.model ?? "";
34
- if (!related)
35
- continue;
36
- const fromColumn = `${snakeCase(singular(related))}_id`, toTable = plural(snakeCase(related));
37
- declared.push({
38
- fromTable,
39
- fromColumn,
40
- toTable,
41
- toColumn: "id",
42
- model: model.name || ""
43
- });
44
- }
45
- else if (belongsTo && typeof belongsTo === "object")
46
- for (const related of Object.keys(belongsTo)) {
47
- const fromColumn = `${snakeCase(singular(related))}_id`, toTable = plural(snakeCase(related));
48
- declared.push({
49
- fromTable,
50
- fromColumn,
51
- toTable,
52
- toColumn: "id",
53
- model: model.name || ""
54
- });
55
- }
77
+ if (model && typeof model === "object")
78
+ models.push(model);
56
79
  }
57
- return declared;
80
+ return getDeclaredFKsFromModels(models);
58
81
  }
59
82
  export async function getLiveFKs() {
60
83
  const { db } = await import("./utils"), dialect = await currentDialect();
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/database",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.108",
5
+ "version": "0.70.109",
6
6
  "description": "The Stacks database integration.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -58,15 +58,15 @@
58
58
  "bun-query-builder": "^0.1.55"
59
59
  },
60
60
  "devDependencies": {
61
- "@stacksjs/cli": "0.70.108",
62
- "@stacksjs/config": "0.70.108",
63
- "@stacksjs/logging": "0.70.108",
64
- "@stacksjs/router": "0.70.108",
61
+ "@stacksjs/cli": "0.70.109",
62
+ "@stacksjs/config": "0.70.109",
63
+ "@stacksjs/logging": "0.70.109",
64
+ "@stacksjs/router": "0.70.109",
65
65
  "better-dx": "^0.2.16",
66
- "@stacksjs/path": "0.70.108",
67
- "@stacksjs/query-builder": "0.70.108",
68
- "@stacksjs/storage": "0.70.108",
69
- "@stacksjs/strings": "0.70.108",
70
- "@stacksjs/utils": "0.70.108"
66
+ "@stacksjs/path": "0.70.109",
67
+ "@stacksjs/query-builder": "0.70.109",
68
+ "@stacksjs/storage": "0.70.109",
69
+ "@stacksjs/strings": "0.70.109",
70
+ "@stacksjs/utils": "0.70.109"
71
71
  }
72
72
  }