diff --git a/lib/RateLimiterDrizzle.js b/lib/RateLimiterDrizzle.js index 80e6422..ea0e384 100644 --- a/lib/RateLimiterDrizzle.js +++ b/lib/RateLimiterDrizzle.js @@ -9,23 +9,76 @@ class RateLimiterDrizzleError extends Error { } } -async function getDrizzleOperators() { +async function loadDrizzleOperators() { if (drizzleOperators) return drizzleOperators; - try { - // Use dynamic import to prevent static analysis tools from detecting the import - function getPackageName() { - return ['drizzle', 'orm'].join('-'); + const requiredOperators = ['and', 'or', 'gt', 'lt', 'eq', 'isNull', 'sql']; + + // Use dynamic import to prevent static analysis tools from detecting the import + function getPackageName() { + return ['drizzle', 'orm'].join('-'); + } + + const collectModuleLayers = (mod) => { + const layers = []; + const seen = new Set(); + let current = mod; + + while (current && (typeof current === 'object' || typeof current === 'function') && !seen.has(current)) { + layers.push(current); + seen.add(current); + current = current.default; + } + + return layers; + }; + + const importModule = async (specifier, { optional = false } = {}) => { + try { + return await import(/* @vite-ignore */ specifier); + } catch (error) { + if (optional) { + return null; + } + throw new RateLimiterDrizzleError( + error.message || 'Failed to import drizzle-orm package.' + ); + } + }; + + const mainModule = await importModule(`${getPackageName()}`); + const sqlModule = await importModule(`${getPackageName()}/sql`, { optional: true }); + + const mainCandidates = collectModuleLayers(mainModule); + const sqlCandidates = sqlModule ? collectModuleLayers(sqlModule) : []; + const resolvedOperators = {}; + const missingOperators = []; + + for (const operatorName of requiredOperators) { + const candidates = operatorName === 'sql' + ? [...sqlCandidates, ...mainCandidates] + : mainCandidates; + + const operator = candidates + .map((candidate) => candidate?.[operatorName]) + .find((value) => typeof value === 'function'); + + if (!operator) { + missingOperators.push(operatorName); + continue; } - const drizzleOrm = await import(`${getPackageName()}`); - const { and, or, gt, lt, eq, isNull, sql } = drizzleOrm.default || drizzleOrm; - drizzleOperators = { and, or, gt, lt, eq, isNull, sql }; - return drizzleOperators; - } catch (error) { + + resolvedOperators[operatorName] = operator; + } + + if (missingOperators.length > 0) { throw new RateLimiterDrizzleError( - 'drizzle-orm is not installed. Please install drizzle-orm to use RateLimiterDrizzle.' + `Failed to load drizzle-orm operators: missing ${missingOperators.join(', ')}` ); } + + drizzleOperators = resolvedOperators; + return drizzleOperators; } const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract'); @@ -71,7 +124,7 @@ class RateLimiterDrizzle extends RateLimiterStoreAbstract { return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established')) } - const { eq, sql } = await getDrizzleOperators(); + const { eq, sql } = await loadDrizzleOperators(); const now = new Date(); const newExpire = msDuration > 0 ? new Date(now.getTime() + msDuration) : null; @@ -117,7 +170,7 @@ class RateLimiterDrizzle extends RateLimiterStoreAbstract { return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established')) } - const { and, or, gt, eq, isNull } = await getDrizzleOperators(); + const { and, or, gt, eq, isNull } = await loadDrizzleOperators(); const [response] = await this.drizzleClient .select() @@ -139,7 +192,7 @@ class RateLimiterDrizzle extends RateLimiterStoreAbstract { return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established')) } - const { eq } = await getDrizzleOperators(); + const { eq } = await loadDrizzleOperators(); const [result] = await this.drizzleClient .delete(this.schema) @@ -156,7 +209,7 @@ class RateLimiterDrizzle extends RateLimiterStoreAbstract { this._clearExpiredTimeoutId = setTimeout(async () => { try { - const { lt } = await getDrizzleOperators(); + const { lt } = await loadDrizzleOperators(); await this.drizzleClient .delete(this.schema) .where(lt(this.schema.expire, new Date(Date.now() - EXPIRED_THRESHOLD_MS))); diff --git a/package.json b/package.json index dbd4c05..1d9cdf9 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "test:valkey-cluster": "VALKEY_CLUSTER_PORT=7001 mocha test/RateLimiterValkeyGlide.test.js -- -g 'RateLimiterValkeyGlide with cluster client'", "prisma:postgres": "prisma generate --schema=./test/RateLimiterPrisma/Postgres/schema.prisma && prisma db push --schema=./test/RateLimiterPrisma/Postgres/schema.prisma", "drizzle:postgres": "cd ./test/RateLimiterDrizzle/Postgres && drizzle-kit push", - "test": "npm run prisma:postgres && npm run drizzle:postgres && nyc --reporter=html --reporter=text mocha \"test/**/*.test.js\"", + "test:drizzle:postgres:v1": "npm i --no-save --no-package-lock drizzle-orm@^1.0.0-beta.23 && mocha test/RateLimiterDrizzle/Postgres/RateLimiterDrizzlePostgres.test.js && ( [ \"$GITHUB_ACTIONS\" = \"true\" ] || npm i )", + "test": "npm run prisma:postgres && npm run drizzle:postgres && nyc --reporter=html --reporter=text mocha \"test/**/*.test.js\" && npm run test:drizzle:postgres:v1", "debug-test": "mocha --inspect-brk lib/**/**.test.js", "coveralls": "cat ./coverage/lcov.info | coveralls", "eslint": "eslint --quiet lib/**/**.js test/**/**.js",