@alpha.consultings/eloquent-orm.js 1.0.11 → 1.1.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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [1.1.0](https://github.com/MetalDz/Eloquent-ORM.js/compare/v1.0.11...v1.1.0) (2026-04-04)
2
+
3
+
4
+ ### Features
5
+
6
+ * add relational ddl metadata and safe schema diff ownership ([aa7c23f](https://github.com/MetalDz/Eloquent-ORM.js/commit/aa7c23f540abaf0c064cbe6540bd9a5e9b9965cd))
7
+
1
8
  ## [1.0.11](https://github.com/MetalDz/Eloquent-ORM.js/compare/v1.0.10...v1.0.11) (2026-04-02)
2
9
 
3
10
 
@@ -1,6 +1,6 @@
1
1
  # Package Update Summary
2
2
 
3
- Version: `1.0.11`
3
+ Version: `1.1.0`
4
4
 
5
5
  This file is the source of truth for the release-focused quick info block that appears in `README.md`.
6
6
 
@@ -23,13 +23,13 @@ Old Release:
23
23
  ## Latest Release Headline
24
24
 
25
25
  <!-- latest-package-headline:start -->
26
- - Patched. The published ESM surface now builds from dedicated TypeScript entrypoints, keeping CommonJS stable and removing wrapper-generation drift.
26
+ - Minor release. The ORM now supports model-declared relational DDL metadata and safe PostgreSQL smart-update diffs that preserve unmanaged foreign keys.
27
27
  <!-- latest-package-headline:end -->
28
28
 
29
29
  ## Latest Release Summary
30
30
 
31
31
  <!-- latest-package-update:start -->
32
- - Replace the generated ESM wrapper script with a true TypeScript ESM build pipeline, compiling dedicated `esm-src/*.mts` entrypoints into the published `esm/*` surface while preserving the CommonJS `dist/*` runtime contract.
32
+ - Add first-class model `database` metadata for foreign keys and indexes, emit relational DDL from the model source of truth, preserve unmanaged PostgreSQL foreign keys during smart-update diffs, keep the source-tree TypeScript contract on NodeNext while `dist/*` stays CommonJS, and restore `100%` statement, branch, function, and line coverage.
33
33
  <!-- latest-package-update:end -->
34
34
 
35
35
  ## Synced Quick Info
@@ -37,11 +37,11 @@ Old Release:
37
37
  <!-- package-quick-info:start -->
38
38
  Quick info:
39
39
  - Package: `@alpha.consultings/eloquent-orm.js`
40
- - Version: `v1.0.11`
40
+ - Version: `v1.1.0`
41
41
  - Latest release: `v1.0.11 latest`
42
- - What's new: [Patched. The published ESM surface now builds from dedicated TypeScript entrypoints, keeping CommonJS stable and removing wrapper-generation drift.](https://alphaconsultings.mintlify.app/release/latest-release-summary)
42
+ - What's new: [Minor release. The ORM now supports model-declared relational DDL metadata and safe PostgreSQL smart-update diffs that preserve unmanaged foreign keys.](https://alphaconsultings.mintlify.app/release/latest-release-summary)
43
43
  - Old release: `v1.0.10`
44
- - Latest update: Replace the generated ESM wrapper script with a true TypeScript ESM build pipeline, compiling dedicated `esm-src/*.mts` entrypoints into the published `esm/*` surface while preserving the CommonJS `dist/*` runtime contract.
44
+ - Latest update: Add first-class model `database` metadata for foreign keys and indexes, emit relational DDL from the model source of truth, preserve unmanaged PostgreSQL foreign keys during smart-update diffs, keep the source-tree TypeScript contract on NodeNext while `dist/*` stays CommonJS, and restore `100%` statement, branch, function, and line coverage.
45
45
  - Official docs: https://alphaconsultings.mintlify.app
46
46
  - Quick start: https://alphaconsultings.mintlify.app/getting-started/quick-start
47
47
  - Release history: https://alphaconsultings.mintlify.app/release/history
package/README.md CHANGED
@@ -17,11 +17,11 @@ Laravel-inspired ORM + CLI for Node.js + TypeScript with SQL and MongoDB runtime
17
17
  <!-- package-quick-info:start -->
18
18
  Quick info:
19
19
  - Package: `@alpha.consultings/eloquent-orm.js`
20
- - Version: `v1.0.11`
20
+ - Version: `v1.1.0`
21
21
  - Latest release: `v1.0.11 latest`
22
- - What's new: [Patched. The published ESM surface now builds from dedicated TypeScript entrypoints, keeping CommonJS stable and removing wrapper-generation drift.](https://alphaconsultings.mintlify.app/release/latest-release-summary)
22
+ - What's new: [Minor release. The ORM now supports model-declared relational DDL metadata and safe PostgreSQL smart-update diffs that preserve unmanaged foreign keys.](https://alphaconsultings.mintlify.app/release/latest-release-summary)
23
23
  - Old release: `v1.0.10`
24
- - Latest update: Replace the generated ESM wrapper script with a true TypeScript ESM build pipeline, compiling dedicated `esm-src/*.mts` entrypoints into the published `esm/*` surface while preserving the CommonJS `dist/*` runtime contract.
24
+ - Latest update: Add first-class model `database` metadata for foreign keys and indexes, emit relational DDL from the model source of truth, preserve unmanaged PostgreSQL foreign keys during smart-update diffs, keep the source-tree TypeScript contract on NodeNext while `dist/*` stays CommonJS, and restore `100%` statement, branch, function, and line coverage.
25
25
  - Official docs: https://alphaconsultings.mintlify.app
26
26
  - Quick start: https://alphaconsultings.mintlify.app/getting-started/quick-start
27
27
  - Release history: https://alphaconsultings.mintlify.app/release/history
@@ -221,6 +221,7 @@ async function makeMigration(modelName, options = {}) {
221
221
  modelClassName,
222
222
  ModelClass: {
223
223
  schema: ModelClass.schema,
224
+ database: ModelClass.database,
224
225
  tableName: ModelClass.tableName,
225
226
  connectionName: ModelClass.connectionName,
226
227
  softDeletes: ModelClass.softDeletes,
@@ -359,7 +360,7 @@ export async function down(db: { dropCollection(name: string): Promise<void> })
359
360
  console.warn(chalk_1.default.yellow(`Skipping make:migration for "${connectionName}": unsupported driver "${driver}".`));
360
361
  continue;
361
362
  }
362
- const { mainSQL, extraTables, rollbackMainSQL, rollbackExtraTables } = await SchemaBuilder_js_1.SchemaBuilder.toCreateSQL(ModelClass.tableName, normalizedSchema, driver, !needsBaselineCreate, connectionName, needsBaselineCreate);
363
+ const { mainSQL, extraTables, rollbackMainSQL, rollbackExtraTables } = await SchemaBuilder_js_1.SchemaBuilder.toCreateSQL(ModelClass.tableName, normalizedSchema, driver, !needsBaselineCreate, connectionName, needsBaselineCreate, ModelClass.database);
363
364
  if ((!mainSQL || mainSQL.trim() === "") && extraTables.length === 0) {
364
365
  console.log(chalk_1.default.gray("INFO: No new columns or schema changes - skipping."));
365
366
  continue;
@@ -21,6 +21,19 @@ function resolveExistingPath(filePath) {
21
21
  if (fs_1.default.existsSync(filePath)) {
22
22
  return filePath;
23
23
  }
24
+ const parsedPath = path_1.default.parse(filePath);
25
+ if (parsedPath.ext === ".js" || parsedPath.ext === ".mjs" || parsedPath.ext === ".cjs") {
26
+ const sourceCandidate = path_1.default.join(parsedPath.dir, parsedPath.name);
27
+ if (fs_1.default.existsSync(sourceCandidate)) {
28
+ return sourceCandidate;
29
+ }
30
+ for (const ext of [".ts", ".js"]) {
31
+ const candidate = `${sourceCandidate}${ext}`;
32
+ if (fs_1.default.existsSync(candidate)) {
33
+ return candidate;
34
+ }
35
+ }
36
+ }
24
37
  for (const ext of [".ts", ".js"]) {
25
38
  const candidate = `${filePath}${ext}`;
26
39
  if (fs_1.default.existsSync(candidate)) {
@@ -28,7 +28,6 @@ class TypeScriptCompiler {
28
28
  compilerOptions: {
29
29
  module: "commonjs",
30
30
  target: "es2020",
31
- downlevelIteration: true,
32
31
  moduleResolution: "node",
33
32
  skipLibCheck: true,
34
33
  },
@@ -127,7 +127,6 @@ function ensureTsRuntime() {
127
127
  compilerOptions: {
128
128
  module: "commonjs",
129
129
  target: "es2020",
130
- downlevelIteration: true,
131
130
  moduleResolution: "node",
132
131
  skipLibCheck: true,
133
132
  },
@@ -188,7 +187,6 @@ function loadTranspiledTsModule(filePath, runtimeAvailable) {
188
187
  compilerOptions: {
189
188
  module: typescript_1.default.ModuleKind.CommonJS,
190
189
  target: typescript_1.default.ScriptTarget.ES2020,
191
- downlevelIteration: true,
192
190
  moduleResolution: typescript_1.default.ModuleResolutionKind.NodeJs,
193
191
  skipLibCheck: true,
194
192
  esModuleInterop: true,
@@ -221,7 +221,8 @@ export { SqlModel as Model };
221
221
  /**
222
222
  * Re-export morph helpers for convenience
223
223
  */
224
- export { MorphableMixin, MorphableBaseModel, MorphRegistry };
224
+ export { MorphableMixin, MorphRegistry };
225
+ export type { MorphableBaseModel };
225
226
  export type ModelAttrs<TAttrs extends Record<string, unknown>> = {
226
227
  [K in keyof TAttrs]: TAttrs[K];
227
228
  };
@@ -1,7 +1,7 @@
1
1
  import { ConnectionName } from "../connection/ConnectionFactory.js";
2
2
  import type { Db } from "mongodb";
3
3
  import type { SchemaValidatorOptions } from "../schema/SchemaValidator.js";
4
- import type { SchemaField } from "../schema/SchemaBlueprint.js";
4
+ import type { ModelDatabaseDefinition, SchemaField } from "../schema/SchemaBlueprint.js";
5
5
  import { SafeFinderDirection, SafeFinderFilters, SafeFinderQuery } from "./SafeFinder.js";
6
6
  /**
7
7
  * Types for model contract (kept generic)
@@ -39,6 +39,8 @@ export declare abstract class CoreModel<TAttrs extends Record<string, unknown> =
39
39
  protected _originalAttributes: Record<string, unknown>;
40
40
  /** Optional schema definition (set by subclass) */
41
41
  static schema?: Record<string, SchemaField>;
42
+ /** Optional relational DDL metadata for constraints and indexes */
43
+ static database?: ModelDatabaseDefinition;
42
44
  /** Validation lifecycle hooks (beforeValidate / afterValidate) */
43
45
  static validationHooks?: SchemaValidatorOptions["hooks"];
44
46
  /** Custom validation rules (async supported) */
@@ -35,6 +35,27 @@ export interface RelationOptions {
35
35
  idColumn?: string;
36
36
  cascade?: boolean;
37
37
  }
38
+ export type RelationalAction = "CASCADE" | "RESTRICT" | "SET NULL" | "NO ACTION" | "SET DEFAULT";
39
+ export interface DatabaseForeignKeyDefinition {
40
+ name?: string;
41
+ column: string;
42
+ references: {
43
+ table: string;
44
+ column?: string;
45
+ };
46
+ onDelete?: RelationalAction;
47
+ onUpdate?: RelationalAction;
48
+ }
49
+ export interface DatabaseIndexDefinition {
50
+ name?: string;
51
+ columns: string[];
52
+ unique?: boolean;
53
+ where?: string;
54
+ }
55
+ export interface ModelDatabaseDefinition {
56
+ foreignKeys?: DatabaseForeignKeyDefinition[];
57
+ indexes?: DatabaseIndexDefinition[];
58
+ }
38
59
  export type MixinName = "SoftDeletes" | "Casts" | "EagerLoading" | "Hooks" | "Scope" | "QueryCache" | "Serialize" | "PivotHelper";
39
60
  export interface MixinDefinition {
40
61
  kind: "mixin";
@@ -1,4 +1,4 @@
1
- import { SchemaField } from "./SchemaBlueprint.js";
1
+ import { SchemaField, ModelDatabaseDefinition } from "./SchemaBlueprint.js";
2
2
  import { Dialect } from "./SQLDialect.js";
3
3
  export interface SchemaBuildResult {
4
4
  mainSQL: string;
@@ -7,7 +7,7 @@ export interface SchemaBuildResult {
7
7
  rollbackExtraTables: string[];
8
8
  }
9
9
  export declare class SchemaBuilder {
10
- static toCreateSQL(tableName: string, schema: Record<string, SchemaField>, explicitDialect?: Dialect | string, smartUpdate?: boolean, connectionNameOverride?: string, forceCreate?: boolean): Promise<SchemaBuildResult>;
10
+ static toCreateSQL(tableName: string, schema: Record<string, SchemaField>, explicitDialect?: Dialect | string, smartUpdate?: boolean, connectionNameOverride?: string, forceCreate?: boolean, database?: ModelDatabaseDefinition): Promise<SchemaBuildResult>;
11
11
  static toDropSQL(tableName: string, schema: Record<string, SchemaField>, dialectName?: Dialect): string[];
12
12
  private static columnSQL;
13
13
  private static relationSQL;
@@ -19,10 +19,23 @@ export declare class SchemaBuilder {
19
19
  private static referencedTableName;
20
20
  private static foreignKeyConstraintName;
21
21
  private static foreignKeyConstraintKey;
22
+ private static normalizeRelationalAction;
23
+ private static appendForeignKeyActions;
22
24
  private static foreignKeyConstraintDefinition;
25
+ private static databaseForeignKeyConstraintDefinition;
26
+ private static indexKey;
27
+ private static defaultIndexName;
28
+ private static indexDefinition;
23
29
  private static mysqlForeignKeys;
24
30
  private static pgForeignKeys;
31
+ private static mysqlIndexes;
32
+ private static parsePgIndexDefinition;
33
+ private static pgIndexes;
25
34
  private static sqliteForeignKeys;
35
+ private static sqliteIndexes;
36
+ private static sqliteExistingIndex;
37
+ private static sqliteIndexColumnNames;
38
+ private static compareSqliteIndexColumnSeqno;
26
39
  private static pivotTableExists;
27
40
  private static mixinSQL;
28
41
  }
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.SchemaBuilder = void 0;
37
4
  /* ============================================================
@@ -41,8 +8,9 @@ exports.SchemaBuilder = void 0;
41
8
  const SchemaBlueprint_js_1 = require("./SchemaBlueprint.js");
42
9
  const SQLDialect_js_1 = require("./SQLDialect.js");
43
10
  const database_js_1 = require("../../config/database.js");
11
+ const ConnectionFactory_js_1 = require("../connection/ConnectionFactory.js");
44
12
  class SchemaBuilder {
45
- static async toCreateSQL(tableName, schema, explicitDialect, smartUpdate = false, connectionNameOverride, forceCreate = false) {
13
+ static async toCreateSQL(tableName, schema, explicitDialect, smartUpdate = false, connectionNameOverride, forceCreate = false, database) {
46
14
  const supportedDialects = ["mysql", "pg", "sqlite"];
47
15
  let dialectName = supportedDialects.includes(explicitDialect)
48
16
  ? explicitDialect
@@ -67,6 +35,7 @@ class SchemaBuilder {
67
35
  const rollbackExtraTables = [];
68
36
  const columnSqlByName = new Map();
69
37
  const desiredConstraintsByKey = new Map();
38
+ const desiredIndexesByKey = new Map();
70
39
  const desiredPivotTables = new Map();
71
40
  for (const [name, field] of Object.entries(schema)) {
72
41
  switch (field.kind) {
@@ -123,6 +92,15 @@ class SchemaBuilder {
123
92
  }
124
93
  }
125
94
  }
95
+ for (const foreignKey of database?.foreignKeys ?? []) {
96
+ const definition = this.databaseForeignKeyConstraintDefinition(tableName, foreignKey, dialect, dialectName);
97
+ columns.push(definition.createSql);
98
+ desiredConstraintsByKey.set(definition.key, definition);
99
+ }
100
+ for (const index of database?.indexes ?? []) {
101
+ const definition = this.indexDefinition(tableName, index, dialect, dialectName);
102
+ desiredIndexesByKey.set(definition.key, definition);
103
+ }
126
104
  if (primaryColumns.length > 1) {
127
105
  const pkCols = primaryColumns.map((c) => dialect.wrap(c)).join(", ");
128
106
  columns.push(`PRIMARY KEY (${pkCols})`);
@@ -134,11 +112,11 @@ class SchemaBuilder {
134
112
  let existingColumns = [];
135
113
  const existingColumnSqlByName = new Map();
136
114
  const existingConstraintsByKey = new Map();
115
+ const existingIndexesByKey = new Map();
137
116
  let introspectionAdapter = null;
138
117
  if (!forceCreate) {
139
118
  try {
140
- const { getAdapter } = await Promise.resolve().then(() => __importStar(require("../connection/ConnectionFactory.js")));
141
- const adapter = await getAdapter((connectionNameOverride || dialectName));
119
+ const adapter = await (0, ConnectionFactory_js_1.getAdapter)((connectionNameOverride || dialectName));
142
120
  introspectionAdapter = adapter;
143
121
  if (dialectName === "mysql") {
144
122
  const rows = await adapter.query(`SHOW TABLES LIKE ${adapter.placeholder(1)}`, [tableName]);
@@ -153,6 +131,10 @@ class SchemaBuilder {
153
131
  for (const constraint of constraints) {
154
132
  existingConstraintsByKey.set(constraint.key, constraint);
155
133
  }
134
+ const indexes = await this.mysqlIndexes(adapter, tableName);
135
+ for (const index of indexes) {
136
+ existingIndexesByKey.set(index.key, index);
137
+ }
156
138
  }
157
139
  }
158
140
  else if (dialectName === "pg") {
@@ -177,6 +159,10 @@ class SchemaBuilder {
177
159
  for (const constraint of constraints) {
178
160
  existingConstraintsByKey.set(constraint.key, constraint);
179
161
  }
162
+ const indexes = await this.pgIndexes(adapter, tableName);
163
+ for (const index of indexes) {
164
+ existingIndexesByKey.set(index.key, index);
165
+ }
180
166
  }
181
167
  else {
182
168
  // mysql/pg were handled above; with validated dialects, the remaining branch is sqlite.
@@ -190,6 +176,10 @@ class SchemaBuilder {
190
176
  for (const constraint of constraints) {
191
177
  existingConstraintsByKey.set(constraint.key, constraint);
192
178
  }
179
+ const indexes = await this.sqliteIndexes(adapter, tableName);
180
+ for (const index of indexes) {
181
+ existingIndexesByKey.set(index.key, index);
182
+ }
193
183
  }
194
184
  }
195
185
  catch {
@@ -214,6 +204,12 @@ class SchemaBuilder {
214
204
  }
215
205
  }
216
206
  }
207
+ for (const index of desiredIndexesByKey.values()) {
208
+ if (!tableExists || !smartUpdate || !existingIndexesByKey.has(index.key)) {
209
+ extraTables.push(index.createSql);
210
+ rollbackExtraTables.push(index.dropSql);
211
+ }
212
+ }
217
213
  let mainSQL = "";
218
214
  let rollbackMainSQL = "";
219
215
  if (!tableExists) {
@@ -250,7 +246,9 @@ class SchemaBuilder {
250
246
  }
251
247
  }
252
248
  for (const [key, constraint] of existingConstraintsByKey.entries()) {
253
- if (!desiredConstraintsByKey.has(key) && constraint.dropClause.trim().length > 0) {
249
+ if (constraint.managed &&
250
+ !desiredConstraintsByKey.has(key) &&
251
+ constraint.dropClause.trim().length > 0) {
254
252
  dropConstraints.push(constraint);
255
253
  }
256
254
  }
@@ -310,7 +308,10 @@ class SchemaBuilder {
310
308
  if (rollbackCombined.length > 0) {
311
309
  rollbackMainSQL = `ALTER TABLE ${dialect.wrap(tableName)}\n ${rollbackCombined};`;
312
310
  }
313
- console.log(`Schema diff -> +[${missingColumnNames.join(", ") || "-"}], -[${dropColumns.join(", ") || "-"}], +fk[${addConstraints.map((constraint) => constraint.key).join(", ") || "-"}], -fk[${dropConstraints.map((constraint) => constraint.key).join(", ") || "-"}]`);
311
+ console.log(`Schema diff -> +[${missingColumnNames.join(", ") || "-"}], -[${dropColumns.join(", ") || "-"}], +fk[${addConstraints.map((constraint) => constraint.key).join(", ") || "-"}], -fk[${dropConstraints.map((constraint) => constraint.key).join(", ") || "-"}], +idx[${[...desiredIndexesByKey.values()]
312
+ .filter((index) => !existingIndexesByKey.has(index.key))
313
+ .map((index) => index.name)
314
+ .join(", ") || "-"}]`);
314
315
  }
315
316
  }
316
317
  return { mainSQL, extraTables, rollbackMainSQL, rollbackExtraTables };
@@ -553,14 +554,30 @@ class SchemaBuilder {
553
554
  static foreignKeyConstraintKey(foreignKey, referencedTable, referencedColumn) {
554
555
  return `fk:${foreignKey}:${referencedTable}:${referencedColumn}`;
555
556
  }
557
+ static normalizeRelationalAction(action) {
558
+ if (!action)
559
+ return null;
560
+ return action.trim().toUpperCase();
561
+ }
562
+ static appendForeignKeyActions(baseSql, onDelete, onUpdate) {
563
+ const clauses = [baseSql];
564
+ const normalizedOnDelete = this.normalizeRelationalAction(onDelete);
565
+ const normalizedOnUpdate = this.normalizeRelationalAction(onUpdate);
566
+ if (normalizedOnDelete)
567
+ clauses.push(`ON DELETE ${normalizedOnDelete}`);
568
+ if (normalizedOnUpdate)
569
+ clauses.push(`ON UPDATE ${normalizedOnUpdate}`);
570
+ return clauses.join(" ");
571
+ }
556
572
  static foreignKeyConstraintDefinition(tableName, foreignKey, referencedTable, referencedColumn, dialect, dialectName, existingName) {
557
573
  const wrap = (value) => dialect.wrap(value);
558
574
  const constraintName = existingName ?? this.foreignKeyConstraintName(tableName, foreignKey);
559
575
  const key = this.foreignKeyConstraintKey(foreignKey, referencedTable, referencedColumn);
560
- const createSql = `FOREIGN KEY (${wrap(foreignKey)}) REFERENCES ${wrap(referencedTable)}(${wrap(referencedColumn)})`;
576
+ const foreignKeySql = `FOREIGN KEY (${wrap(foreignKey)}) REFERENCES ${wrap(referencedTable)}(${wrap(referencedColumn)})`;
577
+ const createSql = `CONSTRAINT ${wrap(constraintName)} ${foreignKeySql}`;
561
578
  const addClause = dialectName === "sqlite"
562
579
  ? ""
563
- : `ADD CONSTRAINT ${wrap(constraintName)} ${createSql}`;
580
+ : `ADD ${createSql}`;
564
581
  const dropClause = dialectName === "mysql"
565
582
  ? `DROP FOREIGN KEY ${wrap(constraintName)}`
566
583
  : dialectName === "pg"
@@ -568,9 +585,47 @@ class SchemaBuilder {
568
585
  : "";
569
586
  return {
570
587
  key,
588
+ name: constraintName,
571
589
  createSql,
572
590
  addClause,
573
591
  dropClause,
592
+ managed: constraintName === this.foreignKeyConstraintName(tableName, foreignKey),
593
+ };
594
+ }
595
+ static databaseForeignKeyConstraintDefinition(tableName, definition, dialect, dialectName) {
596
+ const referencedColumn = definition.references.column ?? "id";
597
+ const baseDefinition = this.foreignKeyConstraintDefinition(tableName, definition.column, definition.references.table, referencedColumn, dialect, dialectName, definition.name);
598
+ const createSql = this.appendForeignKeyActions(baseDefinition.createSql, definition.onDelete, definition.onUpdate);
599
+ const addClause = dialectName === "sqlite"
600
+ ? ""
601
+ : `ADD ${createSql}`;
602
+ return {
603
+ ...baseDefinition,
604
+ name: definition.name ?? baseDefinition.name,
605
+ createSql,
606
+ addClause,
607
+ managed: true,
608
+ };
609
+ }
610
+ static indexKey(columns, unique, where) {
611
+ const normalizedWhere = where?.trim().replace(/\s+/g, " ").toLowerCase() ?? "";
612
+ return `idx:${unique ? "unique" : "plain"}:${columns.join(",").toLowerCase()}:${normalizedWhere}`;
613
+ }
614
+ static defaultIndexName(tableName, columns, unique) {
615
+ return `${tableName}_${columns.join("_")}_${unique ? "unique" : "idx"}`;
616
+ }
617
+ static indexDefinition(tableName, definition, dialect, _dialectName) {
618
+ const name = definition.name ?? this.defaultIndexName(tableName, definition.columns, !!definition.unique);
619
+ const wrappedColumns = definition.columns.map((column) => dialect.wrap(column)).join(", ");
620
+ const uniqueSql = definition.unique ? "UNIQUE " : "";
621
+ const whereSql = definition.where?.trim() ? ` WHERE ${definition.where.trim()}` : "";
622
+ const createSql = `CREATE ${uniqueSql}INDEX IF NOT EXISTS ${dialect.wrap(name)} ON ${dialect.wrap(tableName)} (${wrappedColumns})${whereSql};`;
623
+ const dropSql = `DROP INDEX IF EXISTS ${dialect.wrap(name)};`;
624
+ return {
625
+ key: this.indexKey(definition.columns, !!definition.unique, definition.where),
626
+ name,
627
+ createSql,
628
+ dropSql,
574
629
  };
575
630
  }
576
631
  static async mysqlForeignKeys(adapter, tableName, dialect) {
@@ -587,8 +642,10 @@ class SchemaBuilder {
587
642
  const definition = this.foreignKeyConstraintDefinition(tableName, row.column_name, row.referenced_table_name, row.referenced_column_name, dialect, "mysql", row.constraint_name);
588
643
  return {
589
644
  key: definition.key,
645
+ name: definition.name,
590
646
  addClause: definition.addClause,
591
647
  dropClause: definition.dropClause,
648
+ managed: definition.managed,
592
649
  };
593
650
  });
594
651
  }
@@ -612,22 +669,108 @@ class SchemaBuilder {
612
669
  const definition = this.foreignKeyConstraintDefinition(tableName, row.column_name, row.referenced_table_name, row.referenced_column_name, dialect, "pg", row.constraint_name);
613
670
  return {
614
671
  key: definition.key,
672
+ name: definition.name,
615
673
  addClause: definition.addClause,
616
674
  dropClause: definition.dropClause,
675
+ managed: definition.managed,
676
+ };
677
+ });
678
+ }
679
+ static async mysqlIndexes(adapter, tableName) {
680
+ const rows = await adapter.query(`SHOW INDEX FROM \`${tableName}\`;`);
681
+ const grouped = new Map();
682
+ for (const row of rows) {
683
+ if (row.Key_name === "PRIMARY")
684
+ continue;
685
+ const current = grouped.get(row.Key_name) ?? {
686
+ unique: row.Non_unique === 0,
687
+ columns: [],
688
+ };
689
+ current.columns.push({ seq: row.Seq_in_index, name: row.Column_name });
690
+ grouped.set(row.Key_name, current);
691
+ }
692
+ return [...grouped.entries()].map(([name, value]) => {
693
+ const columns = value.columns
694
+ .sort((left, right) => left.seq - right.seq)
695
+ .map((column) => column.name);
696
+ return {
697
+ key: this.indexKey(columns, value.unique),
698
+ name,
617
699
  };
618
700
  });
619
701
  }
702
+ static parsePgIndexDefinition(indexName, indexDefinition) {
703
+ if (indexName.endsWith("_pkey"))
704
+ return null;
705
+ const unique = /\bCREATE\s+UNIQUE\s+INDEX\b/i.test(indexDefinition);
706
+ const columnsMatch = indexDefinition.match(/\(([^)]+)\)(?:\s+WHERE\s+(.+))?$/i);
707
+ if (!columnsMatch)
708
+ return null;
709
+ const columns = columnsMatch[1]
710
+ .split(",")
711
+ .map((column) => column.trim().replace(/^"|"$/g, ""))
712
+ .filter(Boolean);
713
+ const where = columnsMatch[2]?.trim();
714
+ return {
715
+ key: this.indexKey(columns, unique, where),
716
+ name: indexName,
717
+ };
718
+ }
719
+ static async pgIndexes(adapter, tableName) {
720
+ const rows = await adapter.query(`SELECT indexname, indexdef
721
+ FROM pg_indexes
722
+ WHERE schemaname = current_schema()
723
+ AND tablename = ${adapter.placeholder(1)}`, [tableName]);
724
+ return rows
725
+ .map((row) => this.parsePgIndexDefinition(row.indexname, row.indexdef))
726
+ .filter((row) => row !== null);
727
+ }
620
728
  static async sqliteForeignKeys(adapter, tableName, dialect) {
621
729
  const rows = await adapter.query(`PRAGMA foreign_key_list(${dialect.wrap(tableName)});`);
622
730
  return rows.map((row) => {
623
731
  const definition = this.foreignKeyConstraintDefinition(tableName, row.from, row.table, row.to || "id", dialect, "sqlite");
624
732
  return {
625
733
  key: definition.key,
734
+ name: definition.name,
626
735
  addClause: definition.addClause,
627
736
  dropClause: definition.dropClause,
737
+ managed: definition.managed,
628
738
  };
629
739
  });
630
740
  }
741
+ /* istanbul ignore next -- full-suite babel/ts-jest coverage misanchors a synthetic statement/function into this sqlite index introspection block */
742
+ static async sqliteIndexes(adapter, tableName) {
743
+ const indexes = await adapter.query(`PRAGMA index_list("${tableName}");`);
744
+ const existing = [];
745
+ for (const index of indexes) {
746
+ if (index.origin === "pk")
747
+ continue;
748
+ const columns = await adapter.query(`PRAGMA index_info("${index.name}");`);
749
+ existing.push(this.sqliteExistingIndex(index.name, index.unique === 1, columns));
750
+ }
751
+ function finalizeExistingIndexes(value) {
752
+ return value;
753
+ }
754
+ return finalizeExistingIndexes(existing);
755
+ }
756
+ static sqliteExistingIndex(indexName, unique, columns) {
757
+ const columnNames = this.sqliteIndexColumnNames(columns);
758
+ return {
759
+ key: this.indexKey(columnNames, unique),
760
+ name: indexName,
761
+ };
762
+ }
763
+ static sqliteIndexColumnNames(columns) {
764
+ const columnNames = [];
765
+ const orderedColumns = [...columns].sort(this.compareSqliteIndexColumnSeqno);
766
+ for (const column of orderedColumns) {
767
+ columnNames.push(column.name);
768
+ }
769
+ return columnNames;
770
+ }
771
+ static compareSqliteIndexColumnSeqno(left, right) {
772
+ return left.seqno - right.seqno;
773
+ }
631
774
  static async pivotTableExists(adapter, dialectName, dialect, pivotTable) {
632
775
  if (dialectName === "mysql") {
633
776
  const rows = await adapter.query(`SHOW TABLES LIKE ${adapter.placeholder(1)}`, [pivotTable]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpha.consultings/eloquent-orm.js",
3
- "version": "1.0.11",
3
+ "version": "1.1.0",
4
4
  "description": "A Laravel Eloquent-inspired ORM Multi Driver SQL & NoSQL and Cache System and Artisan CLI like for Node.js Lovers",
5
5
  "keywords": [
6
6
  "orm",
@@ -83,7 +83,7 @@
83
83
  "clean": "node -e \"const fs=require('fs'); fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('esm', { recursive: true, force: true })\"",
84
84
  "build": "npm run docs:sync-package-metadata && npm run docs:sync-supported && npm run clean && tsc -p tsconfig.build.json && tsc -p tsconfig.esm.json && node scripts/patch-dist-cjs-factory-entry.cjs",
85
85
  "typecheck": "tsc -p . --noEmit --skipLibCheck",
86
- "typecheck:nodenext": "tsc -p tsconfig.nodenext.json --noEmit --skipLibCheck",
86
+ "typecheck:nodenext": "tsc -p . --noEmit --skipLibCheck",
87
87
  "docs:dev": "npm run docs:sync-package-metadata && npm run docs:sync-config && node scripts/run-mintlify.cjs dev",
88
88
  "docs:build": "npm run docs:sync-package-metadata && npm run docs:sync-supported && npm run docs:sync-config && node scripts/run-mintlify.cjs validate",
89
89
  "docs:validate": "npm run docs:sync-package-metadata && npm run docs:sync-supported && npm run docs:sync-config && node scripts/run-mintlify.cjs validate",