forge-sql-orm
Version:
Drizzle ORM integration for Atlassian @forge/sql. Provides a custom driver, schema migration, two levels of caching (local and global via @forge/kvs), optimistic locking, and query analysis.
198 lines • 7.39 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.localCacheApplicationContext = exports.cacheApplicationContext = void 0;
exports.saveTableIfInsideCacheContext = saveTableIfInsideCacheContext;
exports.saveQueryLocalCacheQuery = saveQueryLocalCacheQuery;
exports.getQueryLocalCacheQuery = getQueryLocalCacheQuery;
exports.evictLocalCacheQuery = evictLocalCacheQuery;
exports.isTableContainsTableInCacheContext = isTableContainsTableInCacheContext;
const node_async_hooks_1 = require("node:async_hooks");
const table_1 = require("drizzle-orm/table");
const cacheUtils_1 = require("./cacheUtils");
function isQuery(obj) {
return (typeof obj === "object" &&
obj !== null &&
typeof obj.sql === "string" &&
Array.isArray(obj.params));
}
/**
* AsyncLocalStorage instance for managing cache context across async operations.
* This allows tracking which tables are being processed within a cache context
* without explicitly passing context through function parameters.
*/
exports.cacheApplicationContext = new node_async_hooks_1.AsyncLocalStorage();
/**
* AsyncLocalStorage instance for managing local cache context across async operations.
* This allows storing and retrieving cached query results within a local cache context
* without explicitly passing context through function parameters.
*/
exports.localCacheApplicationContext = new node_async_hooks_1.AsyncLocalStorage();
/**
* Saves a table name to the current cache context if one exists.
* This function is used to track which tables are being processed within
* a cache context for proper cache invalidation.
*
* @param table - The Drizzle table schema to track
* @returns Promise that resolves when the table is saved to context
*
* @example
* ```typescript
* await saveTableIfInsideCacheContext(usersTable);
* ```
*/
async function saveTableIfInsideCacheContext(table) {
const context = exports.cacheApplicationContext.getStore();
if (context) {
const tableName = (0, table_1.getTableName)(table).toLowerCase();
context.tables.add(tableName);
}
}
/**
* Saves a query result to the local cache context.
* This function stores query results in memory for the duration of the local cache context.
*
* @param query - The Drizzle query to cache
* @param rows - The query result data to cache
* @param options - ForgeSqlOrm options
* @returns Promise that resolves when the data is saved to local cache
*
* @example
* ```typescript
* const query = db.select({ id: users.id, name: users.name }).from(users);
* const results = await query.execute();
* await saveQueryLocalCacheQuery(query, results);
* ```
*/
async function saveQueryLocalCacheQuery(query, rows, options) {
const context = exports.localCacheApplicationContext.getStore();
if (context) {
if (!context.cache) {
context.cache = {};
}
let sql;
if (isQuery(query)) {
sql = { toSQL: () => query };
}
else {
sql = query;
}
const key = (0, cacheUtils_1.hashKey)(sql.toSQL());
context.cache[key] = {
sql: sql.toSQL().sql.toLowerCase(),
data: rows,
};
if (options.logCache) {
const q = sql.toSQL();
// eslint-disable-next-line no-console
console.debug(`[forge-sql-orm][local-cache][SAVE] Stored result in cache. sql="${q.sql}", params=${JSON.stringify(q.params)}`);
}
}
}
/**
* Retrieves a query result from the local cache context.
* This function checks if a query result is already cached in memory.
*
* @param query - The Drizzle query to check for cached results
* @param options - Option Property
* @returns Promise that resolves to cached data if found, undefined otherwise
*
* @example
* ```typescript
* const query = db.select({ id: users.id, name: users.name }).from(users);
* const cachedResult = await getQueryLocalCacheQuery(query);
* if (cachedResult) {
* return cachedResult; // Use cached data
* }
* // Execute query and cache result
* ```
*/
async function getQueryLocalCacheQuery(query, options) {
const context = exports.localCacheApplicationContext.getStore();
if (context) {
if (!context.cache) {
context.cache = {};
}
let sql;
if (isQuery(query)) {
sql = { toSQL: () => query };
}
else {
sql = query;
}
const toSQL = sql.toSQL();
const key = (0, cacheUtils_1.hashKey)(toSQL);
if (context?.cache[key]?.sql === toSQL.sql.toLowerCase()) {
if (options.logCache) {
const q = toSQL;
// eslint-disable-next-line no-console
console.debug(`[forge-sql-orm][local-cache][HIT] Returned cached result. sql="${q.sql}", params=${JSON.stringify(q.params)}`);
}
return context.cache[key].data;
}
}
return undefined;
}
/**
* Evicts cached queries from the local cache context that involve the specified table.
* This function removes cached query results that contain the given table name.
*
* @param table - The Drizzle table schema to evict cache for
* @param options - ForgeSQL ORM options containing cache configuration
* @returns Promise that resolves when cache eviction is complete
*
* @example
* ```typescript
* // After inserting/updating/deleting from users table
* await evictLocalCacheQuery(usersTable, forgeSqlOptions);
* // All cached queries involving users table are now removed
* ```
*/
async function evictLocalCacheQuery(table, options) {
const context = exports.localCacheApplicationContext.getStore();
if (context) {
if (!context.cache) {
context.cache = {};
}
const tableName = (0, table_1.getTableName)(table);
const searchString = options.cacheWrapTable ? `\`${tableName}\`` : tableName;
const keyToEvicts = [];
for (const key of Object.keys(context.cache)) {
if (context.cache[key].sql.includes(searchString)) {
keyToEvicts.push(key);
}
}
for (const key of keyToEvicts) {
delete context.cache[key];
}
}
}
/**
* Checks if the given SQL query contains any tables that are currently being processed
* within a cache context. This is used to determine if cache should be bypassed
* for queries that affect tables being modified within the context.
*
* @param sql - The SQL query string to check
* @param options - ForgeSQL ORM options containing cache configuration
* @returns Promise that resolves to true if the SQL contains tables in cache context
*
* @example
* ```typescript
* const shouldBypassCache = await isTableContainsTableInCacheContext(
* "SELECT * FROM users WHERE id = 1",
* forgeSqlOptions
* );
* ```
*/
async function isTableContainsTableInCacheContext(sql, options) {
const context = exports.cacheApplicationContext.getStore();
if (!context) {
return false;
}
const tables = Array.from(context.tables);
const lowerSql = sql.toLowerCase();
return tables.some((table) => {
const tablePattern = options.cacheWrapTable ? `\`${table}\`` : table;
return lowerSql.includes(tablePattern);
});
}
//# sourceMappingURL=cacheContextUtils.js.map