@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 +7 -0
- package/PACKAGE-UPDATE-SUMMARY.md +6 -6
- package/README.md +3 -3
- package/dist/cli/commands/makeMigration.js +2 -1
- package/dist/cli/utils/ArtifactStorage.js +13 -0
- package/dist/cli/utils/typescript/TypeScriptCompiler.js +0 -1
- package/dist/cli/utils/typescript/tsRuntime.js +0 -2
- package/dist/core/model/BaseModel.d.ts +2 -1
- package/dist/core/model/CoreModel.d.ts +3 -1
- package/dist/core/schema/SchemaBlueprint.d.ts +21 -0
- package/dist/core/schema/SchemaBuilder.d.ts +15 -2
- package/dist/core/schema/SchemaBuilder.js +183 -40
- package/package.json +2 -2
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
40
|
+
- Version: `v1.1.0`
|
|
41
41
|
- Latest release: `v1.0.11 latest`
|
|
42
|
-
- What's new: [
|
|
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:
|
|
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
|
|
20
|
+
- Version: `v1.1.0`
|
|
21
21
|
- Latest release: `v1.0.11 latest`
|
|
22
|
-
- What's new: [
|
|
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:
|
|
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)) {
|
|
@@ -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,
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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",
|