• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vzakharchenko / forge-sql-orm / 17917370279

22 Sep 2025 01:46PM UTC coverage: 86.669% (-0.4%) from 87.078%
17917370279

push

github

vzakharchenko
added Query Execution with Metadata

479 of 586 branches covered (81.74%)

Branch coverage included in aggregate %.

79 of 96 new or added lines in 6 files covered. (82.29%)

12 existing lines in 3 files now uncovered.

2479 of 2827 relevant lines covered (87.69%)

13.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.03
/src/utils/cacheContextUtils.ts
1
import { AsyncLocalStorage } from "node:async_hooks";
1✔
2
import { AnyMySqlSelectQueryBuilder, AnyMySqlTable } from "drizzle-orm/mysql-core";
3
import { getTableName } from "drizzle-orm/table";
1✔
4
import { ForgeSqlOrmOptions } from "../core/ForgeSQLQueryBuilder";
5
import { MySqlSelectDynamic } from "drizzle-orm/mysql-core/query-builders/select.types";
6
import { hashKey } from "./cacheUtils";
1✔
7
import { Query } from "drizzle-orm/sql/sql";
8

9
/**
10
 * Interface representing the cache application context.
11
 * Stores information about tables that are being processed within a cache context.
12
 */
13
export interface CacheApplicationContext {
14
  /** Set of table names (in lowercase) that are being processed within the cache context */
15
  tables: Set<string>;
16
}
17

18
/**
19
 * Interface representing the local cache application context.
20
 * Stores cached query results in memory for the duration of a local cache context.
21
 *
22
 * @interface LocalCacheApplicationContext
23
 */
24
export interface LocalCacheApplicationContext {
25
  /**
26
   * Cache object mapping query hash keys to cached results
27
   * @property {Record<string, {sql: string, data: unknown[]}>} cache - Map of query keys to cached data
28
   */
29
  cache: Record<
30
    string,
31
    {
32
      sql: string;
33
      data: unknown[];
34
    }
35
  >;
36
}
37

38
/**
39
 * AsyncLocalStorage instance for managing cache context across async operations.
40
 * This allows tracking which tables are being processed within a cache context
41
 * without explicitly passing context through function parameters.
42
 */
43
export const cacheApplicationContext = new AsyncLocalStorage<CacheApplicationContext>();
1✔
44

45
/**
46
 * AsyncLocalStorage instance for managing local cache context across async operations.
47
 * This allows storing and retrieving cached query results within a local cache context
48
 * without explicitly passing context through function parameters.
49
 */
50
export const localCacheApplicationContext = new AsyncLocalStorage<LocalCacheApplicationContext>();
1✔
51

52
/**
53
 * Saves a table name to the current cache context if one exists.
54
 * This function is used to track which tables are being processed within
55
 * a cache context for proper cache invalidation.
56
 *
57
 * @param table - The Drizzle table schema to track
58
 * @returns Promise that resolves when the table is saved to context
59
 *
60
 * @example
61
 * ```typescript
62
 * await saveTableIfInsideCacheContext(usersTable);
63
 * ```
64
 */
65
export async function saveTableIfInsideCacheContext<T extends AnyMySqlTable>(
61✔
66
  table: T,
61✔
67
): Promise<void> {
61✔
68
  const context = cacheApplicationContext.getStore();
61✔
69
  if (context) {
61✔
70
    const tableName = getTableName(table).toLowerCase();
10✔
71
    context.tables.add(tableName);
10✔
72
  }
10✔
73
}
61✔
74

75
/**
76
 * Saves a query result to the local cache context.
77
 * This function stores query results in memory for the duration of the local cache context.
78
 *
79
 * @param query - The Drizzle query to cache
80
 * @param rows - The query result data to cache
81
 * @param options - ForgeSqlOrm options
82
 * @returns Promise that resolves when the data is saved to local cache
83
 *
84
 * @example
85
 * ```typescript
86
 * const query = db.select({ id: users.id, name: users.name }).from(users);
87
 * const results = await query.execute();
88
 * await saveQueryLocalCacheQuery(query, results);
89
 * ```
90
 */
91
export async function saveQueryLocalCacheQuery<
20✔
92
  T extends MySqlSelectDynamic<AnyMySqlSelectQueryBuilder>,
93
>(query: T, rows: unknown[], options: ForgeSqlOrmOptions): Promise<void> {
20✔
94
  const context = localCacheApplicationContext.getStore();
20✔
95
  if (context) {
20✔
96
    if (!context.cache) {
4!
97
      context.cache = {};
×
98
    }
×
99
    const sql = query as { toSQL: () => Query };
4✔
100
    const key = hashKey(sql.toSQL());
4✔
101
    context.cache[key] = {
4✔
102
      sql: sql.toSQL().sql.toLowerCase(),
4✔
103
      data: rows,
4✔
104
    };
4✔
105
    if (options.logCache) {
4!
UNCOV
106
      const q = sql.toSQL();
×
107
      // eslint-disable-next-line no-console
UNCOV
108
      console.debug(
×
UNCOV
109
        `[forge-sql-orm][local-cache][SAVE] Stored result in cache. sql="${q.sql}", params=${JSON.stringify(q.params)}`,
×
UNCOV
110
      );
×
UNCOV
111
    }
×
112
  }
4✔
113
}
20✔
114

