forge-sql-orm 2.0.17 → 2.0.19

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 (41) hide show
  1. package/README.md +95 -4
  2. package/dist/ForgeSQLORM.js +382 -60
  3. package/dist/ForgeSQLORM.js.map +1 -1
  4. package/dist/ForgeSQLORM.mjs +382 -60
  5. package/dist/ForgeSQLORM.mjs.map +1 -1
  6. package/dist/core/ForgeSQLAnalyseOperations.d.ts +250 -0
  7. package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -0
  8. package/dist/core/ForgeSQLCrudOperations.d.ts +1 -1
  9. package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
  10. package/dist/core/ForgeSQLORM.d.ts +12 -2
  11. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  12. package/dist/core/ForgeSQLQueryBuilder.d.ts +112 -21
  13. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  14. package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
  15. package/dist/core/SystemTables.d.ts +167 -0
  16. package/dist/core/SystemTables.d.ts.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/utils/forgeDriverProxy.d.ts +11 -0
  20. package/dist/utils/forgeDriverProxy.d.ts.map +1 -0
  21. package/dist/utils/sqlHints.d.ts +21 -0
  22. package/dist/utils/sqlHints.d.ts.map +1 -0
  23. package/dist/utils/sqlUtils.d.ts +2 -8
  24. package/dist/utils/sqlUtils.d.ts.map +1 -1
  25. package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
  26. package/dist/webtriggers/dropMigrationWebTrigger.d.ts +2 -4
  27. package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
  28. package/package.json +4 -12
  29. package/src/core/ForgeSQLAnalyseOperations.ts +461 -0
  30. package/src/core/ForgeSQLCrudOperations.ts +15 -8
  31. package/src/core/ForgeSQLORM.ts +46 -9
  32. package/src/core/ForgeSQLQueryBuilder.ts +129 -32
  33. package/src/core/ForgeSQLSelectOperations.ts +4 -6
  34. package/src/core/SystemTables.ts +175 -0
  35. package/src/index.ts +1 -0
  36. package/src/utils/forgeDriverProxy.ts +27 -0
  37. package/src/utils/sqlHints.ts +63 -0
  38. package/src/utils/sqlUtils.ts +36 -32
  39. package/src/webtriggers/applyMigrationsWebTrigger.ts +32 -16
  40. package/src/webtriggers/dropMigrationWebTrigger.ts +5 -6
  41. package/src/webtriggers/fetchSchemaWebTrigger.ts +2 -10
@@ -7,16 +7,23 @@ import { customType, mysqlTable, timestamp, varchar, bigint } from "drizzle-orm/
7
7
  import moment$1 from "moment/moment.js";
8
8
  import { getTableName } from "drizzle-orm/table";
9
9
  const parseDateTime = (value, format) => {
10
+ let result;
10
11
  const m = moment(value, format, true);
11
12
  if (!m.isValid()) {
12
- return moment(value).toDate();
13
+ const momentDate = moment(value);
14
+ if (momentDate.isValid()) {
15
+ result = momentDate.toDate();
16
+ } else {
17
+ result = new Date(value);
18
+ }
19
+ } else {
20
+ result = m.toDate();
21
+ }
22
+ if (isNaN(result.getTime())) {
23
+ result = new Date(value);
13
24
  }
14
- return m.toDate();
25
+ return result;
15
26
  };
