forge-sql-orm 2.1.22 → 2.1.24

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 (44) hide show
  1. package/README.md +6 -2
  2. package/dist/core/ForgeSQLAnalyseOperations.d.ts +4 -0
  3. package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -1
  4. package/dist/core/ForgeSQLAnalyseOperations.js +17 -21
  5. package/dist/core/ForgeSQLAnalyseOperations.js.map +1 -1
  6. package/dist/core/ForgeSQLCrudOperations.d.ts +16 -0
  7. package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
  8. package/dist/core/ForgeSQLCrudOperations.js +60 -28
  9. package/dist/core/ForgeSQLCrudOperations.js.map +1 -1
  10. package/dist/core/ForgeSQLQueryBuilder.d.ts +15 -28
  11. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  12. package/dist/core/ForgeSQLQueryBuilder.js +20 -47
  13. package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
  14. package/dist/core/Rovo.d.ts +32 -0
  15. package/dist/core/Rovo.d.ts.map +1 -1
  16. package/dist/core/Rovo.js +116 -67
  17. package/dist/core/Rovo.js.map +1 -1
  18. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  19. package/dist/lib/drizzle/extensions/additionalActions.js +168 -118
  20. package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -1
  21. package/dist/utils/cacheTableUtils.d.ts +0 -8
  22. package/dist/utils/cacheTableUtils.d.ts.map +1 -1
  23. package/dist/utils/cacheTableUtils.js +183 -126
  24. package/dist/utils/cacheTableUtils.js.map +1 -1
  25. package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
  26. package/dist/utils/forgeDriverProxy.js +31 -20
  27. package/dist/utils/forgeDriverProxy.js.map +1 -1
  28. package/dist/utils/sqlHints.d.ts.map +1 -1
  29. package/dist/utils/sqlHints.js +19 -29
  30. package/dist/utils/sqlHints.js.map +1 -1
  31. package/dist/utils/sqlUtils.d.ts +0 -29
  32. package/dist/utils/sqlUtils.d.ts.map +1 -1
  33. package/dist/utils/sqlUtils.js +107 -78
  34. package/dist/utils/sqlUtils.js.map +1 -1
  35. package/package.json +13 -13
  36. package/src/core/ForgeSQLAnalyseOperations.ts +18 -21
  37. package/src/core/ForgeSQLCrudOperations.ts +83 -33
  38. package/src/core/ForgeSQLQueryBuilder.ts +59 -154
  39. package/src/core/Rovo.ts +158 -98
  40. package/src/lib/drizzle/extensions/additionalActions.ts +287 -382
  41. package/src/utils/cacheTableUtils.ts +202 -144
  42. package/src/utils/forgeDriverProxy.ts +39 -21
  43. package/src/utils/sqlHints.ts +21 -26
  44. package/src/utils/sqlUtils.ts +151 -101