115
/**
116
 * Retrieves a query result from the local cache context.
117
 * This function checks if a query result is already cached in memory.
118
 *
119
 * @param query - The Drizzle query to check for cached results
120
 * @param options - Option Property
121
 * @returns Promise that resolves to cached data if found, undefined otherwise
122
 *
123
 * @example
124
 * ```typescript
125
 * const query = db.select({ id: users.id, name: users.name }).from(users);
126
 * const cachedResult = await getQueryLocalCacheQuery(query);
127
 * if (cachedResult) {
128
 *   return cachedResult; // Use cached data
129
 * }
130
 * // Execute query and cache result
131
 * ```
132
 */
133
export async function getQueryLocalCacheQuery<
27✔
134
  T extends MySqlSelectDynamic<AnyMySqlSelectQueryBuilder>,
135
>(query: T, options: ForgeSqlOrmOptions): Promise<unknown[] | undefined> {
27✔
136
  const context = localCacheApplicationContext.getStore();
27✔
137
  if (context) {
27✔
138
    if (!context.cache) {
11!
139
      context.cache = {};
×
140
    }
×
141
    const sql = query as { toSQL: () => Query };
11✔
142
    const key = hashKey(sql.toSQL());
11✔
143
    if (context.cache[key] && context.cache[key].sql === sql.toSQL().sql.toLowerCase()) {
11✔
144
      if (options.logCache) {
7!
UNCOV
145
        const q = sql.toSQL();
×
146
        // eslint-disable-next-line no-console
UNCOV
147
        console.debug(
×
UNCOV
148
          `[forge-sql-orm][local-cache][HIT] Returned cached result. sql="${q.sql}", params=${JSON.stringify(q.params)}`,
×
UNCOV
149
        );
×
UNCOV
150
      }
×
151
      return context.cache[key].data;
7✔
152
    }
7✔
153
  }
11✔
154
  return undefined;
20✔
155
}
20✔
156

157
/**
158
 * Evicts cached queries from the local cache context that involve the specified table.
159
 * This function removes cached query results that contain the given table name.
160
 *
161
 * @param table - The Drizzle table schema to evict cache for
162
 * @param options - ForgeSQL ORM options containing cache configuration
163
 * @returns Promise that resolves when cache eviction is complete
164
 *
165
 * @example
166
 * ```typescript
167
 * // After inserting/updating/deleting from users table
168
 * await evictLocalCacheQuery(usersTable, forgeSqlOptions);
169
 * // All cached queries involving users table are now removed
170
 * ```
171
 */
172
export async function evictLocalCacheQuery<T extends AnyMySqlTable>(
35✔
173
  table: T,
35✔
174
  options: ForgeSqlOrmOptions,
35✔
175
): Promise<void> {
35✔
176
  const context = localCacheApplicationContext.getStore();
35✔
177
  if (context) {
35✔
178
    if (!context.cache) {
7!
179
      context.cache = {};
×
180
    }
×
181
    const tableName = getTableName(table);
7✔
182
    const searchString = options.cacheWrapTable ? `\`${tableName}\`` : tableName;
7!
183
    const keyToEvicts: string[] = [];
7✔
184
    Object.keys(context.cache).forEach((key) => {
7✔
185
      if (context.cache[key].sql.includes(searchString)) {
1✔
186
        keyToEvicts.push(key);
1✔
187
      }
1✔
188
    });
7✔
189
    keyToEvicts.forEach((key) => delete context.cache[key]);
7✔
190
  }
7✔
191
}
35✔
192

193
/**
194
 * Checks if the given SQL query contains any tables that are currently being processed
195
 * within a cache context. This is used to determine if cache should be bypassed
196
 * for queries that affect tables being modified within the context.
197
 *
198
 * @param sql - The SQL query string to check
199
 * @param options - ForgeSQL ORM options containing cache configuration
200
 * @returns Promise that resolves to true if the SQL contains tables in cache context
201
 *
202
 * @example
203
 * ```typescript
204
 * const shouldBypassCache = await isTableContainsTableInCacheContext(
205
 *   "SELECT * FROM users WHERE id = 1",
206
 *   forgeSqlOptions
207
 * );
208
 * ```
209
 */
210
export async function isTableContainsTableInCacheContext(
10✔
211
  sql: string,
10✔
212
  options: ForgeSqlOrmOptions,
10✔
213
): Promise<boolean> {
10✔
214
  const context = cacheApplicationContext.getStore();
10✔
215
  if (!context) {
10✔
216
    return false;
1✔
217
  }
1✔
218

219
  const tables = Array.from(context.tables);
9✔
220
  const lowerSql = sql.toLowerCase();
9✔
221

222
  return tables.some((table) => {
9✔
223
    const tablePattern = options.cacheWrapTable ? `\`${table}\`` : table;
10✔
224
    return lowerSql.includes(tablePattern);
10✔
225
  });
9✔
226
}
9✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc