forge-sql-orm 1.0.31 → 1.1.31

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.
Files changed (37) hide show
  1. package/README.md +216 -695
  2. package/dist/ForgeSQLORM.js +538 -567
  3. package/dist/ForgeSQLORM.js.map +1 -1
  4. package/dist/ForgeSQLORM.mjs +536 -554
  5. package/dist/ForgeSQLORM.mjs.map +1 -1
  6. package/dist/core/ForgeSQLCrudOperations.d.ts +101 -130
  7. package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
  8. package/dist/core/ForgeSQLORM.d.ts +11 -10
  9. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  10. package/dist/core/ForgeSQLQueryBuilder.d.ts +271 -113
  11. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  12. package/dist/core/ForgeSQLSelectOperations.d.ts +65 -22
  13. package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
  14. package/dist/core/SystemTables.d.ts +59 -0
  15. package/dist/core/SystemTables.d.ts.map +1 -0
  16. package/dist/index.d.ts +1 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/utils/sqlUtils.d.ts +53 -6
  19. package/dist/utils/sqlUtils.d.ts.map +1 -1
  20. package/dist-cli/cli.js +471 -397
  21. package/dist-cli/cli.js.map +1 -1
  22. package/dist-cli/cli.mjs +471 -397
  23. package/dist-cli/cli.mjs.map +1 -1
  24. package/package.json +21 -22
  25. package/src/core/ForgeSQLCrudOperations.ts +360 -473
  26. package/src/core/ForgeSQLORM.ts +38 -79
  27. package/src/core/ForgeSQLQueryBuilder.ts +250 -133
  28. package/src/core/ForgeSQLSelectOperations.ts +182 -72
  29. package/src/core/SystemTables.ts +7 -0
  30. package/src/index.ts +1 -2
  31. package/src/utils/sqlUtils.ts +155 -23
  32. package/dist/core/ComplexQuerySchemaBuilder.d.ts +0 -38
  33. package/dist/core/ComplexQuerySchemaBuilder.d.ts.map +0 -1
  34. package/dist/knex/index.d.ts +0 -4
  35. package/dist/knex/index.d.ts.map +0 -1
  36. package/src/core/ComplexQuerySchemaBuilder.ts +0 -63
  37. package/src/knex/index.ts +0 -4
@@ -1,27 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const mysql = require("@mikro-orm/mysql");
4
- const sql = require("@forge/sql");
3
+ const drizzleOrm = require("drizzle-orm");
5
4
  const moment = require("moment");