@@ -1,3 +1,4 @@
1
+ // qlty-ignore: +qlty:file-complexity
1
2
  import {
2
3
  and,
3
4
  AnyColumn,
@@ -60,40 +61,46 @@ interface ConfigBuilderData {
60
61
  [key: string]: any;
61
62
  }
62
63
 
64
+ /**
65
+ * Parses a string value using multiple DateTime parsers.
66
+ */
67
+ function parseStringToDate(value: string, format: string): Date {
68
+ // 1. Try to parse using the provided format (strict mode)
69
+ const dt = DateTime.fromFormat(value, format);
70
+ if (dt.isValid) {
71
+ return dt.toJSDate();
72
+ }
73
+
74
+ // 2. Try to parse as SQL string
75
+ const sqlDt = DateTime.fromSQL(value);
76
+ if (sqlDt.isValid) {
77
+ return sqlDt.toJSDate();
78
+ }
79
+
80
+ // 3. Try to parse as RFC2822 string
81
+ const isoDt = DateTime.fromRFC2822(value);
82
+ if (isoDt.isValid) {
83
+ return isoDt.toJSDate();
84
+ }
85
+
86
+ // 4. Fallback: use native Date constructor
87
+ return new Date(value);
88
+ }
89
+
63
90
  /**
64
91
  * Parses a date string into a Date object using the specified format
65
92
  * @param value - The date string to parse or Date
66
93
  * @param format - The format to use for parsing
67
94
  * @returns Date object
68
95
  */
69
-
70
96
  export const parseDateTime = (value: string | Date, format: string): Date => {
71
97
  let result: Date;
72
98
  if (value instanceof Date) {
73
99
  result = value;
74
100
  } else {
75
- // 1. Try to parse using the provided format (strict mode)
76
- const dt = DateTime.fromFormat(value, format);
77
- if (dt.isValid) {
78
- result = dt.toJSDate();
79
- } else {
80
- // 2. Try to parse as SQL string
81
- const sqlDt = DateTime.fromSQL(value);
82
- if (sqlDt.isValid) {
83
- result = sqlDt.toJSDate();
84
- } else {
85
- // 3. Try to parse as RFC2822 string
86
- const isoDt = DateTime.fromRFC2822(value);
87
- if (isoDt.isValid) {
88
- result = isoDt.toJSDate();
89
- } else {
90
- // 4. Fallback: use native Date constructor
91
- result = new Date(value);
92
- }
93
- }
94
- }
101
+ result = parseStringToDate(value, format);
95
102
  }
96
- // 4. Ensure the result is a valid Date object
103
+ // Ensure the result is a valid Date object
97
104
  if (Number.isNaN(result.getTime())) {
98
105
  result = new Date(value);
99
106
  }
@@ -667,38 +674,61 @@ export function applyFromDriverTransform<T, TSelection>(
667
674
  });
668
675
  }
669
676
 
