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