forge-sql-orm 1.0.1
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/LICENSE +21 -0
- package/README.md +629 -0
- package/dist/core/ForgeSQLCrudOperations.d.ts +45 -0
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -0
- package/dist/core/ForgeSQLORM.d.ts +31 -0
- package/dist/core/ForgeSQLORM.d.ts.map +1 -0
- package/dist/core/ForgeSQLQueryBuilder.d.ts +83 -0
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -0
- package/dist/core/ForgeSQLSelectOperations.d.ts +25 -0
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +332 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +321 -0
- package/dist/index.mjs.map +1 -0
- package/dist/knex/index.d.ts +4 -0
- package/dist/knex/index.d.ts.map +1 -0
- package/dist/utils/sqlUtils.d.ts +8 -0
- package/dist/utils/sqlUtils.d.ts.map +1 -0
- package/package.json +92 -0
- package/scripts/actions/PatchPostinstall.ts +201 -0
- package/scripts/actions/generate-models.ts +65 -0
- package/scripts/actions/migrations-create.ts +192 -0
- package/scripts/actions/migrations-update.ts +200 -0
- package/scripts/cli.js +4 -0
- package/scripts/cli.ts +221 -0
- package/src/core/ForgeSQLCrudOperations.ts +164 -0
- package/src/core/ForgeSQLORM.ts +150 -0
- package/src/core/ForgeSQLQueryBuilder.ts +100 -0
- package/src/core/ForgeSQLSelectOperations.ts +72 -0
- package/src/index.ts +9 -0
- package/src/knex/index.ts +4 -0
- package/src/utils/sqlUtils.ts +25 -0
- package/tsconfig.json +24 -0
package/scripts/cli.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
import inquirer from "inquirer";
|
|
6
|
+
import { generateModels } from "./actions/generate-models";
|
|
7
|
+
import { createMigration } from "./actions/migrations-create";
|
|
8
|
+
import { updateMigration } from "./actions/migrations-update";
|
|
9
|
+
import {runPostInstallPatch} from "./actions/PatchPostinstall";
|
|
10
|
+
|
|
11
|
+
// 🔄 Load environment variables from `.env` file
|
|
12
|
+
dotenv.config();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Prompts the user for missing parameters using Inquirer.js.
|
|
16
|
+
* @param config - The current configuration object.
|
|
17
|
+
* @param defaultOutput - Default output path.
|
|
18
|
+
* @param customAskMissingParams - Optional function for additional prompts.
|
|
19
|
+
* @returns Updated configuration with user input.
|
|
20
|
+
*/
|
|
21
|
+
const askMissingParams = async (
|
|
22
|
+
config: any,
|
|
23
|
+
defaultOutput: string,
|
|
24
|
+
customAskMissingParams?: (cfg: any, questions: unknown[]) => void,
|
|
25
|
+
) => {
|
|
26
|
+
const questions: unknown[] = [];
|
|
27
|
+
|
|
28
|
+
if (!config.host)
|
|
29
|
+
questions.push({
|
|
30
|
+
type: "input",
|
|
31
|
+
name: "host",
|
|
32
|
+
message: "Enter database host:",
|
|
33
|
+
default: "localhost",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!config.port)
|
|
37
|
+
questions.push({
|
|
38
|
+
type: "input",
|
|
39
|
+
name: "port",
|
|
40
|
+
message: "Enter database port:",
|
|
41
|
+
default: "3306",
|
|
42
|
+
validate: (input: string) => !isNaN(parseInt(input, 10)),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!config.user)
|
|
46
|
+
questions.push({
|
|
47
|
+
type: "input",
|
|
48
|
+
name: "user",
|
|
49
|
+
message: "Enter database user:",
|
|
50
|
+
default: "root",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!config.password)
|
|
54
|
+
questions.push({
|
|
55
|
+
type: "password",
|
|
56
|
+
name: "password",
|
|
57
|
+
message: "Enter database password:",
|
|
58
|
+
mask: "*",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!config.dbName)
|
|
62
|
+
questions.push({
|
|
63
|
+
type: "input",
|
|
64
|
+
name: "dbName",
|
|
65
|
+
message: "Enter database name:",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!config.output)
|
|
69
|
+
questions.push({
|
|
70
|
+
type: "input",
|
|
71
|
+
name: "output",
|
|
72
|
+
message: "Enter output path:",
|
|
73
|
+
default: defaultOutput,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Allow additional questions from the caller
|
|
77
|
+
if (customAskMissingParams) {
|
|
78
|
+
customAskMissingParams(config, questions);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// If there are missing parameters, prompt the user
|
|
82
|
+
if (questions.length > 0) {
|
|
83
|
+
// @ts-ignore - Ignore TypeScript warning for dynamic question type
|
|
84
|
+
const answers = await inquirer.prompt(questions);
|
|
85
|
+
return { ...config, ...answers, port: parseInt(answers.port, 10) };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return config;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Retrieves configuration parameters from command-line arguments and environment variables.
|
|
93
|
+
* If any required parameters are missing, prompts the user for input.
|
|
94
|
+
* @param cmd - The command object containing CLI options.
|
|
95
|
+
* @param defaultOutput - Default output directory.
|
|
96
|
+
* @param customConfig - Optional function for additional configuration parameters.
|
|
97
|
+
* @param customAskMissingParams - Optional function for additional prompts.
|
|
98
|
+
* @returns A fully resolved configuration object.
|
|
99
|
+
*/
|
|
100
|
+
const getConfig = async (
|
|
101
|
+
cmd: any,
|
|
102
|
+
defaultOutput: string,
|
|
103
|
+
customConfig?: () => any,
|
|
104
|
+
customAskMissingParams?: (cfg: any, questions: unknown[]) => void,
|
|
105
|
+
) => {
|
|
106
|
+
let config = {
|
|
107
|
+
host: cmd.host || process.env.FORGE_SQL_ORM_HOST,
|
|
108
|
+
port: cmd.port
|
|
109
|
+
? parseInt(cmd.port, 10)
|
|
110
|
+
: process.env.FORGE_SQL_ORM_PORT
|
|
111
|
+
? parseInt(process.env.FORGE_SQL_ORM_PORT, 10)
|
|
112
|
+
: undefined,
|
|
113
|
+
user: cmd.user || process.env.FORGE_SQL_ORM_USER,
|
|
114
|
+
password: cmd.password || process.env.FORGE_SQL_ORM_PASSWORD,
|
|
115
|
+
dbName: cmd.dbName || process.env.FORGE_SQL_ORM_DBNAME,
|
|
116
|
+
output: cmd.output || process.env.FORGE_SQL_ORM_OUTPUT,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Merge additional configurations if provided
|
|
120
|
+
if (customConfig) {
|
|
121
|
+
config = { ...config, ...customConfig() };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return await askMissingParams(config, defaultOutput, customAskMissingParams);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// 📌 Initialize CLI
|
|
128
|
+
const program = new Command();
|
|
129
|
+
program.version("1.0.0");
|
|
130
|
+
|
|
131
|
+
// ✅ Command: Generate database models (Entities)
|
|
132
|
+
program
|
|
133
|
+
.command("generate:model")
|
|
134
|
+
.description("Generate MikroORM models from the database.")
|
|
135
|
+
.option("--host <string>", "Database host")
|
|
136
|
+
.option("--port <number>", "Database port")
|
|
137
|
+
.option("--user <string>", "Database user")
|
|
138
|
+
.option("--password <string>", "Database password")
|
|
139
|
+
.option("--dbName <string>", "Database name")
|
|
140
|
+
.option("--output <string>", "Output path for entities")
|
|
141
|
+
.action(async (cmd) => {
|
|
142
|
+
const config = await getConfig(cmd, "./database/entities");
|
|
143
|
+
await generateModels(config);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// ✅ Command: Create initial database migration
|
|
147
|
+
program
|
|
148
|
+
.command("migrations:create")
|
|
149
|
+
.description("Generate an initial migration for the entire database.")
|
|
150
|
+
.option("--host <string>", "Database host")
|
|
151
|
+
.option("--port <number>", "Database port")
|
|
152
|
+
.option("--user <string>", "Database user")
|
|
153
|
+
.option("--password <string>", "Database password")
|
|
154
|
+
.option("--dbName <string>", "Database name")
|
|
155
|
+
.option("--output <string>", "Output path for migrations")
|
|
156
|
+
.option("--entitiesPath <string>", "Path to the folder containing entities")
|
|
157
|
+
.action(async (cmd) => {
|
|
158
|
+
const config = await getConfig(
|
|
159
|
+
cmd,
|
|
160
|
+
"./database/migration",
|
|
161
|
+
() => ({
|
|
162
|
+
entitiesPath: cmd.entitiesPath || process.env.FORGE_SQL_ORM_ENTITIES_PATH,
|
|
163
|
+
}),
|
|
164
|
+
(cfg, questions: unknown[]) => {
|
|
165
|
+
if (!cfg.entitiesPath)
|
|
166
|
+
questions.push({
|
|
167
|
+
type: "input",
|
|
168
|
+
name: "entitiesPath",
|
|
169
|
+
message: "Enter the path to entities:",
|
|
170
|
+
default: "./database/entities",
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
await createMigration(config);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ✅ Command: Update migration for schema changes
|
|
178
|
+
program
|
|
179
|
+
.command("migrations:update")
|
|
180
|
+
.description("Generate a migration to update the database schema.")
|
|
181
|
+
.option("--host <string>", "Database host")
|
|
182
|
+
.option("--port <number>", "Database port")
|
|
183
|
+
.option("--user <string>", "Database user")
|
|
184
|
+
.option("--password <string>", "Database password")
|
|
185
|
+
.option("--dbName <string>", "Database name")
|
|
186
|
+
.option("--output <string>", "Output path for migrations")
|
|
187
|
+
.option("--entitiesPath <string>", "Path to the folder containing entities")
|
|
188
|
+
.action(async (cmd) => {
|
|
189
|
+
const config = await getConfig(
|
|
190
|
+
cmd,
|
|
191
|
+
"./database/migration",
|
|
192
|
+
() => ({
|
|
193
|
+
entitiesPath: cmd.entitiesPath || process.env.FORGE_SQL_ORM_ENTITIES_PATH,
|
|
194
|
+
}),
|
|
195
|
+
(cfg, questions: unknown[]) => {
|
|
196
|
+
if (!cfg.entitiesPath)
|
|
197
|
+
questions.push({
|
|
198
|
+
type: "input",
|
|
199
|
+
name: "entitiesPath",
|
|
200
|
+
message: "Enter the path to entities:",
|
|
201
|
+
default: "./database/entities",
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
await updateMigration(config);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Patch MikroORM and Knex
|
|
209
|
+
program
|
|
210
|
+
.command("patch:mikroorm")
|
|
211
|
+
.description("Patch MikroORM and Knex dependencies to work properly with Forge")
|
|
212
|
+
.action(async () => {
|
|
213
|
+
console.log("Running MikroORM patch...");
|
|
214
|
+
await runPostInstallPatch();
|
|
215
|
+
await runPostInstallPatch();
|
|
216
|
+
await runPostInstallPatch();
|
|
217
|
+
console.log("✅ MikroORM patch applied successfully!");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// 🔥 Execute CLI
|
|
221
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { UpdateQueryResponse, sql } from "@forge/sql";
|
|
2
|
+
import { EntityProperty, EntitySchema } from "..";
|
|
3
|
+
import type { types } from "@mikro-orm/core/types";
|
|
4
|
+
import { transformValue } from "../utils/sqlUtils";
|
|
5
|
+
import { CRUDForgeSQL, ForgeSqlOperation } from "./ForgeSQLQueryBuilder";
|
|
6
|
+
|
|
7
|
+
export class ForgeSQLCrudOperations implements CRUDForgeSQL {
|
|
8
|
+
private readonly forgeOperations: ForgeSqlOperation;
|
|
9
|
+
|
|
10
|
+
constructor(forgeSqlOperations: ForgeSqlOperation) {
|
|
11
|
+
this.forgeOperations = forgeSqlOperations;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generates an SQL insert query with values.
|
|
16
|
+
* @param schema - The entity schema.
|
|
17
|
+
* @param models - The list of entities to insert.
|
|
18
|
+
* @param updateIfExists - Whether to update the row if it already exists.
|
|
19
|
+
* @returns An object containing the SQL query, fields, and values.
|
|
20
|
+
*/
|
|
21
|
+
private async generateInsertScript<T extends object>(
|
|
22
|
+
schema: EntitySchema<T>,
|
|
23
|
+
models: T[],
|
|
24
|
+
updateIfExists: boolean,
|
|
25
|
+
): Promise<{
|
|
26
|
+
sql: string;
|
|
27
|
+
fields: string[];
|
|
28
|
+
values: { type: keyof typeof types; value: unknown }[];
|
|
29
|
+
}> {
|
|
30
|
+
const fieldNames: Set<string> = new Set();
|
|
31
|
+
const fieldValueMaps: Record<string, { type: keyof typeof types; value: unknown }>[] = [];
|
|
32
|
+
|
|
33
|
+
models.forEach((model) => {
|
|
34
|
+
const fieldValueMap: Record<string, { type: keyof typeof types; value: unknown }> = {};
|
|
35
|
+
schema.meta.props.forEach((p) => {
|
|
36
|
+
const modelValue = model[p.name];
|
|
37
|
+
if (p.kind === "scalar" && modelValue !== undefined) {
|
|
38
|
+
fieldNames.add(p.fieldNames[0] || p.name);
|
|
39
|
+
fieldValueMap[p.fieldNames[0]] = {
|
|
40
|
+
type: p.type as keyof typeof types,
|
|
41
|
+
value: modelValue,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
fieldValueMaps.push(fieldValueMap);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const fields = Array.from(fieldNames);
|
|
49
|
+
const values = fieldValueMaps.flatMap((fieldValueMap) =>
|
|
50
|
+
fields.map(
|
|
51
|
+
(f) =>
|
|
52
|
+
fieldValueMap[f] || {
|
|
53
|
+
type: "string",
|
|
54
|
+
value: null,
|
|
55
|
+
},
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
sql: `INSERT INTO ${schema.meta.collection} (${fields.join(",")}) VALUES ${fieldValueMaps
|
|
61
|
+
.map(
|
|
62
|
+
(fieldValueMap) =>
|
|
63
|
+
`(${fields
|
|
64
|
+
.map((f) =>
|
|
65
|
+
transformValue(
|
|
66
|
+
fieldValueMap[f] || {
|
|
67
|
+
type: "string",
|
|
68
|
+
value: null,
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
.join(",")})`,
|
|
73
|
+
)
|
|
74
|
+
.join(
|
|
75
|
+
", ",
|
|
76
|
+
)} ${updateIfExists ? `ON DUPLICATE KEY UPDATE ${fields.map((f) => `${f} = VALUES(${f})`).join(",")}` : ""}`,
|
|
77
|
+
fields,
|
|
78
|
+
values,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Inserts records into the database.
|
|
84
|
+
* @param schema - The entity schema.
|
|
85
|
+
* @param models - The list of entities to insert.
|
|
86
|
+
* @param updateIfExists - Whether to update the row if it already exists.
|
|
87
|
+
* @returns The ID of the inserted row.
|
|
88
|
+
*/
|
|
89
|
+
async insert<T extends object>(
|
|
90
|
+
schema: EntitySchema<T>,
|
|
91
|
+
models: T[],
|
|
92
|
+
updateIfExists: boolean = false,
|
|
93
|
+
): Promise<number> {
|
|
94
|
+
if (!models || models.length === 0) return 0;
|
|
95
|
+
|
|
96
|
+
const query = await this.generateInsertScript(schema, models, updateIfExists);
|
|
97
|
+
console.debug("INSERT SQL: " + query.sql);
|
|
98
|
+
console.debug("INSERT VALUES: " + JSON.stringify(query.values));
|
|
99
|
+
const sqlStatement = sql.prepare<UpdateQueryResponse>(query.sql);
|
|
100
|
+
const updateQueryResponseResult = await sqlStatement.execute();
|
|
101
|
+
return updateQueryResponseResult.rows.insertId;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Retrieves the primary keys for the given entity schema.
|
|
106
|
+
* @param schema - The entity schema.
|
|
107
|
+
* @returns An array of primary key properties.
|
|
108
|
+
* @throws If no primary keys are found.
|
|
109
|
+
*/
|
|
110
|
+
private getPrimaryKeys<T extends object>(schema: EntitySchema<T>): EntityProperty<T, unknown>[] {
|
|
111
|
+
const primaryKeys = schema.meta.props.filter((p) => p.primary);
|
|
112
|
+
if (!primaryKeys.length) {
|
|
113
|
+
throw new Error(`No primary keys found for schema: ${schema.meta.className}`);
|
|
114
|
+
}
|
|
115
|
+
return primaryKeys;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Deletes a record by its ID.
|
|
120
|
+
* @param id - The ID of the record to delete.
|
|
121
|
+
* @param schema - The entity schema.
|
|
122
|
+
* @returns The number of rows affected.
|
|
123
|
+
* @throws If the entity has more than one primary key.
|
|
124
|
+
*/
|
|
125
|
+
async deleteById<T extends object>(id: unknown, schema: EntitySchema<T>): Promise<number> {
|
|
126
|
+
const primaryKeys = this.getPrimaryKeys(schema);
|
|
127
|
+
if (primaryKeys.length > 1) {
|
|
128
|
+
throw new Error("Only one primary key is supported");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const primaryKey = primaryKeys[0];
|
|
132
|
+
const queryBuilder = this.forgeOperations.createQueryBuilder(schema.meta.class).delete();
|
|
133
|
+
queryBuilder.andWhere({ [primaryKey.name]: { $eq: id } });
|
|
134
|
+
|
|
135
|
+
const query = queryBuilder.getFormattedQuery();
|
|
136
|
+
console.debug("DELETE SQL: " + query);
|
|
137
|
+
|
|
138
|
+
const sqlStatement = sql.prepare<UpdateQueryResponse>(query);
|
|
139
|
+
const updateQueryResponseResult = await sqlStatement.execute();
|
|
140
|
+
return updateQueryResponseResult.rows.affectedRows;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Updates a record by its ID.
|
|
145
|
+
* @param entity - The entity with updated values.
|
|
146
|
+
* @param schema - The entity schema.
|
|
147
|
+
* @throws If the primary key value is missing in the entity.
|
|
148
|
+
*/
|
|
149
|
+
async updateById<T extends object>(entity: T, schema: EntitySchema<T>): Promise<void> {
|
|
150
|
+
const primaryKeys = this.getPrimaryKeys(schema);
|
|
151
|
+
const queryBuilder = this.forgeOperations.createQueryBuilder(schema.meta.class).update(entity);
|
|
152
|
+
|
|
153
|
+
primaryKeys.forEach((pk) => {
|
|
154
|
+
const value = entity[pk.name];
|
|
155
|
+
if (value === null || value === undefined) {
|
|
156
|
+
throw new Error(`Primary Key ${pk.name} must exist in the model`);
|
|
157
|
+
}
|
|
158
|
+
queryBuilder.andWhere({ [pk.name]: { $eq: value } });
|
|
159
|
+
});
|
|
160
|
+
const query = queryBuilder.getFormattedQuery();
|
|
161
|
+
console.debug("UPDATE SQL: " + query);
|
|
162
|
+
await this.forgeOperations.fetch().executeRawUpdateSQL(query);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { EntityName, LoggingOptions } from "@mikro-orm/core";
|
|
2
|
+
import type { EntitySchema } from "@mikro-orm/core/metadata/EntitySchema";
|
|
3
|
+
import type { AnyEntity, EntityClass, EntityClassGroup } from "@mikro-orm/core/typings";
|
|
4
|
+
import type { QueryBuilder } from "@mikro-orm/knex/query";
|
|
5
|
+
import { MemoryCacheAdapter, MikroORM, NullCacheAdapter } from "@mikro-orm/mysql";
|
|
6
|
+
import { ForgeSQLCrudOperations } from "./ForgeSQLCrudOperations";
|
|
7
|
+
import { CRUDForgeSQL, ForgeSqlOperation, SchemaSqlForgeSql } from "./ForgeSQLQueryBuilder";
|
|
8
|
+
import { ForgeSQLSelectOperations } from "./ForgeSQLSelectOperations";
|
|
9
|
+
import type { Knex } from "knex";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Implementation of ForgeSQLORM that interacts with MikroORM.
|
|
13
|
+
*/
|
|
14
|
+
class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
15
|
+
private static instance: ForgeSQLORMImpl | null = null;
|
|
16
|
+
private readonly mikroORM: MikroORM;
|
|
17
|
+
private readonly crudOperations: CRUDForgeSQL;
|
|
18
|
+
private readonly fetchOperations: SchemaSqlForgeSql;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Private constructor to enforce singleton behavior.
|
|
22
|
+
* @param entities - The list of entities for ORM initialization.
|
|
23
|
+
*/
|
|
24
|
+
private constructor(
|
|
25
|
+
entities: (EntityClass<AnyEntity> | EntityClassGroup<AnyEntity> | EntitySchema)[],
|
|
26
|
+
) {
|
|
27
|
+
console.debug("Initializing ForgeSQLORM...");
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
this.mikroORM = MikroORM.initSync({
|
|
31
|
+
dbName: "inmemory",
|
|
32
|
+
schemaGenerator: {
|
|
33
|
+
disableForeignKeys: false,
|
|
34
|
+
},
|
|
35
|
+
discovery: {
|
|
36
|
+
warnWhenNoEntities: true,
|
|
37
|
+
},
|
|
38
|
+
resultCache: {
|
|
39
|
+
adapter: NullCacheAdapter,
|
|
40
|
+
},
|
|
41
|
+
metadataCache: {
|
|
42
|
+
enabled: false,
|
|
43
|
+
adapter: MemoryCacheAdapter,
|
|
44
|
+
},
|
|
45
|
+
entities: entities,
|
|
46
|
+
preferTs: false,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
this.crudOperations = new ForgeSQLCrudOperations(this);
|
|
50
|
+
this.fetchOperations = new ForgeSQLSelectOperations();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("ForgeSQLORM initialization failed:", error);
|
|
53
|
+
throw error; // Prevents inconsistent state
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns the singleton instance of ForgeSQLORMImpl.
|
|
59
|
+
* @param entities - List of entities (required only on first initialization).
|
|
60
|
+
* @returns The singleton instance of ForgeSQLORMImpl.
|
|
61
|
+
*/
|
|
62
|
+
static getInstance(
|
|
63
|
+
entities: (EntityClass<AnyEntity> | EntityClassGroup<AnyEntity> | EntitySchema)[],
|
|
64
|
+
): ForgeSqlOperation {
|
|
65
|
+
if (!ForgeSQLORMImpl.instance) {
|
|
66
|
+
ForgeSQLORMImpl.instance = new ForgeSQLORMImpl(entities);
|
|
67
|
+
}
|
|
68
|
+
return ForgeSQLORMImpl.instance;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Retrieves the CRUD operations instance.
|
|
73
|
+
* @returns CRUD operations.
|
|
74
|
+
*/
|
|
75
|
+
crud(): CRUDForgeSQL {
|
|
76
|
+
return this.crudOperations;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Retrieves the fetch operations instance.
|
|
81
|
+
* @returns Fetch operations.
|
|
82
|
+
*/
|
|
83
|
+
fetch(): SchemaSqlForgeSql {
|
|
84
|
+
return this.fetchOperations;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates a new query builder for the given entity.
|
|
89
|
+
* @param entityName - The entity name or an existing query builder.
|
|
90
|
+
* @param alias - The alias for the entity.
|
|
91
|
+
* @param loggerContext - Logging options.
|
|
92
|
+
* @returns The query builder instance.
|
|
93
|
+
*/
|
|
94
|
+
createQueryBuilder<Entity extends object, RootAlias extends string = never>(
|
|
95
|
+
entityName: EntityName<Entity> | QueryBuilder<Entity>,
|
|
96
|
+
alias?: RootAlias,
|
|
97
|
+
loggerContext?: LoggingOptions,
|
|
98
|
+
): QueryBuilder<Entity, RootAlias> {
|
|
99
|
+
return this.mikroORM.em.createQueryBuilder(entityName, alias, undefined, loggerContext);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getKnex(): Knex<any, any[]> {
|
|
103
|
+
return this.mikroORM.em.getKnex();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Public class that acts as a wrapper around the private ForgeSQLORMImpl.
|
|
109
|
+
*/
|
|
110
|
+
class ForgeSQLORM {
|
|
111
|
+
private readonly ormInstance: ForgeSqlOperation;
|
|
112
|
+
|
|
113
|
+
constructor(entities: (EntityClass<AnyEntity> | EntityClassGroup<AnyEntity> | EntitySchema)[]) {
|
|
114
|
+
this.ormInstance = ForgeSQLORMImpl.getInstance(entities);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Proxies the `crud` method from `ForgeSQLORMImpl`.
|
|
119
|
+
* @returns CRUD operations.
|
|
120
|
+
*/
|
|
121
|
+
crud(): CRUDForgeSQL {
|
|
122
|
+
return this.ormInstance.crud();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Proxies the `fetch` method from `ForgeSQLORMImpl`.
|
|
127
|
+
* @returns Fetch operations.
|
|
128
|
+
*/
|
|
129
|
+
fetch(): SchemaSqlForgeSql {
|
|
130
|
+
return this.ormInstance.fetch();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getKnex(): Knex<any, any[]> {
|
|
134
|
+
return this.ormInstance.getKnex();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Proxies the `createQueryBuilder` method from `ForgeSQLORMImpl`.
|
|
139
|
+
* @returns A new query builder instance.
|
|
140
|
+
*/
|
|
141
|
+
createQueryBuilder<Entity extends object, RootAlias extends string = never>(
|
|
142
|
+
entityName: EntityName<Entity> | QueryBuilder<Entity>,
|
|
143
|
+
alias?: RootAlias,
|
|
144
|
+
loggerContext?: LoggingOptions,
|
|
145
|
+
): QueryBuilder<Entity, RootAlias> {
|
|
146
|
+
return this.ormInstance.createQueryBuilder(entityName, alias, loggerContext);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export default ForgeSQLORM;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { UpdateQueryResponse } from "@forge/sql";
|
|
2
|
+
import type { EntityName, LoggingOptions } from "..";
|
|
3
|
+
import type { EntitySchema } from "@mikro-orm/core/metadata/EntitySchema";
|
|
4
|
+
import type { QueryBuilder } from "@mikro-orm/knex/query";
|
|
5
|
+
import type { Knex } from "knex";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Interface representing the main ForgeSQL operations.
|
|
9
|
+
*/
|
|
10
|
+
export interface ForgeSqlOperation extends QueryBuilderForgeSql {
|
|
11
|
+
/**
|
|
12
|
+
* Provides CRUD operations.
|
|
13
|
+
*/
|
|
14
|
+
crud(): CRUDForgeSQL;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Provides schema-level SQL fetch operations.
|
|
18
|
+
*/
|
|
19
|
+
fetch(): SchemaSqlForgeSql;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Interface for schema-level SQL operations.
|
|
24
|
+
*/
|
|
25
|
+
export interface SchemaSqlForgeSql {
|
|
26
|
+
/**
|
|
27
|
+
* Executes a schema-bound SQL query and maps the result to the specified entity schema.
|
|
28
|
+
* @param query - The SQL query to execute.
|
|
29
|
+
* @param schema - The entity schema.
|
|
30
|
+
* @returns A list of mapped entity objects.
|
|
31
|
+
*/
|
|
32
|
+
executeSchemaSQL<T extends object>(query: string, schema: EntitySchema<T>): Promise<T[]>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Executes a raw SQL query and returns the results.
|
|
36
|
+
* @param query - The raw SQL query.
|
|
37
|
+
* @returns A list of results as objects.
|
|
38
|
+
*/
|
|
39
|
+
executeRawSQL<T extends object | unknown>(query: string): Promise<T[]>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Executes a raw SQL update query.
|
|
43
|
+
* @param query - The raw SQL update query.
|
|
44
|
+
* @returns The update response containing affected rows.
|
|
45
|
+
*/
|
|
46
|
+
executeRawUpdateSQL(query: string): Promise<UpdateQueryResponse>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Interface for CRUD (Create, Read, Update, Delete) operations.
|
|
51
|
+
*/
|
|
52
|
+
export interface CRUDForgeSQL {
|
|
53
|
+
/**
|
|
54
|
+
* Inserts multiple records into the database.
|
|
55
|
+
* @param schema - The entity schema.
|
|
56
|
+
* @param models - The list of entities to insert.
|
|
57
|
+
* @param updateIfExists - Whether to update the row if it already exists.
|
|
58
|
+
* @returns The number of inserted rows.
|
|
59
|
+
*/
|
|
60
|
+
insert<T extends object>(
|
|
61
|
+
schema: EntitySchema<T>,
|
|
62
|
+
models: T[],
|
|
63
|
+
updateIfExists?: boolean,
|
|
64
|
+
): Promise<number>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Deletes a record by its ID.
|
|
68
|
+
* @param id - The ID of the record to delete.
|
|
69
|
+
* @param schema - The entity schema.
|
|
70
|
+
* @returns The number of rows affected.
|
|
71
|
+
*/
|
|
72
|
+
deleteById<T extends object>(id: unknown, schema: EntitySchema<T>): Promise<number>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Updates a record by its ID.
|
|
76
|
+
* @param entity - The entity with updated values.
|
|
77
|
+
* @param schema - The entity schema.
|
|
78
|
+
*/
|
|
79
|
+
updateById<T extends object>(entity: T, schema: EntitySchema<T>): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Interface for Query Builder operations.
|
|
84
|
+
*/
|
|
85
|
+
export interface QueryBuilderForgeSql {
|
|
86
|
+
/**
|
|
87
|
+
* Creates a new query builder for the given entity.
|
|
88
|
+
* @param entityName - The entity name or an existing query builder.
|
|
89
|
+
* @param alias - The alias for the entity.
|
|
90
|
+
* @param loggerContext - Logging options.
|
|
91
|
+
* @returns The query builder instance.
|
|
92
|
+
*/
|
|
93
|
+
createQueryBuilder<Entity extends object, RootAlias extends string = never>(
|
|
94
|
+
entityName: EntityName<Entity> | QueryBuilder<Entity>,
|
|
95
|
+
alias?: RootAlias,
|
|
96
|
+
loggerContext?: LoggingOptions,
|
|
97
|
+
): QueryBuilder<Entity, RootAlias>;
|
|
98
|
+
|
|
99
|
+
getKnex(): Knex<any, any[]>;
|
|
100
|
+
}
|