forge-sql-orm 2.1.4 → 2.1.5

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 (45) hide show
  1. package/README.md +91 -5
  2. package/dist/ForgeSQLORM.js +122 -17
  3. package/dist/ForgeSQLORM.js.map +1 -1
  4. package/dist/ForgeSQLORM.mjs +122 -17
  5. package/dist/ForgeSQLORM.mjs.map +1 -1
  6. package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
  7. package/dist/core/ForgeSQLORM.d.ts +23 -0
  8. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  9. package/dist/core/ForgeSQLQueryBuilder.d.ts +36 -5
  10. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  11. package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
  12. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  13. package/dist/utils/cacheContextUtils.d.ts.map +1 -1
  14. package/dist/utils/cacheUtils.d.ts.map +1 -1
  15. package/dist/utils/forgeDriver.d.ts +21 -0
  16. package/dist/utils/forgeDriver.d.ts.map +1 -1
  17. package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
  18. package/dist/utils/metadataContextUtils.d.ts +11 -0
  19. package/dist/utils/metadataContextUtils.d.ts.map +1 -0
  20. package/dist/utils/sqlUtils.d.ts.map +1 -1
  21. package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
  22. package/dist/webtriggers/clearCacheSchedulerTrigger.d.ts.map +1 -1
  23. package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
  24. package/dist/webtriggers/dropTablesMigrationWebTrigger.d.ts.map +1 -1
  25. package/dist/webtriggers/fetchSchemaWebTrigger.d.ts.map +1 -1
  26. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +24 -7
  27. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +1 -1
  28. package/package.json +1 -1
  29. package/src/core/ForgeSQLCrudOperations.ts +3 -0
  30. package/src/core/ForgeSQLORM.ts +119 -51
  31. package/src/core/ForgeSQLQueryBuilder.ts +51 -17
  32. package/src/core/ForgeSQLSelectOperations.ts +2 -0
  33. package/src/lib/drizzle/extensions/additionalActions.ts +2 -0
  34. package/src/utils/cacheContextUtils.ts +4 -2
  35. package/src/utils/cacheUtils.ts +20 -8
  36. package/src/utils/forgeDriver.ts +22 -1
  37. package/src/utils/forgeDriverProxy.ts +2 -0
  38. package/src/utils/metadataContextUtils.ts +24 -0
  39. package/src/utils/sqlUtils.ts +1 -0
  40. package/src/webtriggers/applyMigrationsWebTrigger.ts +5 -2
  41. package/src/webtriggers/clearCacheSchedulerTrigger.ts +1 -0
  42. package/src/webtriggers/dropMigrationWebTrigger.ts +2 -0
  43. package/src/webtriggers/dropTablesMigrationWebTrigger.ts +2 -0
  44. package/src/webtriggers/fetchSchemaWebTrigger.ts +1 -0
  45. package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +72 -17
@@ -38,6 +38,8 @@ import { cacheApplicationContext, localCacheApplicationContext } from "../utils/
38
38
  import { clearTablesCache } from "../utils/cacheUtils";
39
39
  import { SQLWrapper } from "drizzle-orm/sql/sql";
40
40
  import { WithSubquery } from "drizzle-orm/subquery";
41
+ import { ForgeSQLMetadata } from "../utils/forgeDriver";
42
+ import {getLastestMetadata, metadataQueryContext} from "../utils/metadataContextUtils";
41
43
 
42
44
  /**
43
45
  * Implementation of ForgeSQLORM that uses Drizzle ORM for query building.
@@ -45,59 +47,100 @@ import { WithSubquery } from "drizzle-orm/subquery";
45
47
  * to use Drizzle's query builder while executing queries through Forge SQL.
46
48
  */