677
+ /**
678
+ * Checks if an object is a plain object (not Date, Array, etc.).
679
+ */
680
+ function isPlainObject(obj: unknown): boolean {
681
+ return (
682
+ obj !== null &&
683
+ typeof obj === "object" &&
684
+ (!obj.constructor || obj.constructor.name === "Object")
685
+ );
686
+ }
687
+
688
+ /**
689
+ * Processes a single value in the null branches processing.
690
+ */
691
+ function processValueEntry(
692
+ key: string,
693
+ value: unknown,
694
+ result: Record<string, unknown>,
695
+ allNull: { value: boolean },
696
+ ): void {
697
+ if (value === null || value === undefined) {
698
+ result[key] = null;
699
+ return;
700
+ }
701
+
702
+ if (typeof value === "object" && isPlainObject(value)) {
703
+ const processed = processNullBranches(value as Record<string, unknown>);
704
+ result[key] = processed;
705
+ if (processed !== null) {
706
+ allNull.value = false;
707
+ }
708
+ } else {
709
+ result[key] = value;
710
+ allNull.value = false;
711
+ }
712
+ }
713
+
670
714
  function processNullBranches(obj: Record<string, unknown>): Record<string, unknown> | null {
671
715
  if (obj === null || typeof obj !== "object") {
672
716
  return obj;
673
717
  }
674
718
 
675
719
  // Skip built-in objects like Date, Array, etc.
676
- if (obj.constructor && obj.constructor.name !== "Object") {
720
+ if (!isPlainObject(obj)) {
677
721
  return obj;
678
722
  }
679
723
 
680
724
  const result: Record<string, unknown> = {};
681
- let allNull = true;
725
+ const allNull = { value: true };
682
726
 
683
727
  for (const [key, value] of Object.entries(obj)) {
684
- if (value === null || value === undefined) {
685
- result[key] = null;
686
- continue;
687
- }
688
-
689
- if (typeof value === "object") {
690
- const processed = processNullBranches(value as Record<string, unknown>);
691
- result[key] = processed;
692
- if (processed !== null) {
693
- allNull = false;
694
- }
695
- } else {
696
- result[key] = value;
697
- allNull = false;
698
- }
728
+ processValueEntry(key, value, result, allNull);
699
729
  }
700
730
 
701
- return allNull ? null : result;
731
+ return allNull.value ? null : result;
702
732
  }
703
733
 
704
734
  export function formatLimitOffset(limitOrOffset: number): number {
@@ -745,6 +775,37 @@ function buildClusterStatementsSummaryQuery(forgeSQLORM: ForgeSqlOperation, time
745
775
  );
746
776
  }
747
777
 
778
+ /**
779
+ * Formats and logs query performance result.
780
+ */
781
+ function formatAndLogQueryResult(result: {
782
+ digestText: string;
783
+ avgLatency: number | bigint;
784
+ avgMem: number | bigint;
785
+ stmtType: string;
786
+ execCount: number | bigint;
787
+ plan: string | null;
788
+ }): void {
789
+ const avgTimeMs = Number(result.avgLatency) / 1_000_000;
790
+ const avgMemMB = Number(result.avgMem) / 1_000_000;
791
+
792
+ // eslint-disable-next-line no-console
793
+ console.warn(
794
+ `SQL: ${result.digestText} | Memory: ${avgMemMB.toFixed(2)} MB | Time: ${avgTimeMs.toFixed(2)} ms | stmtType: ${result.stmtType} | Executions: ${Number(result.execCount)}\n Plan:${result.plan}`,
795
+ );
796
+ }
797
+
798
+ /**
799
+ * Handles errors in query execution plan retrieval.
800
+ */
801
+ function handleQueryPlanError(error: unknown): void {
802
+ // eslint-disable-next-line no-console
803
+ console.debug(
804
+ `Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`,
805
+ error,
806
+ );
807
+ }
808
+
748
809
  /**
749
810
  * Analyzes and prints query performance data from CLUSTER_STATEMENTS_SUMMARY table.
750
811
  *
@@ -785,22 +846,10 @@ export async function printQueriesWithPlan(
785
846
  );
786
847
 
787
848
  for (const result of results) {
788
- // Average execution time (convert from nanoseconds to milliseconds)
789
- const avgTimeMs = Number(result.avgLatency) / 1_000_000;
790
- const avgMemMB = Number(result.avgMem) / 1_000_000;
791
-
792
- // 1. Query info: SQL, memory, time, executions
793
- // eslint-disable-next-line no-console
794
- console.warn(
795
- `SQL: ${result.digestText} | Memory: ${avgMemMB.toFixed(2)} MB | Time: ${avgTimeMs.toFixed(2)} ms | stmtType: ${result.stmtType} | Executions: ${result.execCount}\n Plan:${result.plan}`,
796
- );
849
+ formatAndLogQueryResult(result);
797
850
  }
798
851
  } catch (error) {
799
- // eslint-disable-next-line no-console
800
- console.debug(
801
- `Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`,
802
- error,
803
- );
852
+ handleQueryPlanError(error);
804
853
  }
805
854
  }
806
855
 
@@ -823,22 +872,10 @@ export async function handleErrorsWithPlan(
823
872
  );
824
873
 
825
874
  for (const result of results) {
826
- // Average execution time (convert from nanoseconds to milliseconds)
827
- const avgTimeMs = Number(result.avgLatency) / 1_000_000;
828
- const avgMemMB = Number(result.avgMem) / 1_000_000;
829
-
830
- // 1. Query info: SQL, memory, time, executions
831
- // eslint-disable-next-line no-console
832
- console.warn(
833
- `SQL: ${result.digestText} | Memory: ${avgMemMB.toFixed(2)} MB | Time: ${avgTimeMs.toFixed(2)} ms | stmtType: ${result.stmtType} | Executions: ${result.execCount}\n Plan:${result.plan}`,
834
- );
875
+ formatAndLogQueryResult(result);
835
876
  }
836
877
  } catch (error) {
837
- // eslint-disable-next-line no-console
838
- console.debug(
839
- `Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`,
840
- error,
841
- );
878
+ handleQueryPlanError(error);
842
879
  }
843
880
  }
844
881
 
@@ -873,6 +910,48 @@ const SESSION_ALIAS_NAME_ORM = "orm";
873
910
  *
874
911
  * @throws Does not throw - errors are logged to console.debug instead
875
912
  */
913
+ /**
914
+ * Builds slow query query for specified hours.
915
+ */
916
+ function buildSlowQueryQuery(forgeSQLORM: ForgeSqlOperation, hours: number) {
917
+ return forgeSQLORM
918
+ .getDrizzleQueryBuilder()
919
+ .select({
920
+ query: withTidbHint(slowQuery.query),
921
+ queryTime: slowQuery.queryTime,
922
+ memMax: slowQuery.memMax,
923
+ plan: slowQuery.plan,
924
+ })
925
+ .from(slowQuery)
926
+ .where(
927
+ and(
928
+ isNotNull(slowQuery.digest),
929
+ ne(slowQuery.sessionAlias, SESSION_ALIAS_NAME_ORM),
930
+ gte(
931
+ slowQuery.time,
932
+ sql`DATE_SUB
933
+ (NOW(), INTERVAL
934
+ ${hours}
935
+ HOUR
936
+ )`,
937
+ ),
938
+ ),
939
+ );
940
+ }
941
+
942
+ /**
943
+ * Formats slow query result message.
944
+ */
945
+ function formatSlowQueryMessage(result: {
946
+ query: string | null;
947
+ queryTime: number | null;
948
+ memMax: number | bigint | null;
949
+ plan: string | null;
950
+ }): string {
951
+ const memMaxMB = result.memMax ? Number(result.memMax) / 1_000_000 : 0;
952
+ return `Found SlowQuery SQL: ${result.query} | Memory: ${memMaxMB.toFixed(2)} MB | Time: ${result.queryTime} ms\n Plan:${result.plan}`;
953
+ }
954
+
876
955
  export async function slowQueryPerHours(
877
956
  forgeSQLORM: ForgeSqlOperation,
878
957
  hours: number,
@@ -880,51 +959,22 @@ export async function slowQueryPerHours(
880
959
  ) {
881
960
  try {
882
961
  const timeoutMs = timeout ?? 1500;
962
+ const query = buildSlowQueryQuery(forgeSQLORM, hours);
883
963
  const results = await withTimeout(
884
- forgeSQLORM
885
- .getDrizzleQueryBuilder()
886
- .select({
887
- query: withTidbHint(slowQuery.query),
888
- queryTime: slowQuery.queryTime,
889
- memMax: slowQuery.memMax,
890
- plan: slowQuery.plan,
891
- })
892
- .from(slowQuery)
893
- .where(
894
- and(
895
- isNotNull(slowQuery.digest),
896
- ne(slowQuery.sessionAlias, SESSION_ALIAS_NAME_ORM),
897
- gte(
898
- slowQuery.time,
899
- sql`DATE_SUB
900
- (NOW(), INTERVAL
901
- ${hours}
902
- HOUR
903
- )`,
904
- ),
905
- ),
906
- ),
964
+ query,
907
965
  `Timeout ${timeoutMs}ms in slowQueryPerHours - transient timeouts are usually fine; repeated timeouts mean this diagnostic query is consistently slow and should be investigated`,
908
966
  timeoutMs,
909
967
  );
910
968
  const response: string[] = [];
911
969
  for (const result of results) {
912
- // Convert memory from bytes to MB and handle null values
913
- const memMaxMB = result.memMax ? Number(result.memMax) / 1_000_000 : 0;
914
-
915
- const message = `Found SlowQuery SQL: ${result.query} | Memory: ${memMaxMB.toFixed(2)} MB | Time: ${result.queryTime} ms\n Plan:${result.plan}`;
970
+ const message = formatSlowQueryMessage(result);
916
971
  response.push(message);
917
- // 1. Query info: SQL, memory, time, executions
918
972
  // eslint-disable-next-line no-console
919
973
  console.warn(message);
920
974
  }
921
975
  return response;
922
976
  } catch (error) {
923
- // eslint-disable-next-line no-console
924
- console.debug(
925
- `Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`,
926
- error,
927
- );
977
+ handleQueryPlanError(error);
928
978
  return [
929
979
  `Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}`,
930
980
  ];