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