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