6
- const entityGenerator = require("@mikro-orm/entity-generator");
7
- const wrapIfNeeded = (data, wrap) => {
8
- return wrap ? `'${data}'` : data;
9
- };
10
- const transformValue = (value, wrapValue = false) => {
11
- switch (value.type) {
12
- case "text":
13
- case "string":
14
- return wrapIfNeeded(`${value.value}`, wrapValue);
15
- case "datetime":
16
- return wrapIfNeeded(`${moment(value.value).format("YYYY-MM-DDTHH:mm:ss.SSS")}`, wrapValue);
17
- case "date":
18
- return wrapIfNeeded(`${moment(value.value).format("YYYY-MM-DD")}`, wrapValue);
19
- case "time":
20
- return wrapIfNeeded(`${moment(value.value).format("HH:mm:ss.SSS")}`, wrapValue);
21
- default:
22
- return value.value;
23
- }
24
- };
5
+ const primaryKeys = require("drizzle-orm/mysql-core/primary-keys");
6
+ const indexes = require("drizzle-orm/mysql-core/indexes");
7
+ const checks = require("drizzle-orm/mysql-core/checks");
8
+ const foreignKeys = require("drizzle-orm/mysql-core/foreign-keys");
9
+ const uniqueConstraint = require("drizzle-orm/mysql-core/unique-constraint");
10
+ const sql = require("@forge/sql");
11
+ const mysql2 = require("drizzle-orm/mysql2");
12
+ const mysqlCore = require("drizzle-orm/mysql-core");
13
+ const moment$1 = require("moment/moment.js");
25
14
  const parseDateTime = (value, format) => {
26
15
  const m = moment(value, format, true);
27
16
  if (!m.isValid()) {
@@ -29,576 +18,536 @@ const parseDateTime = (value, format) => {
29
18
  }
30
19
  return m.toDate();
31
20
  };
21
+ function extractAlias(query) {
22
+ const match = query.match(/\bas\s+(['"`]?)([\w*]+)\1$/i);
23
+ return match ? match[2] : query;
24
+ }
25
+ function getPrimaryKeys(table) {
26
+ const { columns, primaryKeys: primaryKeys2 } = getTableMetadata(table);
27
+ const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary);
28
+ if (columnPrimaryKeys.length > 0) {
29
+ return columnPrimaryKeys;
30
+ }
31
+ if (Array.isArray(primaryKeys2) && primaryKeys2.length > 0) {
32
+ const primaryKeyColumns = /* @__PURE__ */ new Set();
33
+ primaryKeys2.forEach((primaryKeyBuilder) => {
34
+ Object.entries(columns).filter(([, column]) => {
35
+ return primaryKeyBuilder.columns.includes(column);
36
+ }).forEach(([name, column]) => {
37
+ primaryKeyColumns.add([name, column]);
38
+ });
39
+ });
40
+ const result = Array.from(primaryKeyColumns);
41
+ return result.length > 0 ? result : void 0;
42
+ }
43
+ return void 0;
44
+ }
45
+ function getTableMetadata(table) {
46
+ const symbols = Object.getOwnPropertySymbols(table);
47
+ const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
48
+ const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
49
+ const extraSymbol = symbols.find((s) => s.toString().includes("ExtraConfigBuilder"));
50
+ const foreignKeysSymbol = symbols.find((s) => s.toString().includes("MySqlInlineForeignKeys)"));
51
+ const builders = {
52
+ indexes: [],
53
+ checks: [],
54
+ foreignKeys: [],
55
+ primaryKeys: [],
56
+ uniqueConstraints: [],
57
+ extras: []
58
+ };
59
+ if (foreignKeysSymbol) {
60
+ const foreignKeys2 = table[foreignKeysSymbol];
61
+ if (foreignKeys2) {
62
+ for (const foreignKey of foreignKeys2) {
63
+ builders.foreignKeys.push(foreignKey);
64
+ }
65
+ }
66
+ }
67
+ if (extraSymbol) {
68
+ const extraConfigBuilder = table[extraSymbol];
69
+ if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
70
+ const configBuilders = extraConfigBuilder(table);
71
+ let configBuildersArray = [];
72
+ if (!Array.isArray(configBuilders)) {
73
+ configBuildersArray = Object.values(configBuilders);
74
+ } else {
75
+ configBuildersArray = configBuilders;
76
+ }
77
+ configBuildersArray.forEach((builder) => {
78
+ if (builder instanceof indexes.IndexBuilder) {
79
+ builders.indexes.push(builder);
80
+ } else if (builder instanceof checks.CheckBuilder) {
81
+ builders.checks.push(builder);
82
+ } else if (builder instanceof foreignKeys.ForeignKeyBuilder) {
83
+ builders.foreignKeys.push(builder);
84
+ } else if (builder instanceof primaryKeys.PrimaryKeyBuilder) {
85
+ builders.primaryKeys.push(builder);
86
+ } else if (builder instanceof uniqueConstraint.UniqueConstraintBuilder) {
87
+ builders.uniqueConstraints.push(builder);
88
+ }
89
+ builders.extras.push(builder);
90
+ });
91
+ }
92
+ }
93
+ return {
94
+ tableName: nameSymbol ? table[nameSymbol] : "",
95
+ columns: columnsSymbol ? table[columnsSymbol] : {},
96
+ ...builders
97
+ };
98
+ }
32
99
  class ForgeSQLCrudOperations {
33
100
  forgeOperations;
34
101
  options;
102
+ /**
103
+ * Creates a new instance of ForgeSQLCrudOperations.
104
+ * @param forgeSqlOperations - The ForgeSQL operations instance
105
+ * @param options - Configuration options for the ORM
106
+ */
35
107
  constructor(forgeSqlOperations, options) {
36
108
  this.forgeOperations = forgeSqlOperations;
37
109
  this.options = options;
38
110
  }
39
111
  /**
40
- * Generates an SQL INSERT statement for the provided models.
41
- * If a version field exists in the schema, its value is set accordingly.
42
- *
43
- * @param schema - The entity schema.
44
- * @param models - The list of entities to insert.
45
- * @param updateIfExists - Whether to update the row if it already exists.
46
- * @returns An object containing the SQL query, column names, and values.
47
- */
48
- async generateInsertScript(schema, models, updateIfExists) {
49
- const columnNames = /* @__PURE__ */ new Set();
50
- const modelFieldValues = [];
51
- models.forEach((model) => {
52
- const fieldValues = {};
53
- schema.meta.props.forEach((prop) => {
54
- const value = model[prop.name];
55
- if (prop.kind === "scalar" && value !== void 0) {
56
- const columnName = this.getRealFieldNameFromSchema(prop);
57
- columnNames.add(columnName);
58
- fieldValues[columnName] = { type: prop.type, value };
59
- }
60
- });
61
- modelFieldValues.push(fieldValues);
62
- });
63
- const versionField = this.getVersionField(schema);
64
- if (versionField) {
65
- modelFieldValues.forEach((mv) => {
66
- const versionRealName = this.getRealFieldNameFromSchema(versionField);
67
- if (mv[versionRealName]) {
68
- mv[versionRealName].value = transformValue(
69
- { value: this.createVersionField(versionField), type: versionField.name },
70
- true
71
- );
72
- } else {
73
- mv[versionRealName] = {
74
- type: versionField.type,
75
- value: transformValue(
76
- { value: this.createVersionField(versionField), type: versionField.name },
77
- true
78
- )
79
- };
80
- columnNames.add(versionField.name);
81
- }
82
- });
83
- }
84
- const columns = Array.from(columnNames);
85
- const values = modelFieldValues.flatMap(
86
- (fieldValueMap) => columns.map(
87
- (column) => fieldValueMap[column] || {
88
- type: "string",
89
- value: null
90
- }
91
- )
92
- );
93
- const insertValues = modelFieldValues.map((fieldValueMap) => {
94
- const rowValues = columns.map(
95
- (column) => transformValue(
96
- fieldValueMap[column] || { type: "string", value: null },
97
- true
98
- )
99
- ).join(",");
100
- return `(${rowValues})`;
101
- }).join(", ");
102
- const insertEmptyValues = modelFieldValues.map(() => {
103
- const rowValues = columns.map(
104
- () => "?"
105
- ).join(",");
106
- return `(${rowValues})`;
107
- }).join(", ");
108
- const updateClause = updateIfExists ? ` ON DUPLICATE KEY UPDATE ${columns.map((col) => `${col} = VALUES(${col})`).join(",")}` : "";
109
- return {
110
- sql: `INSERT INTO ${schema.meta.collection} (${columns.join(",")}) VALUES ${insertValues}${updateClause}`,
111
- query: `INSERT INTO ${schema.meta.collection} (${columns.join(",")}) VALUES ${insertEmptyValues}${updateClause}`,
112
- fields: columns,
113
- values
114
- };
115
- }
116
- /**
117
- * Inserts records into the database.
112
+ * Inserts records into the database with optional versioning support.
118
113
  * If a version field exists in the schema, versioning is applied.
119
114
  *
120
- * @param schema - The entity schema.
121
- * @param models - The list of entities to insert.
122
- * @param updateIfExists - Whether to update the row if it already exists.
123
- * @returns The ID of the inserted row.
115
+ * @template T - The type of the table schema
116
+ * @param {T} schema - The entity schema
117
+ * @param {Partial<InferInsertModel<T>>[]} models - Array of entities to insert
118
+ * @param {boolean} [updateIfExists=false] - Whether to update existing records
119
+ * @returns {Promise<number>} The number of inserted rows
120
+ * @throws {Error} If the insert operation fails
124
121
  */
125
122
  async insert(schema, models, updateIfExists = false) {
126
- if (!models || models.length === 0) return 0;
127
- const query = await this.generateInsertScript(schema, models, updateIfExists);
123
+ if (!models?.length) return 0;
124
+ const { tableName, columns } = getTableMetadata(schema);
125
+ const versionMetadata = this.validateVersionField(tableName, columns);
126
+ const preparedModels = models.map(
127
+ (model) => this.prepareModelWithVersion(model, versionMetadata, columns)
128
+ );
129
+ const queryBuilder = this.forgeOperations.getDrizzleQueryBuilder().insert(schema).values(preparedModels);
130
+ const finalQuery = updateIfExists ? queryBuilder.onDuplicateKeyUpdate({
131
+ set: Object.fromEntries(
132
+ Object.keys(preparedModels[0]).map((key) => [key, schema[key]])
133
+ )
134
+ }) : queryBuilder;
135
+ const query = finalQuery.toSQL();
128
136
  if (this.options?.logRawSqlQuery) {
129
- console.debug("INSERT SQL: " + query.query);
137
+ console.debug("INSERT SQL:", query.sql);
130
138
  }
131
- const sqlStatement = sql.sql.prepare(query.sql);
132
- const result = await sqlStatement.execute();
133
- return result.rows.insertId;
139
+ const result = await this.forgeOperations.fetch().executeRawUpdateSQL(query.sql, query.params);
140
+ return result.insertId;
134
141
  }
135
142
  /**
136
- * Retrieves the primary key properties from the entity schema.
143
+ * Deletes a record by its primary key with optional version check.
144
+ * If versioning is enabled, ensures the record hasn't been modified since last read.
137
145
  *
138
- * @param schema - The entity schema.
139
- * @returns An array of primary key properties.
140
- * @throws If no primary keys are found.
141
- */
142
- getPrimaryKeys(schema) {
143
- const primaryKeys = schema.meta.props.filter((prop) => prop.primary);
144
- if (!primaryKeys.length) {
145
- throw new Error(`No primary keys found for schema: ${schema.meta.className}`);
146
- }
147
- return primaryKeys;
148
- }
149
- /**
150
- * Deletes a record by its primary key.
151
- *
152
- * @param id - The ID of the record to delete.
153
- * @param schema - The entity schema.
154
- * @returns The number of rows affected.
155
- * @throws If the entity has more than one primary key.
146
+ * @template T - The type of the table schema
147
+ * @param {unknown} id - The ID of the record to delete
148
+ * @param {T} schema - The entity schema
149
+ * @returns {Promise<number>} Number of affected rows
150
+ * @throws {Error} If the delete operation fails
151
+ * @throws {Error} If multiple primary keys are found
156
152
  */
157
153
  async deleteById(id, schema) {
158
- const primaryKeys = this.getPrimaryKeys(schema);
159
- if (primaryKeys.length > 1) {
160
- throw new Error("Only one primary key is supported");
161
- }
162
- const primaryKey = primaryKeys[0];
163
- const queryBuilder = this.forgeOperations.createQueryBuilder(schema.meta.class).delete();
164
- queryBuilder.andWhere({ [primaryKey.name]: { $eq: id } });
165
- const query = queryBuilder.getFormattedQuery();
166
- if (this.options?.logRawSqlQuery) {
167
- console.debug("DELETE SQL: " + queryBuilder.getQuery());
168
- }
169
- const sqlStatement = sql.sql.prepare(query);
170
- const result = await sqlStatement.execute();
171
- return result.rows.affectedRows;
172
- }
173
- /**
174
- * Retrieves the version field from the entity schema.
175
- * The version field must be of type datetime, integer, or decimal, not a primary key, and not nullable.
176
- *
177
- * @param schema - The entity schema.
178
- * @returns The version field property if it exists.
179
- */
180
- getVersionField(schema) {
181
- if (this.options.disableOptimisticLocking) {
182
- return void 0;
154
+ const { tableName, columns } = getTableMetadata(schema);
155
+ const primaryKeys2 = this.getPrimaryKeys(schema);
156
+ if (primaryKeys2.length !== 1) {
157
+ throw new Error("Only single primary key is supported");
183
158
  }
184
- return schema.meta.props.filter((prop) => prop.version).filter((prop) => {
185
- const validType = prop.type === "datetime" || prop.type === "integer" || prop.type === "decimal";
186
- if (!validType) {
187
- console.warn(
188
- `Version field "${prop.name}" in table ${schema.meta.tableName} must be datetime, integer, or decimal, but is "${prop.type}"`
189
- );
190
- }
191
- return validType;
192
- }).filter((prop) => {
193
- if (prop.primary) {
194
- console.warn(
195
- `Version field "${prop.name}" in table ${schema.meta.tableName} cannot be a primary key`
196
- );
197
- return false;
198
- }
199
- return true;
200
- }).find((prop) => {
201
- if (prop.nullable) {
202
- console.warn(
203
- `Version field "${prop.name}" in table ${schema.meta.tableName} should not be nullable`
204
- );
205
- return false;
206
- }
207
- return true;
208
- });
209
- }
210
- /**
211
- * Increments the version field of an entity.
212
- * For datetime types, sets the current date; for numeric types, increments by 1.
213
- *
214
- * @param versionField - The version field property.
215
- * @param updateModel - The entity to update.
216
- */
217
- incrementVersionField(versionField, updateModel) {
218
- const key = versionField.name;
219
- switch (versionField.type) {
220
- case "datetime": {
221
- updateModel[key] = /* @__PURE__ */ new Date();
222
- break;
223
- }
224
- case "decimal":
225
- case "integer": {
226
- updateModel[key] = updateModel[key] + 1;
227
- break;
159
+ const [primaryKeyName, primaryKeyColumn] = primaryKeys2[0];
160
+ const versionMetadata = this.validateVersionField(tableName, columns);
161
+ const conditions = [drizzleOrm.eq(primaryKeyColumn, id)];
162
+ if (versionMetadata && columns) {
163
+ const versionField = columns[versionMetadata.fieldName];
164
+ if (versionField) {
165
+ const oldModel = await this.getOldModel({ [primaryKeyName]: id }, schema, [
166
+ versionMetadata.fieldName,
167
+ versionField
168
+ ]);
169
+ conditions.push(drizzleOrm.eq(versionField, oldModel[versionMetadata.fieldName]));
228
170
  }
229
- default:
230
- throw new Error(`Unsupported version field type: ${versionField.type}`);
231
171
  }
232
- }
233
- /**
234
- * Creates the initial version field value for an entity.
235
- * For datetime types, returns the current date; for numeric types, returns 0.
236
- *
237
- * @param versionField - The version field property.
238
- */
239
- createVersionField(versionField) {
240
- switch (versionField.type) {
241
- case "datetime": {
242
- return /* @__PURE__ */ new Date();
243
- }
244
- case "decimal":
245
- case "integer": {
246
- return 0;
247
- }
248
- default:
249
- throw new Error(`Unsupported version field type: ${versionField.type}`);
172
+ const queryBuilder = this.forgeOperations.getDrizzleQueryBuilder().delete(schema).where(drizzleOrm.and(...conditions));
173
+ if (this.options?.logRawSqlQuery) {
174
+ console.debug("DELETE SQL:", queryBuilder.toSQL().sql);
250
175
  }
176
+ const result = await this.forgeOperations.fetch().executeRawUpdateSQL(queryBuilder.toSQL().sql, queryBuilder.toSQL().params);
177
+ return result.affectedRows;
251
178
  }
252
179
  /**
253
- * Updates a record by its primary key using the provided entity data.
180
+ * Updates a record by its primary key with optimistic locking support.
181
+ * If versioning is enabled:
182
+ * - Retrieves the current version
183
+ * - Checks for concurrent modifications
184
+ * - Increments the version on successful update
254
185
  *
255
- * @param entity - The entity with updated values.
256
- * @param schema - The entity schema.
186
+ * @template T - The type of the table schema
187
+ * @param {Partial<InferInsertModel<T>>} entity - The entity with updated values
188
+ * @param {T} schema - The entity schema
189
+ * @returns {Promise<number>} Number of affected rows
190
+ * @throws {Error} If the primary key is not provided
191
+ * @throws {Error} If optimistic locking check fails
192
+ * @throws {Error} If multiple primary keys are found
257
193
  */
258
194
  async updateById(entity, schema) {
259
- const fields = schema.meta.props.filter((prop) => prop.kind === "scalar").map((prop) => prop.name);
260
- await this.updateFieldById(entity, fields, schema);
261
- }
262
- /**
263
- * Updates specified fields of records based on provided conditions.
264
- * If the "where" parameter is not provided, the WHERE clause is built from the entity fields
265
- * that are not included in the list of fields to update.
266
- *
267
- * @param entity - The object containing values to update and potential criteria for filtering.
268
- * @param fields - Array of field names to update.
269
- * @param schema - The entity schema.
270
- * @param where - Optional filtering conditions for the WHERE clause.
271
- * @returns The number of affected rows.
272
- * @throws If no filtering criteria are provided (either via "where" or from the remaining entity fields).
273
- */
274
- async updateFields(entity, fields, schema, where) {
275
- const updateData = this.filterEntityFields(entity, fields);
276
- const updateModel = this.modifyModel(updateData, schema);
277
- let queryBuilder = this.forgeOperations.createQueryBuilder(schema.meta.class).getKnexQuery();
278
- queryBuilder.update(updateModel);
279
- if (where) {
280
- queryBuilder.where(where);
281
- } else {
282
- const filterCriteria = Object.keys(entity).filter((key) => !fields.includes(key)).reduce((criteria, key) => {
283
- if (entity[key] !== void 0) {
284
- criteria[key] = entity[key];
285
- }
286
- return criteria;
287
- }, {});
288
- if (Object.keys(filterCriteria).length === 0) {
289
- throw new Error(
290
- "Filtering criteria (WHERE clause) must be provided either via the 'where' parameter or through non-updated entity fields"
291
- );
292
- }
293
- queryBuilder.where(filterCriteria);
195
+ const { tableName, columns } = getTableMetadata(schema);
196
+ const primaryKeys2 = this.getPrimaryKeys(schema);
197
+ if (primaryKeys2.length !== 1) {
198
+ throw new Error("Only single primary key is supported");
294
199
  }
295
- if (this.options?.logRawSqlQuery) {
296
- console.debug("UPDATE SQL (updateFields): " + queryBuilder.toSQL().sql);
200
+ const [primaryKeyName, primaryKeyColumn] = primaryKeys2[0];
201
+ const versionMetadata = this.validateVersionField(tableName, columns);
202
+ if (!(primaryKeyName in entity)) {
203
+ throw new Error(`Primary key ${primaryKeyName} must be provided in the entity`);
297
204
  }
298
- const sqlQuery = queryBuilder.toQuery();
299
- const updateQueryResponse = await this.forgeOperations.fetch().executeRawUpdateSQL(sqlQuery);
300
- return updateQueryResponse.affectedRows;
301
- }
302
- /**
303
- * Updates specific fields of a record identified by its primary key.
304
- * If a version field exists in the schema, versioning is applied.
305
- *
306
- * @param entity - The entity with updated values.
307
- * @param fields - The list of field names to update.
308
- * @param schema - The entity schema.
309
- * @throws If the primary key is not included in the update fields.
310
- */
311
- async updateFieldById(entity, fields, schema) {
312
- const primaryKeys = this.getPrimaryKeys(schema);
313
- primaryKeys.forEach((pk) => {
314
- if (!fields.includes(pk.name)) {
315
- throw new Error("Update fields must include primary key: " + pk.name);
316
- }
317
- });
318
- const updatedEntity = this.filterEntityFields(entity, fields);
319
- let queryBuilder = this.forgeOperations.createQueryBuilder(schema.meta.class).getKnexQuery();
320
- const versionField = this.getVersionField(schema);
321
- const useVersion = Boolean(versionField);
322
- let updateModel = { ...updatedEntity };
323
- if (useVersion && versionField) {
324
- let oldModel = entity;
325
- if (entity[versionField.name] === void 0 || entity[versionField.name] === null) {
326
- oldModel = await this.getOldModel(primaryKeys, entity, schema, versionField);
327
- }
328
- const primaryFieldNames = primaryKeys.map((pk) => pk.name);
329
- const fieldsToRetain = primaryFieldNames.concat(versionField.name);
330
- const fromEntries = Object.fromEntries(fieldsToRetain.map((key) => [key, oldModel[key]]));
331
- updateModel = { ...updatedEntity, ...fromEntries };
332
- this.incrementVersionField(versionField, updateModel);
333
- updateModel = this.modifyModel(updateModel, schema);
334
- queryBuilder.update(updateModel);
335
- if (oldModel[versionField.name] !== void 0 || oldModel[versionField.name] !== null && this.isValid(oldModel[versionField.name])) {
336
- queryBuilder.andWhere(this.optWhere(oldModel, versionField));
205
+ const currentVersion = await this.getCurrentVersion(
206
+ entity,
207
+ primaryKeyName,
208
+ versionMetadata,
209
+ columns,
210
+ schema
211
+ );
212
+ const updateData = this.prepareUpdateData(entity, versionMetadata, columns, currentVersion);
213
+ const conditions = [
214
+ drizzleOrm.eq(primaryKeyColumn, entity[primaryKeyName])
215
+ ];
216
+ if (versionMetadata && columns) {
217
+ const versionField = columns[versionMetadata.fieldName];
218
+ if (versionField) {
219
+ conditions.push(drizzleOrm.eq(versionField, currentVersion));
337
220
  }
338
- } else {
339
- updateModel = this.modifyModel(updatedEntity, schema);
340
- queryBuilder.update(updateModel);
341
221
  }
342
- this.addPrimaryWhere(queryBuilder, primaryKeys, updateModel);
343
- const sqlQuery = queryBuilder.toQuery();
222
+ const queryBuilder = this.forgeOperations.getDrizzleQueryBuilder().update(schema).set(updateData).where(drizzleOrm.and(...conditions));
344
223
  if (this.options?.logRawSqlQuery) {
345
- console.debug("UPDATE SQL: " + queryBuilder.toSQL().sql);
224
+ console.debug("UPDATE SQL:", queryBuilder.toSQL().sql);
346
225
  }
347
- const updateQueryResponse = await this.forgeOperations.fetch().executeRawUpdateSQL(sqlQuery);
348
- if (versionField && !updateQueryResponse.affectedRows) {
226
+ const result = await this.forgeOperations.fetch().executeRawUpdateSQL(queryBuilder.toSQL().sql, queryBuilder.toSQL().params);
227
+ if (versionMetadata && result.affectedRows === 0) {
349
228
  throw new Error(
350
- "Optimistic locking failed: the record with primary key(s) " + primaryKeys.map((p) => updateModel[p.name]).join(", ") + " has been modified by another process."
229
+ `Optimistic locking failed: record with primary key ${entity[primaryKeyName]} has been modified`
351
230
  );
352
231
  }
232
+ return result.affectedRows;
353
233
  }
354
234
  /**
355
- * Constructs an optional WHERE clause for the version field.
356
- *
357
- * @param updateModel - The model containing the current version field value.
358
- * @param versionField - The version field property.
359
- * @returns A filter query for the version field.
360
- */
361
- optWhere(updateModel, versionField) {
362
- const currentVersionValue = transformValue(
363
- { value: updateModel[versionField.name], type: versionField.type },
364
- false
365
- );
366
- return { [versionField.name]: currentVersionValue };
367
- }
368
- /**
369
- * Retrieves the current state of a record from the database.
235
+ * Updates specified fields of records based on provided conditions.
236
+ * This method does not support versioning and should be used with caution.
370
237
  *
371
- * @param primaryKeys - The primary key properties.
372
- * @param entity - The entity with updated values.
373
- * @param schema - The entity schema.
374
- * @param versionField - The version field property.
375
- * @returns The existing record from the database.
376
- * @throws If the record does not exist or if multiple records are found.
377
- */
378
- async getOldModel(primaryKeys, entity, schema, versionField) {
379
- const primaryFieldNames = primaryKeys.map((pk) => pk.name);
380
- const fieldsToSelect = primaryFieldNames.concat(versionField.name);
381
- const queryBuilder = this.forgeOperations.createQueryBuilder(schema).select(fieldsToSelect);
382
- this.addPrimaryWhere(queryBuilder, primaryKeys, entity);
383
- const formattedQuery = queryBuilder.getFormattedQuery();
384
- const models = await this.forgeOperations.fetch().executeSchemaSQL(formattedQuery, schema);
385
- if (!models || models.length === 0) {
386
- throw new Error(`Cannot modify record because it does not exist in table ${schema.meta.tableName}`);
238
+ * @template T - The type of the table schema
239
+ * @param {Partial<InferInsertModel<T>>} updateData - The data to update
240
+ * @param {T} schema - The entity schema
241
+ * @param {SQL<unknown>} where - The WHERE conditions
242
+ * @returns {Promise<number>} Number of affected rows
243
+ * @throws {Error} If WHERE conditions are not provided
244
+ * @throws {Error} If the update operation fails
245
+ */
246
+ async updateFields(updateData, schema, where) {
247
+ if (!where) {
248
+ throw new Error("WHERE conditions must be provided");
387
249
  }
388
- if (models.length > 1) {
389
- throw new Error(
390
- `Cannot modify record because multiple rows with the same ID were found in table ${schema.meta.tableName}. Please verify the table metadata.`
391
- );
250
+ const queryBuilder = this.forgeOperations.getDrizzleQueryBuilder().update(schema).set(updateData).where(where);
251
+ if (this.options?.logRawSqlQuery) {
252
+ console.debug("UPDATE SQL:", queryBuilder.toSQL().sql);
392
253
  }
393
- return models[0];
254
+ const result = await this.forgeOperations.fetch().executeRawUpdateSQL(queryBuilder.toSQL().sql, queryBuilder.toSQL().params);
255
+ return result.affectedRows;
394
256
  }
257
+ // Helper methods
395
258
  /**
396
- * Adds primary key conditions to the query builder.
397
- *
398
- * @param queryBuilder - The Knex query builder instance.
399
- * @param primaryKeys - The primary key properties.
400
- * @param entity - The entity containing primary key values.
401
- * @throws If any primary key value is missing.
259
+ * Gets primary keys from the schema.
260
+ * @template T - The type of the table schema
261
+ * @param {T} schema - The table schema
262
+ * @returns {[string, AnyColumn][]} Array of primary key name and column pairs
263
+ * @throws {Error} If no primary keys are found
402
264
  */
403
- addPrimaryWhere(queryBuilder, primaryKeys, entity) {
404
- primaryKeys.forEach((pk) => {
405
- const fieldName = this.getRealFieldNameFromSchema(pk);
406
- const value = entity[fieldName];
407
- if (value === null || value === void 0) {
408
- throw new Error(`Primary key ${fieldName} must exist in the model`);
409
- }
410
- queryBuilder.andWhere({ [fieldName]: value });
411
- });
265
+ getPrimaryKeys(schema) {
266
+ const primaryKeys2 = getPrimaryKeys(schema);
267
+ if (!primaryKeys2) {
268
+ throw new Error(`No primary keys found for schema: ${schema}`);
269
+ }
270
+ return primaryKeys2;
412
271
  }
413
272
  /**
414
- * Filters the entity to include only the specified fields.
415
- *
416
- * @param entity - The original entity.
417
- * @param fields - The list of fields to retain.
418
- * @returns A partial entity object containing only the specified fields.
273
+ * Validates and retrieves version field metadata.
274
+ * @param {string} tableName - The name of the table
275
+ * @param {Record<string, AnyColumn>} columns - The table columns
276
+ * @returns {Object | undefined} Version field metadata if valid, undefined otherwise
419
277
  */
420
- filterEntityFields = (entity, fields) => fields.reduce((result, field) => {
421
- if (field in entity) {
422
- result[field] = entity[field];
278
+ validateVersionField(tableName, columns) {
279
+ if (this.options.disableOptimisticLocking) {
280
+ return void 0;
423
281
  }
424
- return result;
425
- }, {});
426
- /**
427
- * Transforms and modifies the updated entity model based on the schema.
428
- *
429
- * @param updatedEntity - The updated entity.
430
- * @param schema - The entity schema.
431
- * @returns The modified entity.
432
- */
433
- modifyModel(updatedEntity, schema) {
434
- const modifiedModel = {};
435
- schema.meta.props.filter((p) => p.kind === "scalar").forEach((p) => {
436
- const value = updatedEntity[p.name];
437
- if (value !== void 0 && value !== null) {
438
- const fieldName = this.getRealFieldNameFromSchema(p);
439
- modifiedModel[fieldName] = transformValue({ value, type: p.type }, false);
282
+ const versionMetadata = this.options.additionalMetadata?.[tableName]?.versionField;
283
+ if (!versionMetadata) return void 0;
284
+ const versionField = columns[versionMetadata.fieldName];
285
+ if (!versionField) {
286
+ console.warn(
287
+ `Version field "${versionMetadata.fieldName}" not found in table ${tableName}. Versioning will be skipped.`
288
+ );
289
+ return void 0;
290
+ }
291
+ if (!versionField.notNull) {
292
+ console.warn(
293
+ `Version field "${versionMetadata.fieldName}" in table ${tableName} is nullable. Versioning may not work correctly.`
294
+ );
295
+ return void 0;
296
+ }
297
+ const fieldType = versionField.getSQLType();
298
+ const isSupportedType = fieldType === "datetime" || fieldType === "timestamp" || fieldType === "int" || fieldType === "number" || fieldType === "decimal";
299
+ if (!isSupportedType) {
300
+ console.warn(
301
+ `Version field "${versionMetadata.fieldName}" in table ${tableName} has unsupported type "${fieldType}". Only datetime, timestamp, int, and decimal types are supported for versioning. Versioning will be skipped.`
302
+ );
303
+ return void 0;
304
+ }
305
+ return { fieldName: versionMetadata.fieldName, type: fieldType };
306
+ }
307
+ /**
308
+ * Gets the current version of an entity.
309
+ * @template T - The type of the table schema
310
+ * @param {Partial<InferInsertModel<T>>} entity - The entity
311
+ * @param {string} primaryKeyName - The name of the primary key
312
+ * @param {Object | undefined} versionMetadata - Version field metadata
313
+ * @param {Record<string, AnyColumn>} columns - The table columns
314
+ * @param {T} schema - The table schema
315
+ * @returns {Promise<unknown>} The current version value
316
+ */
317
+ async getCurrentVersion(entity, primaryKeyName, versionMetadata, columns, schema) {
318
+ if (!versionMetadata || !columns) return void 0;
319
+ const versionField = columns[versionMetadata.fieldName];
320
+ if (!versionField) return void 0;
321
+ if (versionMetadata.fieldName in entity) {
322
+ return entity[versionMetadata.fieldName];
323
+ }
324
+ const oldModel = await this.getOldModel(
325
+ { [primaryKeyName]: entity[primaryKeyName] },
326
+ schema,
327
+ [versionMetadata.fieldName, versionField]
328
+ );
329
+ return oldModel[versionMetadata.fieldName];
330
+ }
331
+ /**
332
+ * Prepares a model for insertion with version field.
333
+ * @template T - The type of the table schema
334
+ * @param {Partial<InferInsertModel<T>>} model - The model to prepare
335
+ * @param {Object | undefined} versionMetadata - Version field metadata
336
+ * @param {Record<string, AnyColumn>} columns - The table columns
337
+ * @returns {InferInsertModel<T>} The prepared model
338
+ */
339
+ prepareModelWithVersion(model, versionMetadata, columns) {
340
+ if (!versionMetadata || !columns) return model;
341
+ const versionField = columns[versionMetadata.fieldName];
342
+ if (!versionField) return model;
343
+ const modelWithVersion = { ...model };
344
+ const fieldType = versionField.getSQLType();
345
+ const versionValue = fieldType === "datetime" || fieldType === "timestamp" ? /* @__PURE__ */ new Date() : 1;
346
+ modelWithVersion[versionMetadata.fieldName] = versionValue;
347
+ return modelWithVersion;
348
+ }
349
+ /**
350
+ * Prepares update data with version field.
351
+ * @template T - The type of the table schema
352
+ * @param {Partial<InferInsertModel<T>>} entity - The entity to update
353
+ * @param {Object | undefined} versionMetadata - Version field metadata
354
+ * @param {Record<string, AnyColumn>} columns - The table columns
355
+ * @param {unknown} currentVersion - The current version value
356
+ * @returns {Partial<InferInsertModel<T>>} The prepared update data
357
+ */
358
+ prepareUpdateData(entity, versionMetadata, columns, currentVersion) {
359
+ const updateData = { ...entity };
360
+ if (versionMetadata && columns) {
361
+ const versionField = columns[versionMetadata.fieldName];
362
+ if (versionField) {
363
+ const fieldType = versionField.getSQLType();
364
+ updateData[versionMetadata.fieldName] = fieldType === "datetime" || fieldType === "timestamp" ? /* @__PURE__ */ new Date() : currentVersion + 1;
440
365
  }
441
- });
442
- return modifiedModel;
366
+ }
367
+ return updateData;
368
+ }
369
+ /**
370
+ * Retrieves an existing model by primary key.
371
+ * @template T - The type of the table schema
372
+ * @param {Record<string, unknown>} primaryKeyValues - The primary key values
373
+ * @param {T} schema - The table schema
374
+ * @param {[string, AnyColumn]} versionField - The version field name and column
375
+ * @returns {Promise<Awaited<T> extends Array<any> ? Awaited<T>[number] | undefined : Awaited<T> | undefined>} The existing model
376
+ * @throws {Error} If the record is not found
377
+ */
378
+ async getOldModel(primaryKeyValues, schema, versionField) {
379
+ const [versionFieldName, versionFieldColumn] = versionField;
380
+ const primaryKeys2 = this.getPrimaryKeys(schema);
381
+ const [primaryKeyName, primaryKeyColumn] = primaryKeys2[0];
382
+ const resultQuery = this.forgeOperations.getDrizzleQueryBuilder().select({
383
+ [primaryKeyName]: primaryKeyColumn,
384
+ [versionFieldName]: versionFieldColumn
385
+ }).from(schema).where(drizzleOrm.eq(primaryKeyColumn, primaryKeyValues[primaryKeyName]));
386
+ const model = await this.forgeOperations.fetch().executeQueryOnlyOne(resultQuery);
387
+ if (!model) {
388
+ throw new Error(`Record not found in table ${schema}`);
389
+ }
390
+ return model;
443
391
  }
392
+ }
393
+ class ForgeSQLSelectOperations {
394
+ options;
444
395
  /**
445
- * Returns the real field name from the entity property based on the schema.
446
- *
447
- * @param p - The entity property.
448
- * @returns The real field name.
396
+ * Creates a new instance of ForgeSQLSelectOperations.
397
+ * @param {ForgeSqlOrmOptions} options - Configuration options for the ORM
449
398
  */
450
- getRealFieldNameFromSchema(p) {
451
- return p.fieldNames && p.fieldNames.length ? p.fieldNames[0] : p.name;
399
+ constructor(options) {
400
+ this.options = options;
452
401
  }
453
402
  /**
454
- * Validates the provided value.
403
+ * Executes a Drizzle query and returns the results.
404
+ * Maps the raw database results to the appropriate entity types.
455
405
  *
456
- * @param value - The value to validate.
457
- * @returns True if the value is valid, false otherwise.
458
- */
459
- isValid(value) {
460
- if (value instanceof Date) {
461
- return !isNaN(value.getTime());
462
- }
463
- return true;
406
+ * @template T - The type of the query builder
407
+ * @param {T} query - The Drizzle query to execute
408
+ * @returns {Promise<Awaited<T>>} The query results mapped to entity types
409
+ */
410
+ async executeQuery(query) {
411
+ const queryType = query;
412
+ const querySql = queryType.toSQL();
413
+ const datas = await this.executeRawSQL(querySql.sql, querySql.params);
414
+ if (!datas.length) return [];
415
+ return datas.map((r) => {
416
+ const rawModel = r;
417
+ const newModel = {};
418
+ const columns = queryType.config.fields;
419
+ Object.entries(columns).forEach(([name, column]) => {
420
+ const { realColumn, aliasName } = this.extractColumnInfo(column);
421
+ const value = rawModel[aliasName];
422
+ if (value === null || value === void 0) {
423
+ newModel[name] = value;
424
+ return;
425
+ }
426
+ newModel[name] = this.parseColumnValue(realColumn, value);
427
+ });
428
+ return newModel;
429
+ });
464
430
  }
465
- }
466
- class DynamicEntity {
467
431
  /**
468
- * Retrieves a schema field by its original entity property.
469
- * @param field - The entity property to search for.
470
- * @returns The corresponding schema field or undefined if not found.
432
+ * Extracts column information and alias name from a column definition.
433
+ * @param {any} column - The column definition from Drizzle
434
+ * @returns {Object} Object containing the real column and its alias name
471
435
  */
472
- getSchemaBySchemaField(field) {
473
- return this[field.name];
436
+ extractColumnInfo(column) {
437
+ if (column instanceof drizzleOrm.SQL) {
438
+ const realColumnSql = column;
439
+ const realColumn = realColumnSql.queryChunks.find(
440
+ (q) => q instanceof drizzleOrm.Column
441
+ );
442
+ let stringChunk = this.findAliasChunk(realColumnSql);
443
+ let withoutAlias = false;
444
+ if (!realColumn && (!stringChunk || !stringChunk.value || !stringChunk.value?.length)) {
445
+ stringChunk = realColumnSql.queryChunks.filter((q) => q instanceof drizzleOrm.StringChunk).find((q) => q.value[0]);
446
+ withoutAlias = true;
447
+ }
448
+ const aliasName = this.resolveAliasName(stringChunk, realColumn, withoutAlias);
449
+ return { realColumn, aliasName };
450
+ }
451
+ return { realColumn: column, aliasName: column.name };
474
452
  }
475
453
  /**
476
- * Retrieves a schema field by its alias.
477
- * @param alias - The alias of the field.
478
- * @returns The corresponding schema field or undefined if not found.
454
+ * Finds the alias chunk in SQL query chunks.
455
+ * @param {SQL} realColumnSql - The SQL query chunks
456
+ * @returns {StringChunk | undefined} The string chunk containing the alias or undefined
479
457
  */
480
- getSchemaByAliasField(alias) {
481
- return this[alias];
482
- }
483
- }
484
- class EntitySchemaBuilder {
485
- constructor(entityClass) {
486
- this.entityClass = entityClass;
458
+ findAliasChunk(realColumnSql) {
459
+ return realColumnSql.queryChunks.filter((q) => q instanceof drizzleOrm.StringChunk).find(
460
+ (q) => q.value.find((f) => f.toLowerCase().includes("as"))
461
+ );
487
462
  }
488
- properties = {};
489
463
  /**
490
- * Adds a field to the schema definition.
491
- * @param field - The entity property to add.
492
- * @param alias - (Optional) Custom alias for the field name.
493
- * @returns The current instance of EntitySchemaBuilder for method chaining.
464
+ * Resolves the alias name from the string chunk or column.
465
+ * @param {StringChunk | undefined} stringChunk - The string chunk containing the alias
466
+ * @param {Column | undefined} realColumn - The real column definition
467
+ * @param {boolean} withoutAlias - Whether the column has no alias
468
+ * @returns {string} The resolved alias name
494
469
  */
495
- addField(field, alias) {
496
- const fieldName = alias || field.name;
497
- const fieldNameType = fieldName;
498
- this.properties[fieldNameType] = { ...field, name: fieldNameType };
499
- return this;
470
+ resolveAliasName(stringChunk, realColumn, withoutAlias) {
471
+ if (stringChunk) {
472
+ if (withoutAlias) {
473
+ return stringChunk.value[0];
474
+ }
475
+ const asClause = stringChunk.value.find((f) => f.toLowerCase().includes("as"));
476
+ return asClause ? extractAlias(asClause.trim()) : realColumn?.name || "";
477
+ }
478
+ return realColumn?.name || "";
500
479
  }
501
480
  /**
502
- * Creates and initializes a new EntitySchema based on the added fields.
503
- * @returns A new EntitySchema<T> instance.
504
- */
505
- createSchema() {
506
- const es = new mysql.EntitySchema({
507
- class: this.entityClass,
508
- // @ts-ignore
509
- properties: this.properties
510
- });
511
- es.init();
512
- return es;
513
- }
514
- }
515
- class DynamicEntitySchemaBuilder extends EntitySchemaBuilder {
516
- constructor() {
517
- super(DynamicEntity);
518
- }
519
- }
520
- class ForgeSQLSelectOperations {
521
- options;
522
- constructor(options) {
523
- this.options = options;
481
+ * Parses a column value based on its SQL type.
482
+ * Handles datetime, date, and time types with appropriate formatting.
483
+ *
484
+ * @param {Column} column - The column definition
485
+ * @param {unknown} value - The raw value to parse
486
+ * @returns {unknown} The parsed value
487
+ */
488
+ parseColumnValue(column, value) {
489
+ if (!column) return value;
490
+ switch (column.getSQLType()) {
491
+ case "datetime":
492
+ return parseDateTime(value, "YYYY-MM-DDTHH:mm:ss.SSS");
493
+ case "date":
494
+ return parseDateTime(value, "YYYY-MM-DD");
495
+ case "time":
496
+ return parseDateTime(value, "HH:mm:ss.SSS");
497
+ default:
498
+ return value;
499
+ }
524
500
  }
525
501
  /**
526
- * Creates a builder for constructing complex query schemas dynamically.
527
- * This method is useful when working with dynamic entity structures where fields
528
- * may not be known at compile time.
529
- * @returns An instance of ComplexQuerySchemaBuilder configured for dynamic entities.
530
- */
531
- createComplexQuerySchema() {
532
- return new DynamicEntitySchemaBuilder();
533
- }
534
- async executeSchemaSQLOnlyOne(query, schema) {
535
- const results = await this.executeSchemaSQL(query, schema);
536
- if (!results || results.length === 0) {
502
+ * Executes a Drizzle query and returns a single result.
503
+ * Throws an error if more than one record is returned.
504
+ *
505
+ * @template T - The type of the query builder
506
+ * @param {T} query - The Drizzle query to execute
507
+ * @returns {Promise<Awaited<T> extends Array<any> ? Awaited<T>[number] | undefined : Awaited<T> | undefined>} A single result object or undefined
508
+ * @throws {Error} If more than one record is returned
509
+ */
510
+ async executeQueryOnlyOne(query) {
511
+ const results = await this.executeQuery(query);
512
+ const datas = results;
513
+ if (!datas.length) {
537
514
  return void 0;
538
515
  }
539
- if (results.length > 1) {
540
- throw new Error("Expected 1 record but returned " + results.length);
516
+ if (datas.length > 1) {
517
+ throw new Error(`Expected 1 record but returned ${datas.length}`);
541
518
  }
542
- return results[0];
543
- }
544
- /**
545
- * Executes a schema-based SQL query and maps the result to the entity schema.
546
- * @param query - The SQL query to execute.
547
- * @param schema - The entity schema defining the structure.
548
- * @returns A list of mapped entity objects.
549
- */
550
- async executeSchemaSQL(query, schema) {
551
- const datas = await this.executeRawSQL(query);
552
- if (!datas.length) return [];
553
- return datas.map((r) => {
554
- const rawModel = r;
555
- const newModel = Object.create(schema.meta.prototype);
556
- schema.meta.props.filter((p) => p.kind === "scalar").forEach((p) => {
557
- const fieldName = p.name;
558
- const fieldNames = p.fieldNames;
559
- const rawFieldName = fieldNames && Array.isArray(fieldNames) ? fieldNames[0] : p.name;
560
- switch (p.type) {
561
- case "datetime":
562
- newModel[fieldName] = parseDateTime(
563
- rawModel[rawFieldName],
564
- "YYYY-MM-DDTHH:mm:ss.SSS"
565
- );
566
- break;
567
- case "date":
568
- newModel[fieldName] = parseDateTime(rawModel[rawFieldName], "YYYY-MM-DD");
569
- break;
570
- case "time":
571
- newModel[fieldName] = parseDateTime(rawModel[rawFieldName], "HH:mm:ss.SSS");
572
- break;
573
- default:
574
- newModel[fieldName] = rawModel[rawFieldName];
575
- }
576
- });
577
- return newModel;
578
- });
519
+ return datas[0];
579
520
  }
580
521
  /**
581
522
  * Executes a raw SQL query and returns the results.
582
- * @param query - The raw SQL query to execute.
583
- * @returns A list of results as objects.
523
+ * Logs the query if logging is enabled.
524
+ *
525
+ * @template T - The type of the result objects
526
+ * @param {string} query - The raw SQL query to execute
527
+ * @param {SqlParameters[]} [params] - Optional SQL parameters
528
+ * @returns {Promise<T[]>} A list of results as objects
584
529
  */
585
- async executeRawSQL(query) {
530
+ async executeRawSQL(query, params) {
586
531
  if (this.options.logRawSqlQuery) {
587
532
  console.debug("Executing raw SQL: " + query);
588
533
  }
589
- const sqlStatement = await sql.sql.prepare(query).execute();
590
- return sqlStatement.rows;
534
+ const sqlStatement = sql.sql.prepare(query);
535
+ if (params) {
536
+ sqlStatement.bindParams(...params);
537
+ }
538
+ const result = await sqlStatement.execute();
539
+ return result.rows;
591
540
  }
592
541
  /**
593
542
  * Executes a raw SQL update query.
594
- * @param query - The raw SQL update query.
595
- * @param params - sql parameters.
596
- * @returns The update response containing affected rows.
543
+ * @param {string} query - The raw SQL update query
544
+ * @param {SqlParameters[]} [params] - Optional SQL parameters
545
+ * @returns {Promise<UpdateQueryResponse>} The update response containing affected rows
597
546
  */
598
547
  async executeRawUpdateSQL(query, params) {
599
548
  const sqlStatement = sql.sql.prepare(query);
600
549
  if (params) {
601
- sqlStatement.bindParams(params);
550
+ sqlStatement.bindParams(...params);
602
551
  }
603
552
  const updateQueryResponseResults = await sqlStatement.execute();
604
553
  return updateQueryResponseResults.rows;
@@ -606,39 +555,23 @@ class ForgeSQLSelectOperations {
606
555
  }
607
556
  class ForgeSQLORMImpl {
608
557
  static instance = null;
609
- mikroORM;
558
+ drizzle;
610
559
  crudOperations;
611
560
  fetchOperations;
612
561
  /**
613
562
  * Private constructor to enforce singleton behavior.
614
- * @param entities - The list of entities for ORM initialization.
615
563
  * @param options - Options for configuring ForgeSQL ORM behavior.
616
564
  */
617
- constructor(entities, options) {
565
+ constructor(options) {
618
566
  try {
619
- const newOptions = options ?? { logRawSqlQuery: false, disableOptimisticLocking: false };
567
+ const newOptions = options ?? {
568
+ logRawSqlQuery: false,
569
+ disableOptimisticLocking: false
570
+ };
620
571
  if (newOptions.logRawSqlQuery) {
621
572
  console.debug("Initializing ForgeSQLORM...");
622
573
  }
623
- this.mikroORM = mysql.MikroORM.initSync({
624
- dbName: "inmemory",
625
- schemaGenerator: {
626
- disableForeignKeys: false
627
- },
628
- discovery: {
629
- warnWhenNoEntities: true
630
- },
631
- resultCache: {
632
- adapter: mysql.NullCacheAdapter
633
- },
634
- metadataCache: {
635
- enabled: false,
636
- adapter: mysql.MemoryCacheAdapter
637
- },
638
- entities,
639
- preferTs: false,
640
- debug: false
641
- });
574
+ this.drizzle = mysql2.drizzle("");
642
575
  this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
643
576
  this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
644
577
  } catch (error) {
@@ -648,13 +581,12 @@ class ForgeSQLORMImpl {
648
581
  }
649
582
  /**
650
583
  * Returns the singleton instance of ForgeSQLORMImpl.
651
- * @param entities - List of entities (required only on first initialization).
652
584
  * @param options - Options for configuring ForgeSQL ORM behavior.
653
585
  * @returns The singleton instance of ForgeSQLORMImpl.
654
586
  */
655
- static getInstance(entities, options) {
587
+ static getInstance(options) {
656
588
  if (!ForgeSQLORMImpl.instance) {
657
- ForgeSQLORMImpl.instance = new ForgeSQLORMImpl(entities, options);
589
+ ForgeSQLORMImpl.instance = new ForgeSQLORMImpl(options);
658
590
  }
659
591
  return ForgeSQLORMImpl.instance;
660
592
  }
@@ -673,28 +605,22 @@ class ForgeSQLORMImpl {
673
605
  return this.fetchOperations;
674
606
  }
675
607
  /**
676
- * Creates a new query builder for the given entity.
677
- * @param entityName - The entity name or an existing query builder.
678
- * @param alias - The alias for the entity.
679
- * @param loggerContext - Logging options.
680
- * @returns The query builder instance.
681
- */
682
- createQueryBuilder(entityName, alias, loggerContext) {
683
- return this.mikroORM.em.createQueryBuilder(entityName, alias, void 0, loggerContext);
684
- }
685
- /**
686
- * Provides access to the underlying Knex instance for building complex query parts.
687
- * enabling advanced query customization and performance tuning.
688
- * @returns The Knex instance, which can be used for query building.
608
+ * Returns a Drizzle query builder instance.
609
+ *
610
+ * ⚠️ IMPORTANT: This method should be used ONLY for query building purposes.
611
+ * The returned instance should NOT be used for direct database connections or query execution.
612
+ * All database operations should be performed through Forge SQL's executeRawSQL or executeRawUpdateSQL methods.
613
+ *
614
+ * @returns A Drizzle query builder instance for query construction only.
689
615
  */
690
- getKnex() {
691
- return this.mikroORM.em.getKnex();
616
+ getDrizzleQueryBuilder() {
617
+ return this.drizzle;
692
618
  }
693
619
  }
694
620
  class ForgeSQLORM {
695
621
  ormInstance;
696
- constructor(entities, options) {
697
- this.ormInstance = ForgeSQLORMImpl.getInstance(entities, options);
622
+ constructor(options) {
623
+ this.ormInstance = ForgeSQLORMImpl.getInstance(options);
698
624
  }
699
625
  /**
700
626
  * Proxies the `crud` method from `ForgeSQLORMImpl`.
@@ -710,30 +636,75 @@ class ForgeSQLORM {
710
636
  fetch() {
711
637
  return this.ormInstance.fetch();
712
638
  }
713
- getKnex() {
714
- return this.ormInstance.getKnex();
715
- }
716
639
  /**
717
- * Proxies the `createQueryBuilder` method from `ForgeSQLORMImpl`.
718
- * @returns A new query builder instance.
640
+ * Returns a Drizzle query builder instance.
641
+ *
642
+ * ⚠️ IMPORTANT: This method should be used ONLY for query building purposes.
643
+ * The returned instance should NOT be used for direct database connections or query execution.
644
+ * All database operations should be performed through Forge SQL's executeRawSQL or executeRawUpdateSQL methods.
645
+ *
646
+ * @returns A Drizzle query builder instance for query construction only.
719
647
  */
720
- createQueryBuilder(entityName, alias, loggerContext) {
721
- return this.ormInstance.createQueryBuilder(entityName, alias, loggerContext);
648
+ getDrizzleQueryBuilder() {
649
+ return this.ormInstance.getDrizzleQueryBuilder();
722
650
  }
723
651
  }
652
+ const mySqlDateTimeString = mysqlCore.customType({
653
+ dataType() {
654
+ return "datetime";
655
+ },
656
+ toDriver(value) {
657
+ return moment$1(value).format("YYYY-MM-DDTHH:mm:ss.SSS");
658
+ },
659
+ fromDriver(value) {
660
+ const format = "YYYY-MM-DDTHH:mm:ss.SSS";
661
+ return parseDateTime(value, format);
662
+ }
663
+ });
664
+ const mySqlTimestampString = mysqlCore.customType({
665
+ dataType() {
666
+ return "timestamp";
667
+ },
668
+ toDriver(value) {
669
+ return moment$1(value).format("YYYY-MM-DDTHH:mm:ss.SSS");
670
+ },
671
+ fromDriver(value) {
672
+ const format = "YYYY-MM-DDTHH:mm:ss.SSS";
673
+ return parseDateTime(value, format);
674
+ }
675
+ });
676
+ const mySqlDateString = mysqlCore.customType({
677
+ dataType() {
678
+ return "date";
679
+ },
680
+ toDriver(value) {
681
+ return moment$1(value).format("YYYY-MM-DD");
682
+ },
683
+ fromDriver(value) {
684
+ const format = "YYYY-MM-DD";
685
+ return parseDateTime(value, format);
686
+ }
687
+ });
688
+ const mySqlTimeString = mysqlCore.customType({
689
+ dataType() {
690
+ return "time";
691
+ },
692
+ toDriver(value) {
693
+ return moment$1(value).format("HH:mm:ss.SSS");
694
+ },
695
+ fromDriver(value) {
696
+ return parseDateTime(value, "HH:mm:ss.SSS");
697
+ }
698
+ });
724
699
  exports.ForgeSQLCrudOperations = ForgeSQLCrudOperations;
725
700
  exports.ForgeSQLSelectOperations = ForgeSQLSelectOperations;
726
701
  exports.default = ForgeSQLORM;
727
- Object.keys(mysql).forEach((k) => {
728
- if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
729
- enumerable: true,
730
- get: () => mysql[k]
731
- });
732
- });
733
- Object.keys(entityGenerator).forEach((k) => {
734
- if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
735
- enumerable: true,
736
- get: () => entityGenerator[k]
737
- });
738
- });
702
+ exports.extractAlias = extractAlias;
703
+ exports.getPrimaryKeys = getPrimaryKeys;
704
+ exports.getTableMetadata = getTableMetadata;
705
+ exports.mySqlDateString = mySqlDateString;
706
+ exports.mySqlDateTimeString = mySqlDateTimeString;
707
+ exports.mySqlTimeString = mySqlTimeString;
708
+ exports.mySqlTimestampString = mySqlTimestampString;
709
+ exports.parseDateTime = parseDateTime;
739
710
  //# sourceMappingURL=ForgeSQLORM.js.map