47
49
  class ForgeSQLORMImpl implements ForgeSqlOperation {
48
- private static instance: ForgeSQLORMImpl | null = null;
49
- private readonly drizzle: MySqlRemoteDatabase<any> & {
50
- selectAliased: SelectAliasedType;
51
- selectAliasedDistinct: SelectAliasedDistinctType;
52
- selectAliasedCacheable: SelectAliasedCacheableType;
53
- selectAliasedDistinctCacheable: SelectAliasedDistinctCacheableType;
54
- insertWithCacheContext: InsertAndEvictCacheType;
55
- insertAndEvictCache: InsertAndEvictCacheType;
56
- updateAndEvictCache: UpdateAndEvictCacheType;
57
- updateWithCacheContext: UpdateAndEvictCacheType;
58
- deleteAndEvictCache: DeleteAndEvictCacheType;
59
- deleteWithCacheContext: DeleteAndEvictCacheType;
60
- };
61
- private readonly crudOperations: VerioningModificationForgeSQL;
62
- private readonly fetchOperations: SchemaSqlForgeSql;
63
- private readonly analyzeOperations: SchemaAnalyzeForgeSql;
64
- private readonly cacheOperations: ForgeSQLCacheOperations;
65
- private readonly options: ForgeSqlOrmOptions;
50
+ private static instance: ForgeSQLORMImpl | null = null;
51
+ private readonly drizzle: MySqlRemoteDatabase<any> & {
52
+ selectAliased: SelectAliasedType;
53
+ selectAliasedDistinct: SelectAliasedDistinctType;
54
+ selectAliasedCacheable: SelectAliasedCacheableType;
55
+ selectAliasedDistinctCacheable: SelectAliasedDistinctCacheableType;
56
+ insertWithCacheContext: InsertAndEvictCacheType;
57
+ insertAndEvictCache: InsertAndEvictCacheType;
58
+ updateAndEvictCache: UpdateAndEvictCacheType;
59
+ updateWithCacheContext: UpdateAndEvictCacheType;
60
+ deleteAndEvictCache: DeleteAndEvictCacheType;
61
+ deleteWithCacheContext: DeleteAndEvictCacheType;
62
+ };
63
+ private readonly crudOperations: VerioningModificationForgeSQL;
64
+ private readonly fetchOperations: SchemaSqlForgeSql;
65
+ private readonly analyzeOperations: SchemaAnalyzeForgeSql;
66
+ private readonly cacheOperations: ForgeSQLCacheOperations;
67
+ private readonly options: ForgeSqlOrmOptions;
66
68
 
67
- /**
68
- * Private constructor to enforce singleton behavior.
69
- * @param options - Options for configuring ForgeSQL ORM behavior.
70
- */
71
- private constructor(options?: ForgeSqlOrmOptions) {
72
- try {
73
- const newOptions: ForgeSqlOrmOptions = options ?? {
74
- logRawSqlQuery: false,
75
- disableOptimisticLocking: false,
76
- cacheWrapTable: true,
77
- cacheTTL: 120,
78
- cacheEntityQueryName: "sql",
79
- cacheEntityExpirationName: "expiration",
80
- cacheEntityDataName: "data",
81
- };
82
- this.options = newOptions;
83
- if (newOptions.logRawSqlQuery) {
84
- console.debug("Initializing ForgeSQLORM...");
85
- }
86
- // Initialize Drizzle instance with our custom driver
87
- const proxiedDriver = createForgeDriverProxy(newOptions.hints, newOptions.logRawSqlQuery);
88
- this.drizzle = patchDbWithSelectAliased(
89
- drizzle(proxiedDriver, { logger: newOptions.logRawSqlQuery }),
90
- newOptions,
91
- );
92
- this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
93
- this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
94
- this.analyzeOperations = new ForgeSQLAnalyseOperation(this);
95
- this.cacheOperations = new ForgeSQLCacheOperations(newOptions, this);
96
- } catch (error) {
97
- console.error("ForgeSQLORM initialization failed:", error);
98
- throw error;
69
+ /**
70
+ * Private constructor to enforce singleton behavior.
71
+ * @param options - Options for configuring ForgeSQL ORM behavior.
72
+ */
73
+ private constructor(options?: ForgeSqlOrmOptions) {
74
+ try {
75
+ const newOptions: ForgeSqlOrmOptions = options ?? {
76
+ logRawSqlQuery: false,
77
+ logCache: false,
78
+ disableOptimisticLocking: false,
79
+ cacheWrapTable: true,
80
+ cacheTTL: 120,
81
+ cacheEntityQueryName: "sql",
82
+ cacheEntityExpirationName: "expiration",
83
+ cacheEntityDataName: "data",
84
+ };
85
+ this.options = newOptions;
86
+ if (newOptions.logRawSqlQuery) {
87
+ // eslint-disable-next-line no-console
88
+ console.debug("Initializing ForgeSQLORM...");
89
+ }
90
+ // Initialize Drizzle instance with our custom driver
91
+ const proxiedDriver = createForgeDriverProxy(newOptions.hints, newOptions.logRawSqlQuery);
92
+ this.drizzle = patchDbWithSelectAliased(
93
+ drizzle(proxiedDriver, {logger: newOptions.logRawSqlQuery}),
94
+ newOptions,
95
+ );
96
+ this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
97
+ this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
98
+ this.analyzeOperations = new ForgeSQLAnalyseOperation(this);
99
+ this.cacheOperations = new ForgeSQLCacheOperations(newOptions, this);
100
+ } catch (error) {
101
+ // eslint-disable-next-line no-console
102
+ console.error("ForgeSQLORM initialization failed:", error);
103
+ throw error;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Executes a query and provides access to execution metadata.
109
+ * This method allows you to capture detailed information about query execution
110
+ * including database execution time, response size, and Forge SQL metadata.
111
+ *
112
+ * @template T - The return type of the query
113
+ * @param query - A function that returns a Promise with the query result
114
+ * @param onMetadata - Callback function that receives execution metadata
115
+ * @returns Promise with the query result
116
+ * @example
117
+ * ```typescript
118
+ * const result = await forgeSQL.executeWithMetadata(
119
+ * async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
120
+ * (dbTime, responseSize, metadata) => {
121
+ * console.log(`DB execution time: ${dbTime}ms`);
122
+ * console.log(`Response size: ${responseSize} bytes`);
123
+ * console.log('Forge metadata:', metadata);
124
+ * }
125
+ * );
126
+ * ```
127
+ */
128
+ async executeWithMetadata<T>(query: () => Promise<T>, onMetadata: (totalDbExecutionTime: number, totalResponseSize: number, forgeMetadata: ForgeSQLMetadata) => Promise<void> | void): Promise<T> {
129
+ return metadataQueryContext.run({
130
+ totalDbExecutionTime: 0,
131
+ totalResponseSize: 0,
132
+ }, async ()=>{
133
+ try {
134
+ return await query();
135
+ } finally {
136
+ const metadata = await getLastestMetadata();
137
+ if (metadata && metadata.lastMetadata){
138
+ await onMetadata(metadata.totalDbExecutionTime, metadata.totalResponseSize, metadata.lastMetadata)
139
+ }
140
+ }
141
+
142
+ })
99
143
  }
100
- }
101
144
 
102
145
  /**
103
146
  * Executes operations within a cache context that collects cache eviction events.
@@ -610,6 +653,31 @@ class ForgeSQLORM implements ForgeSqlOperation {
610
653
  this.ormInstance = ForgeSQLORMImpl.getInstance(options);
611
654
  }
612
655
 
656
+ /**
657
+ * Executes a query and provides access to execution metadata.
658
+ * This method allows you to capture detailed information about query execution
659
+ * including database execution time, response size, and Forge SQL metadata.
660
+ *
661
+ * @template T - The return type of the query
662
+ * @param query - A function that returns a Promise with the query result
663
+ * @param onMetadata - Callback function that receives execution metadata
664
+ * @returns Promise with the query result
665
+ * @example
666
+ * ```typescript
667
+ * const result = await forgeSQL.executeWithMetadata(
668
+ * async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
669
+ * (dbTime, responseSize, metadata) => {
670
+ * console.log(`DB execution time: ${dbTime}ms`);
671
+ * console.log(`Response size: ${responseSize} bytes`);
672
+ * console.log('Forge metadata:', metadata);
673
+ * }
674
+ * );
675
+ * ```
676
+ */
677
+ async executeWithMetadata<T>(query: () => Promise<T>, onMetadata: (totalDbExecutionTime: number, totalResponseSize: number, forgeMetadata: ForgeSQLMetadata) => Promise<void> | void): Promise<T> {
678
+ return this.ormInstance.executeWithMetadata(query, onMetadata);
679
+ }
680
+
613
681
  selectCacheable<TSelection extends SelectedFields>(
614
682
  fields: TSelection,
615
683
  cacheTTL?: number,
@@ -26,19 +26,19 @@ import {
26
26
  } from "./SystemTables";
27
27
  import { ForgeSQLCacheOperations } from "./ForgeSQLCacheOperations";
28
28
  import {
29
- DeleteAndEvictCacheType,
30
- ExecuteQuery,
31
- ExecuteQueryCacheable,
32
- InsertAndEvictCacheType,
33
- SelectAliasedCacheableType,
34
- SelectAliasedDistinctCacheableType,
35
- SelectAliasedDistinctType,
36
- SelectAliasedType,
37
- SelectAllDistinctFromAliasedType,
38
- SelectAllDistinctFromCacheableAliasedType,
39
- SelectAllFromAliasedType,
40
- SelectAllFromCacheableAliasedType,
41
- UpdateAndEvictCacheType,
29
+ DeleteAndEvictCacheType,
30
+ ExecuteQuery,
31
+ ExecuteQueryCacheable, ForgeSQLMetadata,
32
+ InsertAndEvictCacheType,
33
+ SelectAliasedCacheableType,
34
+ SelectAliasedDistinctCacheableType,
35
+ SelectAliasedDistinctType,
36
+ SelectAliasedType,
37
+ SelectAllDistinctFromAliasedType,
38
+ SelectAllDistinctFromCacheableAliasedType,
39
+ SelectAllFromAliasedType,
40
+ SelectAllFromCacheableAliasedType,
41
+ UpdateAndEvictCacheType,
42
42
  } from "..";
43
43
  import {
44
44
  MySqlDeleteBase,
@@ -497,6 +497,31 @@ export interface QueryBuilderForgeSql {
497
497
  */
498
498
  executeWithLocalCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T>;
499
499
 
500
+ /**
501
+ * Executes a query and provides access to execution metadata.
502
+ * This method allows you to capture detailed information about query execution
503
+ * including database execution time, response size, and Forge SQL metadata.
504
+ *
505
+ * @template T - The return type of the query
506
+ * @param query - A function that returns a Promise with the query result
507
+ * @param onMetadata - Callback function that receives execution metadata
508
+ * @returns Promise with the query result
509
+ * @example
510
+ * ```typescript
511
+ * const result = await forgeSQL.executeWithMetadata(
512
+ * async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
513
+ * (dbTime, responseSize, metadata) => {
514
+ * console.log(`DB execution time: ${dbTime}ms`);
515
+ * console.log(`Response size: ${responseSize} bytes`);
516
+ * console.log('Forge metadata:', metadata);
517
+ * }
518
+ * );
519
+ * ```
520
+ */
521
+ executeWithMetadata<T>(
522
+ query: () => Promise<T>,
523
+ onMetadata: (totalDbExecutionTime: number, totalResponseSize: number, forgeMetadata: ForgeSQLMetadata) => Promise<void> | void
524
+ ): Promise<T>;
500
525
  /**
501
526
  * Executes a raw SQL query with local cache support.
502
527
  * This method provides local caching for raw SQL queries within the current invocation context.
@@ -816,22 +841,31 @@ export type AdditionalMetadata = Record<string, TableMetadata>;
816
841
  * @interface ForgeSqlOrmOptions
817
842
  */
818
843
  export interface ForgeSqlOrmOptions {
819
- /** Whether to log raw SQL queries */
844
+ /** Whether to log raw SQL queries to the console */
820
845
  logRawSqlQuery?: boolean;
821
- /** Whether to disable optimistic locking */
846
+ /** Whether to log cache operations (hits, misses, evictions) */
847
+ logCache?: boolean;
848
+ /** Whether to disable optimistic locking for update operations */
822
849
  disableOptimisticLocking?: boolean;
823
- /** SQL hints to be applied to queries */
850
+ /** SQL hints to be applied to queries for optimization */
824
851
  hints?: SqlHints;
852
+ /** Default Cache TTL (Time To Live) in seconds */
825
853
  cacheTTL?: number;
854
+ /** Name of the KVS entity used for cache storage */
826
855
  cacheEntityName?: string;
856
+ /** Name of the field in cache entity that stores SQL query */
827
857
  cacheEntityQueryName?: string;
858
+ /** Whether to wrap table names with backticks in cache keys */
828
859
  cacheWrapTable?: boolean;
860
+ /** Name of the field in cache entity that stores expiration timestamp */
829
861
  cacheEntityExpirationName?: string;
862
+ /** Name of the field in cache entity that stores cached data */
830
863
  cacheEntityDataName?: string;
831
864
 
832
865
  /**
833
866
  * Additional metadata for table configuration.
834
- * Allows specifying table-specific settings and behaviors.
867
+ * Allows specifying table-specific settings and behaviors such as version fields for optimistic locking.
868
+ *
835
869
  * @example
836
870
  * ```typescript
837
871
  * {
@@ -58,6 +58,7 @@ export class ForgeSQLSelectOperations implements SchemaSqlForgeSql {
58
58
  async executeRawSQL<T extends object | unknown>(query: string, params?: unknown[]): Promise<T[]> {
59
59
  if (this.options.logRawSqlQuery) {
60
60
  const paramsStr = params ? `, with params: ${JSON.stringify(params)}` : "";
61
+ // eslint-disable-next-line no-console
61
62
  console.debug(`Executing with SQL ${query}${paramsStr}`);
62
63
  }
63
64
  const sqlStatement = sql.prepare<T>(query);
@@ -80,6 +81,7 @@ export class ForgeSQLSelectOperations implements SchemaSqlForgeSql {
80
81
  sqlStatement.bindParams(...params);
81
82
  }
82
83
  if (this.options.logRawSqlQuery) {
84
+ // eslint-disable-next-line no-console
83
85
  console.debug(
84
86
  `Executing Update with SQL ${query}` +
85
87
  (params ? `, with params: ${JSON.stringify(params)}` : ""),
@@ -268,6 +268,7 @@ async function handleSuccessfulExecution(
268
268
  await evictLocalCacheQuery(table, options);
269
269
  if (isCached) {
270
270
  await clearCache(table, options).catch((e) => {
271
+ // eslint-disable-next-line no-console
271
272
  console.warn("Ignore cache clear errors", e);
272
273
  });
273
274
  } else {
@@ -457,6 +458,7 @@ async function handleCachedQuery(
457
458
  await saveQueryLocalCacheQuery(target, transformed, options);
458
459
  await setCacheResult(target, options, transformed, cacheTtl).catch((cacheError) => {
459
460
  // Log cache error but don't fail the query
461
+ // eslint-disable-next-line no-console
460
462
  console.warn("Cache set error:", cacheError);
461
463
  });
462
464
 
@@ -102,8 +102,9 @@ export async function saveQueryLocalCacheQuery<
102
102
  sql: sql.toSQL().sql.toLowerCase(),
103
103
  data: rows,
104
104
  };
105
- if (options.logRawSqlQuery) {
105
+ if (options.logCache) {
106
106
  const q = sql.toSQL();
107
+ // eslint-disable-next-line no-console
107
108
  console.debug(
108
109
  `[forge-sql-orm][local-cache][SAVE] Stored result in cache. sql="${q.sql}", params=${JSON.stringify(q.params)}`,
109
110
  );
@@ -140,8 +141,9 @@ export async function getQueryLocalCacheQuery<
140
141
  const sql = query as { toSQL: () => Query };
141
142
  const key = hashKey(sql.toSQL());
142
143
  if (context.cache[key] && context.cache[key].sql === sql.toSQL().sql.toLowerCase()) {
143
- if (options.logRawSqlQuery) {
144
+ if (options.logCache) {
144
145
  const q = sql.toSQL();
146
+ // eslint-disable-next-line no-console
145
147
  console.debug(
146
148
  `[forge-sql-orm][local-cache][HIT] Returned cached result. sql="${q.sql}", params=${JSON.stringify(q.params)}`,
147
149
  );
@@ -123,7 +123,8 @@ async function clearCursorCache(
123
123
 
124
124
  const listResult = await entityQueryBuilder.limit(100).getMany();
125
125
 
126
- if (options.logRawSqlQuery) {
126
+ if (options.logCache) {
127
+ // eslint-disable-next-line no-console
127
128
  console.warn(`clear cache Records: ${JSON.stringify(listResult.results.map((r) => r.key))}`);
128
129
  }
129
130
 
@@ -170,7 +171,8 @@ async function clearExpirationCursorCache(
170
171
 
171
172
  const listResult = await entityQueryBuilder.limit(100).getMany();
172
173
 
173
- if (options.logRawSqlQuery) {
174
+ if (options.logCache) {
175
+ // eslint-disable-next-line no-console
174
176
  console.warn(`clear expired Records: ${JSON.stringify(listResult.results.map((r) => r.key))}`);
175
177
  }
176
178
 
@@ -201,10 +203,12 @@ async function executeWithRetry<T>(operation: () => Promise<T>, operationName: s
201
203
  try {
202
204
  return await operation();
203
205
  } catch (err: any) {
206
+ // eslint-disable-next-line no-console
204
207
  console.warn(`Error during ${operationName}: ${err.message}, retry ${attempt}`, err);
205
208
  attempt++;
206
209
 
207
210
  if (attempt >= CACHE_CONSTANTS.MAX_RETRY_ATTEMPTS) {
211
+ // eslint-disable-next-line no-console
208
212
  console.error(`Error during ${operationName}: ${err.message}`, err);
209
213
  throw err;
210
214
  }
@@ -260,8 +264,9 @@ export async function clearTablesCache(
260
264
  "clearing cache",
261
265
  );
262
266
  } finally {
263
- if (options.logRawSqlQuery) {
267
+ if (options.logCache) {
264
268
  const duration = DateTime.now().toSeconds() - startTime.toSeconds();
269
+ // eslint-disable-next-line no-console
265
270
  console.info(`Cleared ${totalRecords} cache records in ${duration} seconds`);
266
271
  }
267
272
  }
@@ -287,7 +292,8 @@ export async function clearExpiredCache(options: ForgeSqlOrmOptions): Promise<vo
287
292
  );
288
293
  } finally {
289
294
  const duration = DateTime.now().toSeconds() - startTime.toSeconds();
290
- if (options?.logRawSqlQuery) {
295
+ if (options?.logCache) {
296
+ // eslint-disable-next-line no-console
291
297
  console.debug(`Cleared ${totalRecords} expired cache records in ${duration} seconds`);
292
298
  }
293
299
  }
@@ -318,7 +324,8 @@ export async function getFromCache<T>(
318
324
 
319
325
  // Skip cache if table is in cache context (will be cleared)
320
326
  if (await isTableContainsTableInCacheContext(sqlQuery.sql, options)) {
321
- if (options.logRawSqlQuery) {
327
+ if (options.logCache) {
328
+ // eslint-disable-next-line no-console
322
329
  console.warn(`Context contains value to clear. Skip getting from cache`);
323
330
  }
324
331
  return undefined;
@@ -332,13 +339,15 @@ export async function getFromCache<T>(
332
339
  (cacheResult[expirationName] as number) >= getCurrentTime() &&
333
340
  sqlQuery.sql.toLowerCase() === cacheResult[entityQueryName]
334
341
  ) {
335
- if (options.logRawSqlQuery) {
342
+ if (options.logCache) {
343
+ // eslint-disable-next-line no-console
336
344
  console.warn(`Get value from cache, cacheKey: ${key}`);
337
345
  }
338
346
  const results = cacheResult[dataName];
339
347
  return JSON.parse(results as string);
340
348
  }
341
349
  } catch (error: any) {
350
+ // eslint-disable-next-line no-console
342
351
  console.error(`Error getting from cache: ${error.message}`, error);
343
352
  }
344
353
 
@@ -375,7 +384,8 @@ export async function setCacheResult(
375
384
 
376
385
  // Skip cache if table is in cache context (will be cleared)
377
386
  if (await isTableContainsTableInCacheContext(sqlQuery.sql, options)) {
378
- if (options.logRawSqlQuery) {
387
+ if (options.logCache) {
388
+ // eslint-disable-next-line no-console
379
389
  console.warn(`Context contains value to clear. Skip setting from cache`);
380
390
  }
381
391
  return;
@@ -396,10 +406,12 @@ export async function setCacheResult(
396
406
  )
397
407
  .execute();
398
408
 
399
- if (options.logRawSqlQuery) {
409
+ if (options.logCache) {
410
+ // eslint-disable-next-line no-console
400
411
  console.warn(`Store value to cache, cacheKey: ${key}`);
401
412
  }
402
413
  } catch (error: any) {
414
+ // eslint-disable-next-line no-console
403
415
  console.error(`Error setting cache: ${error.message}`, error);
404
416
  }
405
417
  }
@@ -1,7 +1,27 @@
1
1
  import { sql, UpdateQueryResponse } from "@forge/sql";
2
+ import {saveMetaDataInContextContext} from "./metadataContextUtils";
2
3
 
3
- interface ForgeSQLResult {
4
+ export type ForgeSQLMetadata = {
5
+ dbExecutionTime: number,
6
+ responseSize: number,
7
+ fields: {
8
+ "catalog": string,
9
+ "name": string,
10
+ "schema": string,
11
+ "characterSet": number,
12
+ "decimals": number,
13
+ "table": string,
14
+ "orgTable": string,
15
+ "orgName": string,
16
+ "flags": number,
17
+ "columnType": number,
18
+ "columnLength": number
19
+ }[]
20
+ };
21
+
22
+ export interface ForgeSQLResult {
4
23
  rows: Record<string, unknown>[] | Record<string, unknown>;
24
+ metadata: ForgeSQLMetadata
5
25
  }
6
26
 
7
27
  export const forgeDriver = async (
@@ -27,6 +47,7 @@ export const forgeDriver = async (
27
47
  await sqlStatement.bindParams(...params);
28
48
  }
29
49
  const result = (await sqlStatement.execute()) as ForgeSQLResult;
50
+ await saveMetaDataInContextContext(result.metadata)
30
51
  let rows;
31
52
  rows = (result.rows as any[]).map((r) => Object.values(r as Record<string, unknown>));
32
53
  return { rows: rows };
@@ -19,6 +19,7 @@ export function createForgeDriverProxy(options?: SqlHints, logRawSqlQuery?: bool
19
19
  const modifiedQuery = injectSqlHints(query, options);
20
20
 
21
21
  if (options && logRawSqlQuery && modifiedQuery !== query) {
22
+ // eslint-disable-next-line no-console
22
23
  console.debug("injected Hints: " + modifiedQuery);
23
24
  }
24
25
  try {
@@ -26,6 +27,7 @@ export function createForgeDriverProxy(options?: SqlHints, logRawSqlQuery?: bool
26
27
  return await forgeDriver(modifiedQuery, params, method);
27
28
  } catch (error) {
28
29
  if (logRawSqlQuery) {
30
+ // eslint-disable-next-line no-console
29
31
  console.debug("SQL Error:", JSON.stringify(error));
30
32
  }
31
33
  throw error;
@@ -0,0 +1,24 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import {ForgeSQLMetadata} from "./forgeDriver";
3
+
4
+ export type MetadataQueryContext = {
5
+ totalDbExecutionTime: number,
6
+ totalResponseSize: number,
7
+ lastMetadata?: ForgeSQLMetadata;
8
+ }
9
+ export const metadataQueryContext = new AsyncLocalStorage<MetadataQueryContext>();
10
+
11
+ export async function saveMetaDataInContextContext(
12
+ metadata: ForgeSQLMetadata,
13
+ ): Promise<void> {
14
+ const context = metadataQueryContext.getStore();
15
+ if (context && metadata) {
16
+ context.totalResponseSize += metadata.responseSize
17
+ context.totalDbExecutionTime += metadata.dbExecutionTime
18
+ context.lastMetadata = metadata;
19
+ }
20
+ }
21
+
22
+ export async function getLastestMetadata():Promise<MetadataQueryContext|undefined> {
23
+ return metadataQueryContext.getStore();
24
+ }
@@ -329,6 +329,7 @@ export function generateDropTableStatements(
329
329
  const dropStatements: string[] = [];
330
330
  const validOptions = options ?? { sequence: true, table: true };
331
331
  if (!validOptions.sequence && !validOptions.table) {
332
+ // eslint-disable-next-line no-console
332
333
  console.warn('No drop operations requested: both "table" and "sequence" options are false');
333
334
  return [];
334
335
  }
@@ -31,12 +31,14 @@ export const applySchemaMigrations = async (
31
31
  if (typeof migration !== "function") {
32
32
  throw new Error("migration is not a function");
33
33
  }
34
-
34
+ // eslint-disable-next-line no-console
35
35
  console.log("Provisioning the database");
36
36
  await sql._provision();
37
+ // eslint-disable-next-line no-console
37
38
  console.info("Running schema migrations");
38
39
  const migrations = await migration(migrationRunner);
39
40
  const successfulMigrations = await migrations.run();
41
+ // eslint-disable-next-line no-console
40
42
  console.info("Migrations applied:", successfulMigrations);
41
43
 
42
44
  const migrationList = await migrationRunner.list();
@@ -51,7 +53,7 @@ export const applySchemaMigrations = async (
51
53
  .map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`)
52
54
  .join("\n");
53
55
  }
54
-
56
+ // eslint-disable-next-line no-console
55
57
  console.info("Migrations history:\nid, name, migrated_at\n", migrationHistory);
56
58
 
57
59
  return {
@@ -68,6 +70,7 @@ export const applySchemaMigrations = async (
68
70
  error?.debug?.context?.message ??
69
71
  error.message ??
70
72
  "Unknown error occurred";
73
+ // eslint-disable-next-line no-console
71
74
  console.error("Error during migration:", errorMessage);
72
75
  return {
73
76
  headers: { "Content-Type": ["application/json"] },
@@ -64,6 +64,7 @@ export const clearCacheSchedulerTrigger = async (options?: ForgeSqlOrmOptions) =
64
64
  }),
65
65
  };
66
66
  } catch (error) {
67
+ // eslint-disable-next-line no-console
67
68
  console.error("Error during cache cleanup: ", JSON.stringify(error));
68
69
  return {
69
70
  headers: { "Content-Type": ["application/json"] },
@@ -34,6 +34,7 @@ export async function dropSchemaMigrations(): Promise<TriggerResponse<string>> {
34
34
 
35
35
  // Execute each statement
36
36
  for (const statement of dropStatements) {
37
+ // eslint-disable-next-line no-console
37
38
  console.debug(`execute DDL: ${statement}`);
38
39
  await sql.executeDDL(statement);
39
40
  }
@@ -48,6 +49,7 @@ export async function dropSchemaMigrations(): Promise<TriggerResponse<string>> {
48
49
  error?.debug?.message ??
49
50
  error.message ??
50
51
  "Unknown error occurred";
52
+ // eslint-disable-next-line no-console
51
53
  console.error(errorMessage);
52
54
  return getHttpResponse<string>(500, errorMessage);
53
55
  }
@@ -34,6 +34,7 @@ export async function dropTableSchemaMigrations(): Promise<TriggerResponse<strin
34
34
 
35
35
  // Execute each statement
36
36
  for (const statement of dropStatements) {
37
+ // eslint-disable-next-line no-console
37
38
  console.debug(`execute DDL: ${statement}`);
38
39
  await sql.executeDDL(statement);
39
40
  }
@@ -48,6 +49,7 @@ export async function dropTableSchemaMigrations(): Promise<TriggerResponse<strin
48
49
  error?.debug?.message ??
49
50
  error.message ??
50
51
  "Unknown error occurred";
52
+ // eslint-disable-next-line no-console
51
53
  console.error(errorMessage);
52
54
  return getHttpResponse<string>(500, errorMessage);
53
55
  }
@@ -46,6 +46,7 @@ export async function fetchSchemaWebTrigger(): Promise<TriggerResponse<string>>
46
46
  error?.debug?.message ??
47
47
  error.message ??
48
48
  "Unknown error occurred";
49
+ // eslint-disable-next-line no-console
49
50
  console.error(errorMessage);
50
51
  return getHttpResponse<string>(500, errorMessage);
51
52
  }