16
- function extractAlias(query) {
17
- const match = query.match(/\bas\s+(['"`]?)([\w*]+)\1$/i);
18
- return match ? match[2] : query;
19
- }
20
27
  function getPrimaryKeys(table) {
21
28
  const { columns, primaryKeys } = getTableMetadata(table);
22
29
  const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary);
@@ -55,7 +62,7 @@ function processForeignKeys(table, foreignKeysSymbol, extraSymbol) {
55
62
  const configBuilderData = extraConfigBuilder(table);
56
63
  if (configBuilderData) {
57
64
  const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
58
- (item) => item.value || item
65
+ (item) => item.value ?? item
59
66
  );
60
67
  configBuilders.forEach((builder) => {
61
68
  if (!builder?.constructor) return;
@@ -90,7 +97,7 @@ function getTableMetadata(table) {
90
97
  const configBuilderData = extraConfigBuilder(table);
91
98
  if (configBuilderData) {
92
99
  const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
93
- (item) => item.value || item
100
+ (item) => item.value ?? item
94
101
  );
95
102
  configBuilders.forEach((builder) => {
96
103
  if (!builder?.constructor) return;
@@ -120,13 +127,9 @@ function getTableMetadata(table) {
120
127
  }
121
128
  function generateDropTableStatements(tables) {
122
129
  const dropStatements = [];
123
- tables.forEach((table) => {
124
- const tableMetadata = getTableMetadata(table);
125
- if (tableMetadata.tableName) {
126
- dropStatements.push(`DROP TABLE IF EXISTS \`${tableMetadata.tableName}\`;`);
127
- }
130
+ tables.forEach((tableName) => {
131
+ dropStatements.push(`DROP TABLE IF EXISTS \`${tableName}\`;`);
128
132
  });
129
- dropStatements.push(`DELETE FROM __migrations;`);
130
133
  return dropStatements;
131
134
  }
132
135
  function mapSelectTableToAlias(table, uniqPrefix, aliasMap) {
@@ -184,9 +187,9 @@ function getAliasFromDrizzleAlias(value) {
184
187
  const aliasNameChunk = queryChunks[queryChunks.length - 2];
185
188
  if (isSQLWrapper(aliasNameChunk) && "queryChunks" in aliasNameChunk) {
186
189
  const aliasNameChunkSql = aliasNameChunk;
187
- if (aliasNameChunkSql && aliasNameChunkSql.queryChunks.length === 1) {
190
+ if (aliasNameChunkSql.queryChunks?.length === 1 && aliasNameChunkSql.queryChunks[0]) {
188
191
  const queryChunksStringChunc = aliasNameChunkSql.queryChunks[0];
189
- if (queryChunksStringChunc && "value" in queryChunksStringChunc) {
192
+ if ("value" in queryChunksStringChunc) {
190
193
  const values = queryChunksStringChunc.value;
191
194
  if (values && values.length === 1) {
192
195
  return values[0];
@@ -238,7 +241,10 @@ function applyFromDriverTransform(rows, selections, aliasMap) {
238
241
  });
239
242
  }
240
243
  function processNullBranches(obj) {
241
- if (obj === null || typeof obj !== "object" || obj === void 0) {
244
+ if (obj === null || typeof obj !== "object") {
245
+ return obj;
246
+ }
247
+ if (obj.constructor && obj.constructor.name !== "Object") {
242
248
  return obj;
243
249
  }
244
250
  const result = {};
@@ -248,7 +254,7 @@ function processNullBranches(obj) {
248
254
  result[key] = null;
249
255
  continue;
250
256
  }
251
- if (typeof value === "object" && (value !== null || value !== void 0)) {
257
+ if (typeof value === "object") {
252
258
  const processed = processNullBranches(value);
253
259
  result[key] = processed;
254
260
  if (processed !== null) {
@@ -436,7 +442,7 @@ class ForgeSQLCrudOperations {
436
442
  let fieldName = versionMetadata.fieldName;
437
443
  let versionField = columns[versionMetadata.fieldName];
438
444
  if (!versionField) {
439
- const find = Object.entries(columns).find(([_, c]) => c.name === versionMetadata.fieldName);
445
+ const find = Object.entries(columns).find(([, c]) => c.name === versionMetadata.fieldName);
440
446
  if (find) {
441
447
  fieldName = find[0];
442
448
  versionField = find[1];
@@ -498,12 +504,20 @@ class ForgeSQLCrudOperations {
498
504
  */
499
505
  prepareModelWithVersion(model, versionMetadata, columns) {
500
506
  if (!versionMetadata || !columns) return model;
501
- const versionField = columns[versionMetadata.fieldName];
507
+ let fieldName = versionMetadata.fieldName;
508
+ let versionField = columns[versionMetadata.fieldName];
509
+ if (!versionField) {
510
+ const find = Object.entries(columns).find(([, c]) => c.name === versionMetadata.fieldName);
511
+ if (find) {
512
+ fieldName = find[0];
513
+ versionField = find[1];
514
+ }
515
+ }
502
516
  if (!versionField) return model;
503
517
  const modelWithVersion = { ...model };
504
518
  const fieldType = versionField.getSQLType();
505
519
  const versionValue = fieldType === "datetime" || fieldType === "timestamp" ? /* @__PURE__ */ new Date() : 1;
506
- modelWithVersion[versionMetadata.fieldName] = versionValue;
520
+ modelWithVersion[fieldName] = versionValue;
507
521
  return modelWithVersion;
508
522
  }
509
523
  /**
@@ -590,9 +604,8 @@ class ForgeSQLSelectOperations {
590
604
  */
591
605
  async executeRawSQL(query, params) {
592
606
  if (this.options.logRawSqlQuery) {
593
- console.debug(
594
- `Executing with SQL ${query}` + params ? `, with params: ${JSON.stringify(params)}` : ""
595
- );
607
+ const paramsStr = params ? `, with params: ${JSON.stringify(params)}` : "";
608
+ console.debug(`Executing with SQL ${query}${paramsStr}`);
596
609
  }
597
610
  const sqlStatement = sql$1.prepare(query);
598
611
  if (params) {
@@ -614,7 +627,7 @@ class ForgeSQLSelectOperations {
614
627
  }
615
628
  if (this.options.logRawSqlQuery) {
616
629
  console.debug(
617
- `Executing Update with SQL ${query}` + params ? `, with params: ${JSON.stringify(params)}` : ""
630
+ `Executing Update with SQL ${query}` + (params ? `, with params: ${JSON.stringify(params)}` : "")
618
631
  );
619
632
  }
620
633
  const updateQueryResponseResults = await sqlStatement.execute();
@@ -646,6 +659,45 @@ const forgeDriver = async (query, params, method) => {
646
659
  throw error;
647
660
  }
648
661
  };
662
+ function injectSqlHints(query, hints) {
663
+ if (!hints) {
664
+ return query;
665
+ }
666
+ const normalizedQuery = query.trim().toUpperCase();
667
+ let queryHints;
668
+ if (normalizedQuery.startsWith("SELECT")) {
669
+ queryHints = hints.select;
670
+ } else if (normalizedQuery.startsWith("INSERT")) {
671
+ queryHints = hints.insert;
672
+ } else if (normalizedQuery.startsWith("UPDATE")) {
673
+ queryHints = hints.update;
674
+ } else if (normalizedQuery.startsWith("DELETE")) {
675
+ queryHints = hints.delete;
676
+ }
677
+ if (!queryHints || queryHints.length === 0) {
678
+ return query;
679
+ }
680
+ const hintsString = queryHints.join(" ");
681
+ if (normalizedQuery.startsWith("SELECT")) {
682
+ return `SELECT /*+ ${hintsString} */ ${query.substring(6)}`;
683
+ } else if (normalizedQuery.startsWith("INSERT")) {
684
+ return `INSERT /*+ ${hintsString} */ ${query.substring(6)}`;
685
+ } else if (normalizedQuery.startsWith("UPDATE")) {
686
+ return `UPDATE /*+ ${hintsString} */ ${query.substring(6)}`;
687
+ } else if (normalizedQuery.startsWith("DELETE")) {
688
+ return `DELETE /*+ ${hintsString} */ ${query.substring(6)}`;
689
+ }
690
+ return query;
691
+ }
692
+ function createForgeDriverProxy(options, logRawSqlQuery) {
693
+ return async (query, params, method) => {
694
+ const modifiedQuery = injectSqlHints(query, options);
695
+ if (options && logRawSqlQuery && modifiedQuery !== query) {
696
+ console.warn("modified query: " + modifiedQuery);
697
+ }
698
+ return forgeDriver(modifiedQuery, params, method);
699
+ };
700
+ }
649
701
  function createAliasedSelectBuilder(db, fields, selectFn) {
650
702
  const { selections, aliasMap } = mapSelectFieldsWithAlias(fields);
651
703
  const builder = selectFn(selections);
@@ -689,11 +741,239 @@ function patchDbWithSelectAliased(db) {
689
741
  };
690
742
  return db;
691
743
  }
744
+ class ForgeSQLAnalyseOperation {
745
+ forgeOperations;
746
+ /**
747
+ * Creates a new instance of ForgeSQLAnalizeOperation.
748
+ * @param {ForgeSqlOperation} forgeOperations - The ForgeSQL operations instance
749
+ */
750
+ constructor(forgeOperations) {
751
+ this.forgeOperations = forgeOperations;
752
+ this.mapToCamelCaseClusterStatement = this.mapToCamelCaseClusterStatement.bind(this);
753
+ }
754
+ /**
755
+ * Executes EXPLAIN on a raw SQL query.
756
+ * @param {string} query - The SQL query to analyze
757
+ * @param {unknown[]} bindParams - The query parameters
758
+ * @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
759
+ */
760
+ async explainRaw(query, bindParams) {
761
+ const results = await this.forgeOperations.fetch().executeRawSQL(`EXPLAIN ${query}`, bindParams);
762
+ return results.map((row) => ({
763
+ id: row.id,
764
+ estRows: row.estRows,
765
+ actRows: row.actRows,
766
+ task: row.task,
767
+ accessObject: row["access object"],
768
+ executionInfo: row["execution info"],
769
+ operatorInfo: row["operator info"],
770
+ memory: row.memory,
771
+ disk: row.disk
772
+ }));
773
+ }
774
+ /**
775
+ * Executes EXPLAIN on a Drizzle query.
776
+ * @param {{ toSQL: () => Query }} query - The Drizzle query to analyze
777
+ * @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
778
+ */
779
+ async explain(query) {
780
+ const { sql: sql2, params } = query.toSQL();
781
+ return this.explainRaw(sql2, params);
782
+ }
783
+ /**
784
+ * Executes EXPLAIN ANALYZE on a raw SQL query.
785
+ * @param {string} query - The SQL query to analyze
786
+ * @param {unknown[]} bindParams - The query parameters
787
+ * @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
788
+ */
789
+ async explainAnalyzeRaw(query, bindParams) {
790
+ const results = await this.forgeOperations.fetch().executeRawSQL(`EXPLAIN ANALYZE ${query}`, bindParams);
791
+ return results.map((row) => ({
792
+ id: row.id,
793
+ estRows: row.estRows,
794
+ actRows: row.actRows,
795
+ task: row.task,
796
+ accessObject: row["access object"],
797
+ executionInfo: row["execution info"],
798
+ operatorInfo: row["operator info"],
799
+ memory: row.memory,
800
+ disk: row.disk
801
+ }));
802
+ }
803
+ /**
804
+ * Executes EXPLAIN ANALYZE on a Drizzle query.
805
+ * @param {{ toSQL: () => Query }} query - The Drizzle query to analyze
806
+ * @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
807
+ */
808
+ async explainAnalyze(query) {
809
+ const { sql: sql2, params } = query.toSQL();
810
+ return this.explainAnalyzeRaw(sql2, params);
811
+ }
812
+ /**
813
+ * Decodes a query execution plan from its string representation.
814
+ * @param {string} input - The raw execution plan string
815
+ * @returns {ExplainAnalyzeRow[]} The decoded execution plan rows
816
+ */
817
+ decodedPlan(input) {
818
+ if (!input) {
819
+ return [];
820
+ }
821
+ const lines = input.trim().split("\n");
822
+ if (lines.length < 2) return [];
823
+ const headersRaw = lines[0].split(" ").map((h) => h.trim()).filter(Boolean);
824
+ const headers = headersRaw.map((h) => {
825
+ return h.replace(/\s+/g, " ").replace(/[-\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (s) => s.toLowerCase());
826
+ });
827
+ return lines.slice(1).map((line) => {
828
+ const values = line.split(" ").map((s) => s.trim()).filter(Boolean);
829
+ const row = {};
830
+ headers.forEach((key, i) => {
831
+ row[key] = values[i] ?? "";
832
+ });
833
+ return row;
834
+ });
835
+ }
836
+ /**
837
+ * Normalizes a raw slow query row into a more structured format.
838
+ * @param {SlowQueryRaw} row - The raw slow query data
839
+ * @returns {SlowQueryNormalized} The normalized slow query data
840
+ */
841
+ normalizeSlowQuery(row) {
842
+ return {
843
+ time: row.Time,
844
+ txnStartTs: row.Txn_start_ts,
845
+ user: row.User,
846
+ host: row.Host,
847
+ connId: row.Conn_ID,
848
+ db: row.DB,
849
+ query: row.Query,
850
+ digest: row.Digest,
851
+ queryTime: row.Query_time,
852
+ compileTime: row.Compile_time,
853
+ optimizeTime: row.Optimize_time,
854
+ processTime: row.Process_time,
855
+ waitTime: row.Wait_time,
856
+ parseTime: row.Parse_time,
857
+ rewriteTime: row.Rewrite_time,
858
+ copTime: row.Cop_time,
859
+ copProcAvg: row.Cop_proc_avg,
860
+ copProcMax: row.Cop_proc_max,
861
+ copProcP90: row.Cop_proc_p90,
862
+ copProcAddr: row.Cop_proc_addr,
863
+ copWaitAvg: row.Cop_wait_avg,
864
+ copWaitMax: row.Cop_wait_max,
865
+ copWaitP90: row.Cop_wait_p90,
866
+ copWaitAddr: row.Cop_wait_addr,
867
+ memMax: row.Mem_max,
868
+ diskMax: row.Disk_max,
869
+ totalKeys: row.Total_keys,
870
+ processKeys: row.Process_keys,
871
+ requestCount: row.Request_count,
872
+ kvTotal: row.KV_total,
873
+ pdTotal: row.PD_total,
874
+ resultRows: row.Result_rows,
875
+ rocksdbBlockCacheHitCount: row.Rocksdb_block_cache_hit_count,
876
+ rocksdbBlockReadCount: row.Rocksdb_block_read_count,
877
+ rocksdbBlockReadByte: row.Rocksdb_block_read_byte,
878
+ plan: row.Plan,
879
+ binaryPlan: row.Binary_plan,
880
+ planDigest: row.Plan_digest,
881
+ parsedPlan: this.decodedPlan(row.Plan)
882
+ };
883
+ }
884
+ /**
885
+ * Builds a SQL query for retrieving cluster statement history.
886
+ * @param {string[]} tables - The tables to analyze
887
+ * @param {Date} [from] - The start date for the analysis
888
+ * @param {Date} [to] - The end date for the analysis
889
+ * @returns {string} The SQL query for cluster statement history
890
+ */
891
+ buildClusterStatementQuery(tables, from, to) {
892
+ const formatDateTime = (date) => moment(date).format("YYYY-MM-DDTHH:mm:ss.SSS");
893
+ const tableConditions = tables.map((table) => `TABLE_NAMES LIKE CONCAT(SCHEMA_NAME, '.', '%', '${table}', '%')`).join(" OR ");
894
+ const timeConditions = [];
895
+ if (from) {
896
+ timeConditions.push(`SUMMARY_BEGIN_TIME >= '${formatDateTime(from)}'`);
897
+ }
898
+ if (to) {
899
+ timeConditions.push(`SUMMARY_END_TIME <= '${formatDateTime(to)}'`);
900
+ }
901
+ let whereClauses;
902
+ if (tableConditions?.length) {
903
+ whereClauses = [tableConditions ? `(${tableConditions})` : "", ...timeConditions];
904
+ } else {
905
+ whereClauses = timeConditions;
906
+ }
907
+ return `
908
+ SELECT *
909
+ FROM (
910
+ SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY
911
+ UNION ALL
912
+ SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
913
+ ) AS combined
914
+ ${whereClauses?.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : ""}
915
+ `;
916
+ }
917
+ /**
918
+ * Retrieves and analyzes slow queries from the database.
919
+ * @returns {Promise<SlowQueryNormalized[]>} The normalized slow query data
920
+ */
921
+ async analyzeSlowQueries() {
922
+ const results = await this.forgeOperations.fetch().executeRawSQL(`
923
+ SELECT *
924
+ FROM information_schema.slow_query
925
+ ORDER BY time DESC
926
+ `);
927
+ return results.map((row) => this.normalizeSlowQuery(row));
928
+ }
929
+ /**
930
+ * Converts a cluster statement row to camelCase format.
931
+ * @param {Record<string, any>} input - The input row data
932
+ * @returns {ClusterStatementRowCamelCase} The converted row data
933
+ */
934
+ mapToCamelCaseClusterStatement(input) {
935
+ if (!input) {
936
+ return {};
937
+ }
938
+ const result = {};
939
+ result.parsedPlan = this.decodedPlan(input["PLAN"] ?? "");
940
+ for (const key in input) {
941
+ const camelKey = key.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
942
+ result[camelKey] = input[key];
943
+ }
944
+ return result;
945
+ }
946
+ /**
947
+ * Analyzes query history for specific tables using raw table names.
948
+ * @param {string[]} tables - The table names to analyze
949
+ * @param {Date} [fromDate] - The start date for the analysis
950
+ * @param {Date} [toDate] - The end date for the analysis
951
+ * @returns {Promise<ClusterStatementRowCamelCase[]>} The analyzed query history
952
+ */
953
+ async analyzeQueriesHistoryRaw(tables, fromDate, toDate) {
954
+ const results = await this.forgeOperations.fetch().executeRawSQL(
955
+ this.buildClusterStatementQuery(tables ?? [], fromDate, toDate)
956
+ );
957
+ return results.map((r) => this.mapToCamelCaseClusterStatement(r));
958
+ }
959
+ /**
960
+ * Analyzes query history for specific tables using Drizzle table objects.
961
+ * @param {AnyMySqlTable[]} tables - The Drizzle table objects to analyze
962
+ * @param {Date} [fromDate] - The start date for the analysis
963
+ * @param {Date} [toDate] - The end date for the analysis
964
+ * @returns {Promise<ClusterStatementRowCamelCase[]>} The analyzed query history
965
+ */
966
+ async analyzeQueriesHistory(tables, fromDate, toDate) {
967
+ const tableNames = tables?.map((table) => getTableName(table)) ?? [];
968
+ return this.analyzeQueriesHistoryRaw(tableNames, fromDate, toDate);
969
+ }
970
+ }
692
971
  class ForgeSQLORMImpl {
693
972
  static instance = null;
694
973
  drizzle;
695
974
  crudOperations;
696
975
  fetchOperations;
976
+ analyzeOperations;
697
977
  /**
698
978
  * Private constructor to enforce singleton behavior.
699
979
  * @param options - Options for configuring ForgeSQL ORM behavior.
@@ -707,25 +987,32 @@ class ForgeSQLORMImpl {
707
987
  if (newOptions.logRawSqlQuery) {
708
988
  console.debug("Initializing ForgeSQLORM...");
709
989
  }
990
+ const proxiedDriver = createForgeDriverProxy(newOptions.hints, newOptions.logRawSqlQuery);
710
991
  this.drizzle = patchDbWithSelectAliased(
711
- drizzle(forgeDriver, { logger: newOptions.logRawSqlQuery })
992
+ drizzle(proxiedDriver, { logger: newOptions.logRawSqlQuery })
712
993
  );
713
994
  this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
714
995
  this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
996
+ this.analyzeOperations = new ForgeSQLAnalyseOperation(this);
715
997
  } catch (error) {
716
998
  console.error("ForgeSQLORM initialization failed:", error);
717
999
  throw error;
718
1000
  }
719
1001
  }
1002
+ /**
1003
+ * Create the modify operations instance.
1004
+ * @returns modify operations.
1005
+ */
1006
+ modify() {
1007
+ return this.crudOperations;
1008
+ }
720
1009
  /**
721
1010
  * Returns the singleton instance of ForgeSQLORMImpl.
722
1011
  * @param options - Options for configuring ForgeSQL ORM behavior.
723
1012
  * @returns The singleton instance of ForgeSQLORMImpl.
724
1013
  */
725
1014
  static getInstance(options) {
726
- if (!ForgeSQLORMImpl.instance) {
727
- ForgeSQLORMImpl.instance = new ForgeSQLORMImpl(options);
728
- }
1015
+ ForgeSQLORMImpl.instance ??= new ForgeSQLORMImpl(options);
729
1016
  return ForgeSQLORMImpl.instance;
730
1017
  }
731
1018
  /**
@@ -733,7 +1020,7 @@ class ForgeSQLORMImpl {
733
1020
  * @returns CRUD operations.
734
1021
  */
735
1022
  crud() {
736
- return this.crudOperations;
1023
+ return this.modify();
737
1024
  }
738
1025
  /**
739
1026
  * Retrieves the fetch operations instance.
@@ -742,6 +1029,9 @@ class ForgeSQLORMImpl {
742
1029
  fetch() {
743
1030
  return this.fetchOperations;
744
1031
  }
1032
+ analyze() {
1033
+ return this.analyzeOperations;
1034
+ }
745
1035
  /**
746
1036
  * Returns a Drizzle query builder instance.
747
1037
  *
@@ -829,7 +1119,7 @@ class ForgeSQLORM {
829
1119
  *
830
1120
  * @template TSelection - The type of the selected fields
831
1121
  * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
832
- * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
1122
+ * @returns {MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>} A distinct select query builder with unique field aliases
833
1123
  * @throws {Error} If fields parameter is empty
834
1124
  * @example
835
1125
  * ```typescript
@@ -847,7 +1137,14 @@ class ForgeSQLORM {
847
1137
  * @returns CRUD operations.
848
1138
  */
849
1139
  crud() {
850
- return this.ormInstance.crud();
1140
+ return this.ormInstance.modify();
1141
+ }
1142
+ /**
1143
+ * Proxies the `modify` method from `ForgeSQLORMImpl`.
1144
+ * @returns Modify operations.
1145
+ */
1146
+ modify() {
1147
+ return this.ormInstance.modify();
851
1148
  }
852
1149
  /**
853
1150
  * Proxies the `fetch` method from `ForgeSQLORMImpl`.
@@ -856,6 +1153,13 @@ class ForgeSQLORM {
856
1153
  fetch() {
857
1154
  return this.ormInstance.fetch();
858
1155
  }
1156
+ /**
1157
+ * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
1158
+ * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
1159
+ */
1160
+ analyze() {
1161
+ return this.ormInstance.analyze();
1162
+ }
859
1163
  /**
860
1164
  * Returns a Drizzle query builder instance.
861
1165
  *
@@ -886,7 +1190,7 @@ const forgeTimestampString = customType({
886
1190
  return "timestamp";
887
1191
  },
888
1192
  toDriver(value) {
889
- return moment$1(value).format("YYYY-MM-DDTHH:mm:ss.SSS");
1193
+ return moment$1(new Date(value)).format("YYYY-MM-DDTHH:mm:ss.SSS");
890
1194
  },
891
1195
  fromDriver(value) {
892
1196
  const format = "YYYY-MM-DDTHH:mm:ss.SSS";
@@ -916,8 +1220,19 @@ const forgeTimeString = customType({
916
1220
  return parseDateTime(value, "HH:mm:ss.SSS");
917
1221
  }
918
1222
  });
919
- async function dropSchemaMigrations(tables) {
1223
+ const migrations = mysqlTable("__migrations", {
1224
+ id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
1225
+ name: varchar("name", { length: 255 }).notNull(),
1226
+ migratedAt: timestamp("migratedAt").defaultNow().notNull()
1227
+ });
1228
+ async function getTables() {
1229
+ const tables = await sql$1.executeDDL("SHOW TABLES");
1230
+ return tables.rows.flatMap((tableInfo) => Object.values(tableInfo));
1231
+ }
1232
+ const forgeSystemTables = [migrations];
1233
+ async function dropSchemaMigrations() {
920
1234
  try {
1235
+ const tables = await getTables();
921
1236
  const dropStatements = generateDropTableStatements(tables);
922
1237
  for (const statement of dropStatements) {
923
1238
  console.warn(statement);
@@ -928,32 +1243,41 @@ async function dropSchemaMigrations(tables) {
928
1243
  "⚠️ All data in these tables has been permanently deleted. This operation cannot be undone."
929
1244
  );
930
1245
  } catch (error) {
1246
+ console.error(error);
931
1247
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
932
1248
  return getHttpResponse(500, errorMessage);
933
1249
  }
934
1250
  }
935
1251
  const applySchemaMigrations = async (migration) => {
936
- console.log("Provisioning the database");
937
- await sql$1._provision();
938
- console.info("Running schema migrations");
939
- const migrations2 = await migration(migrationRunner);
940
- const successfulMigrations = await migrations2.run();
941
- console.info("Migrations applied:", successfulMigrations);
942
- const migrationHistory = (await migrationRunner.list()).map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`).join("\n");
943
- console.info("Migrations history:\nid, name, migrated_at\n", migrationHistory);
944
- return {
945
- headers: { "Content-Type": ["application/json"] },
946
- statusCode: 200,
947
- statusText: "OK",
948
- body: "Migrations successfully executed"
949
- };
1252
+ try {
1253
+ if (typeof migration !== "function") {
1254
+ throw new Error("migration is not a function");
1255
+ }
1256
+ console.log("Provisioning the database");
1257
+ await sql$1._provision();
1258
+ console.info("Running schema migrations");
1259
+ const migrations2 = await migration(migrationRunner);
1260
+ const successfulMigrations = await migrations2.run();
1261
+ console.info("Migrations applied:", successfulMigrations);
1262
+ const migrationList = await migrationRunner.list();
1263
+ const migrationHistory = Array.isArray(migrationList) && migrationList.length > 0 ? migrationList.map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`).join("\n") : "No migrations found";
1264
+ console.info("Migrations history:\nid, name, migrated_at\n", migrationHistory);
1265
+ return {
1266
+ headers: { "Content-Type": ["application/json"] },
1267
+ statusCode: 200,
1268
+ statusText: "OK",
1269
+ body: "Migrations successfully executed"
1270
+ };
1271
+ } catch (error) {
1272
+ console.error("Error during migration:", error);
1273
+ return {
1274
+ headers: { "Content-Type": ["application/json"] },
1275
+ statusCode: 500,
1276
+ statusText: "Internal Server Error",
1277
+ body: error instanceof Error ? error.message : "Unknown error during migration"
1278
+ };
1279
+ }
950
1280
  };
951
- const migrations = mysqlTable("__migrations", {
952
- id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
953
- name: varchar("name", { length: 255 }).notNull(),
954
- migratedAt: timestamp("migratedAt").defaultNow().notNull()
955
- });
956
- const forgeSystemTables = [migrations];
957
1281
  async function fetchSchemaWebTrigger() {
958
1282
  try {
959
1283
  const tables = await getTables();
@@ -966,14 +1290,10 @@ async function fetchSchemaWebTrigger() {
966
1290
  return getHttpResponse(500, errorMessage);
967
1291
  }
968
1292
  }
969
- async function getTables() {
970
- const tables = await sql$1.executeDDL("SHOW TABLES");
971
- return tables.rows.flatMap((tableInfo) => Object.values(tableInfo));
972
- }
973
1293
  async function generateCreateTableStatements(tables) {
974
1294
  const statements = [];
975
1295
  for (const table of tables) {
976
- const createTableResult = await sql$1.executeDDL(`SHOW CREATE TABLE ${table}`);
1296
+ const createTableResult = await sql$1.executeDDL(`SHOW CREATE TABLE "${table}"`);
977
1297
  const createTableStatements = createTableResult.rows.filter((row) => !isSystemTable(row.Table)).map((row) => formatCreateTableStatement(row["Create Table"]));
978
1298
  statements.push(...createTableStatements);
979
1299
  }
@@ -1009,19 +1329,21 @@ export {
1009
1329
  applySchemaMigrations,
1010
1330
  ForgeSQLORM as default,
1011
1331
  dropSchemaMigrations,
1012
- extractAlias,
1013
1332
  fetchSchemaWebTrigger,
1014
1333
  forgeDateString,
1015
1334
  forgeDateTimeString,
1016
1335
  forgeDriver,
1336
+ forgeSystemTables,
1017
1337
  forgeTimeString,
1018
1338
  forgeTimestampString,
1019
1339
  generateDropTableStatements,
1020
1340
  getHttpResponse,
1021
1341
  getPrimaryKeys,
1022
1342
  getTableMetadata,
1343
+ getTables,
1023
1344
  mapSelectAllFieldsToAlias,
1024
1345
  mapSelectFieldsWithAlias,
1346
+ migrations,
1025
1347
  parseDateTime,
1026
1348
  patchDbWithSelectAliased
1027
1349
  };