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

vzakharchenko / forge-sql-orm / 17700587539

13 Sep 2025 06:40PM UTC coverage: 81.149% (+0.4%) from 80.749%
17700587539

push

github

vzakharchenko
added local cache, updated documentation

390 of 475 branches covered (82.11%)

Branch coverage included in aggregate %.

85 of 94 new or added lines in 4 files covered. (90.43%)

1715 of 2119 relevant lines covered (80.93%)

14.08 hits per line

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

90.2
/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
 * @returns Promise that resolves when the data is saved to local cache
82
 * 
83
 * @example
84
 * ```typescript
85
 * const query = db.select({ id: users.id, name: users.name }).from(users);
86
 * const results = await query.execute();
87
 * await saveQueryLocalCacheQuery(query, results);
88
 * ```
89
 */
90
export async function saveQueryLocalCacheQuery<
13✔
91
  T extends MySqlSelectDynamic<AnyMySqlSelectQueryBuilder>,
92
>(query: T, rows: unknown[]): Promise<void> {
13✔
93
  const context = localCacheApplicationContext.getStore();
13✔
94
  if (context) {
13✔
95
    if (!context.cache) {
4!
NEW
96
      context.cache = {};
×
NEW
97
    }
×
98
    const sql = query as { toSQL: () => Query };
4✔
99
    const key = hashKey(sql.toSQL());
4✔
100
    context.cache[key] = {
4✔
101
      sql: sql.toSQL().sql.toLowerCase(),
4✔
102
      data: rows,
4✔
103
    };
4✔
104
  }
4✔
105
}
13✔
106

107
/**
108
 * Retrieves a query result from the local cache context.
109
 * This function checks if a query result is already cached in memory.
110
 * 
111
 * @param query - The Drizzle query to check for cached results
112
 * @returns Promise that resolves to cached data if found, undefined otherwise
113
 * 
114
 * @example
115
 * ```typescript
116
 * const query = db.select({ id: users.id, name: users.name }).from(users);
117
 * const cachedResult = await getQueryLocalCacheQuery(query);
118
 * if (cachedResult) {
119
 *   return cachedResult; // Use cached data
120
 * }
121
 * // Execute query and cache result
122
 * ```
123
 */
124
export async function getQueryLocalCacheQuery<
20✔
125
  T extends MySqlSelectDynamic<AnyMySqlSelectQueryBuilder>,
126
>(query: T): Promise<unknown[] | undefined> {
20✔
127
  const context = localCacheApplicationContext.getStore();
20✔
128
  if (context) {
20✔
129
    if (!context.cache) {
11!
NEW
130
      context.cache = {};
×
NEW
131
    }
×
132
    const sql = query as { toSQL: () => Query };
11✔
133
    const key = hashKey(sql.toSQL());
11✔
134
    if (context.cache[key] && context.cache[key].sql === sql.toSQL().sql.toLowerCase()) {
11✔
135
      return context.cache[key].data;
7✔
136
    }
7✔
137
  }
11✔
138
  return undefined;
13✔
139
}
13✔
140

141
/**
142
 * Evicts cached queries from the local cache context that involve the specified table.
143
 * This function removes cached query results that contain the given table name.
144
 * 
145
 * @param table - The Drizzle table schema to evict cache for
146
 * @param options - ForgeSQL ORM options containing cache configuration
147
 * @returns Promise that resolves when cache eviction is complete
148
 * 
149
 * @example
150
 * ```typescript
151
 * // After inserting/updating/deleting from users table
152
 * await evictLocalCacheQuery(usersTable, forgeSqlOptions);
153
 * // All cached queries involving users table are now removed
154
 * ```
155
 */
156
export async function evictLocalCacheQuery<T extends AnyMySqlTable>(
35✔
157
  table: T,
35✔
158
  options: ForgeSqlOrmOptions,
35✔
159
): Promise<void> {
35✔
160
  const context = localCacheApplicationContext.getStore();
35✔
161
  if (context) {
35✔
162
    if (!context.cache) {
7!
NEW
163
      context.cache = {};
×
NEW
164
    }
×
165
    const tableName = getTableName(table);
7✔
166
    const searchString = options.cacheWrapTable ? `\`${tableName}\`` : tableName;
7!
167
    const keyToEvicts: string[] = [];
7✔
168
    Object.keys(context.cache).forEach((key) => {
7✔
169
      if (context.cache[key].sql.includes(searchString)) {
1✔
170
        keyToEvicts.push(key);
1✔
171
      }
1✔
172
    });
7✔
173
    keyToEvicts.forEach((key) => delete context.cache[key]);
7✔
174
  }
7✔
175
}
35✔
176

177
/**
178
 * Checks if the given SQL query contains any tables that are currently being processed
179
 * within a cache context. This is used to determine if cache should be bypassed
180
 * for queries that affect tables being modified within the context.
181
 *
182
 * @param sql - The SQL query string to check
183
 * @param options - ForgeSQL ORM options containing cache configuration
184
 * @returns Promise that resolves to true if the SQL contains tables in cache context
185
 *
186
 * @example
187
 * ```typescript
188
 * const shouldBypassCache = await isTableContainsTableInCacheContext(
189
 *   "SELECT * FROM users WHERE id = 1",
190
 *   forgeSqlOptions
191
 * );
192
 * ```
193
 */
194
export async function isTableContainsTableInCacheContext(
10✔
195
  sql: string,
10✔
196
  options: ForgeSqlOrmOptions,
10✔
197
): Promise<boolean> {
10✔
198
  const context = cacheApplicationContext.getStore();
10✔
199
  if (!context) {
10✔
200
    return false;
1✔
201
  }
1✔
202

203
  const tables = Array.from(context.tables);
9✔
204
  const lowerSql = sql.toLowerCase();
9✔
205

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

© 2026 Coveralls, Inc