@donotlb/keypal 0.1.0 → 0.1.1

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.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # keypal
2
2
 
3
+ > Fork of [izadoesdev/keypal](https://github.com/izadoesdev/keypal). Drops Prisma, Kysely, and Convex adapters.
4
+
3
5
  [![Test](https://github.com/izadoesdev/keypal/actions/workflows/test.yml/badge.svg)](https://github.com/izadoesdev/keypal/actions/workflows/test.yml)
4
6
  [![Benchmark](https://github.com/izadoesdev/keypal/actions/workflows/benchmark.yml/badge.svg)](https://github.com/izadoesdev/keypal/actions/workflows/benchmark.yml)
5
7
  [![npm version](https://badge.fury.io/js/keypal.svg)](https://badge.fury.io/js/keypal)
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @donotlb/keypal v0.1.0
2
+ * @donotlb/keypal v0.1.1
3
3
  * A TypeScript library for secure API key management with cryptographic hashing, expiration, scopes, and pluggable storage
4
4
  * © 2026 "donotlb" <donotlb@gmail.com>
5
5
  * Released under the MIT License
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * @donotlb/keypal v0.1.0
2
+ * @donotlb/keypal v0.1.1
3
3
  * A TypeScript library for secure API key management with cryptographic hashing, expiration, scopes, and pluggable storage
4
4
  * © 2026 "donotlb" <donotlb@gmail.com>
5
5
  * Released under the MIT License
6
6
  * https://github.com/donotlb/keypal#readme
7
- */import{nanoid as I}from"nanoid";import{l as d,g as D}from"./shared/keypal.lTVSZWgp.mjs";import{createHash as R,timingSafeEqual as O}from"node:crypto";import{MemoryStore as T}from"./storage/memory.mjs";import{RedisStore as y}from"./storage/redis.mjs";import"./shared/keypal.C-UeOmUF.mjs";function g(s){return s?new Date(s)<=new Date:!1}function L(s){return s?new Date(s):null}const k=["authorization","x-api-key"],S="bearer ";function _(s,e){if(s instanceof Headers)return s.get(e);const t=e.toLowerCase();for(const a in s)if(a.toLowerCase()===t)return s[a]??null;return null}function N(s,e){const t=s.trim();if(!t)return null;const a=t.toLowerCase();return a==="bearer"?null:a.startsWith(S)&&e?t.slice(7).trim()||null:t}function E(s,e={}){const{headerNames:t=k,extractBearer:a=!0}=e;for(const r of t){const o=_(s,r);if(!o)continue;const n=N(o,a);if(n)return n}return null}function m(s,e={}){return E(s,e)!==null}function v(s,e,t){return s?.includes(e)?!0:(t?.resource,!1)}function x(s,e,t){return!s||s.length===0?!1:e.some(a=>s.includes(a))?!0:(t?.resource,!1)}function K(s,e,t){return!s||s.length===0?!1:e.every(a=>s.includes(a))?!0:(t?.resource,!1)}function C(s,e,t,a){return!!(s?.includes(t)||a?.resource&&e&&e[a.resource]?.includes(t))}function B(s,e,t,a){if(s&&t.some(r=>s.includes(r)))return!0;if(a?.resource&&e){const r=e[a.resource];if(r&&t.some(o=>r.includes(o)))return!0}return!1}function b(s,e,t,a){if(s&&t.every(r=>s.includes(r)))return!0;if(a?.resource&&e){const r=e[a.resource];if(!r)return!1;if(t.every(n=>r.includes(n)))return!0;const o=[...s||[],...r];return t.every(n=>o.includes(n))}return!1}class A{resources={};add(e,t,a){const r=`${e}:${t}`;if(this.resources[r]){const o=new Set(this.resources[r]);for(const n of a)o.add(n);this.resources[r]=Array.from(o)}else this.resources[r]=a;return this}addOne(e,t,a){return this.add(e,t,[a])}addMany(e,t,a){for(const r of t)this.add(e,r,a);return this}remove(e,t){const a=`${e}:${t}`;return delete this.resources[a],this}removeScopes(e,t,a){const r=`${e}:${t}`;if(this.resources[r]){const o=new Set(a);this.resources[r]=this.resources[r]?.filter(n=>!o.has(n))??[],this.resources[r]?.length===0&&delete this.resources[r]}return this}has(e,t){return`${e}:${t}`in this.resources}get(e,t){const a=`${e}:${t}`;return this.resources[a]||[]}clear(){return this.resources={},this}build(){return{...this.resources}}static from(e){const t=new A;return t.resources={...e},t}}function G(){return new A}class Y{cache=new Map;maxSize;cleanupTimer=null;constructor(e={}){this.maxSize=e.maxSize??1e4;const t=e.cleanupInterval??6e4;this.cleanupTimer=setInterval(()=>this.cleanup(),t),this.cleanupTimer.unref?.()}get(e){const t=this.cache.get(e);return t?t.expires<Date.now()?(this.cache.delete(e),null):t.value:null}set(e,t,a=60){if(this.cache.size>=this.maxSize&&!this.cache.has(e)&&(this.cleanup(),this.cache.size>=this.maxSize)){const r=this.cache.keys().next().value;r!==void 0&&this.cache.delete(r)}this.cache.set(e,{value:t,expires:Date.now()+a*1e3})}del(e){this.cache.delete(e)}clear(){this.cache.clear()}cleanup(){const e=Date.now();for(const[t,a]of this.cache)a.expires<e&&this.cache.delete(t)}dispose(){this.cleanupTimer&&(clearInterval(this.cleanupTimer),this.cleanupTimer=null)}get size(){return this.cache.size}}class ${client;constructor(e){this.client=e}async get(e){return this.client.get(e)}async set(e,t,a=60){await this.client.setex(e,a,t)}async del(e){await this.client.del(e)}}function p(s,e={}){const{algorithm:t="sha256",salt:a=""}=e,r=a?`${s}${a}`:s;return R(t).update(r).digest("hex")}function F(s,e,t={}){const a=p(s,t);return a.length!==e.length?!1:O(Buffer.from(a),Buffer.from(e))}const i={MISSING_KEY:"MISSING_KEY",INVALID_FORMAT:"INVALID_FORMAT",INVALID_KEY:"INVALID_KEY",EXPIRED:"EXPIRED",REVOKED:"REVOKED",DISABLED:"DISABLED",STORAGE_ERROR:"STORAGE_ERROR",CACHE_ERROR:"CACHE_ERROR",ALREADY_REVOKED:"ALREADY_REVOKED",ALREADY_ENABLED:"ALREADY_ENABLED",ALREADY_DISABLED:"ALREADY_DISABLED",CANNOT_MODIFY_REVOKED:"CANNOT_MODIFY_REVOKED",KEY_NOT_FOUND:"KEY_NOT_FOUND",AUDIT_LOGGING_DISABLED:"AUDIT_LOGGING_DISABLED",STORAGE_NOT_SUPPORTED:"STORAGE_NOT_SUPPORTED"},P={[i.MISSING_KEY]:"Missing API key",[i.INVALID_FORMAT]:"Invalid API key format",[i.INVALID_KEY]:"Invalid API key",[i.EXPIRED]:"API key has expired",[i.REVOKED]:"API key has been revoked",[i.DISABLED]:"API key is disabled",[i.STORAGE_ERROR]:"Storage error occurred",[i.CACHE_ERROR]:"Cache error occurred",[i.ALREADY_REVOKED]:"API key is already revoked",[i.ALREADY_ENABLED]:"API key is already enabled",[i.ALREADY_DISABLED]:"API key is already disabled",[i.CANNOT_MODIFY_REVOKED]:"Cannot modify a revoked key",[i.KEY_NOT_FOUND]:"API key not found",[i.AUDIT_LOGGING_DISABLED]:"Audit logging is not enabled",[i.STORAGE_NOT_SUPPORTED]:"Storage does not support this operation"};function c(s,e){return{code:s,message:P[s],details:e}}function l(s,e){const t=c(s,e);return{valid:!1,error:t.message,errorCode:t.code}}function U(s){if(typeof s!="object"||s===null)return!1;const e=s;return typeof e.id=="string"&&(e.expiresAt===null||typeof e.expiresAt=="string")&&(e.revokedAt===null||typeof e.revokedAt=="string")&&typeof e.enabled=="boolean"}class M{config;storage;cache;cacheTtl;extractionOptions;revokedKeyTtl;isRedisStorage;autoTrackUsage;auditLogsEnabled;defaultContext;constructor(e={}){const t=e.salt?p(e.salt,{algorithm:"sha256"}):"";if(this.config={prefix:e.prefix,length:e.length??32,algorithm:e.algorithm??"sha256",alphabet:e.alphabet,salt:t},this.revokedKeyTtl=e.revokedKeyTtl??604800,this.isRedisStorage=e.storage==="redis",this.autoTrackUsage=e.autoTrackUsage??!0,this.auditLogsEnabled=e.auditLogs??!1,this.defaultContext=e.auditContext,e.storage==="redis"){if(!e.redis)throw new Error('Redis client required when storage is "redis"');try{this.storage=new y({client:e.redis})}catch(a){throw d.error("CRITICAL: Failed to initialize Redis storage:",a),a}}else e.storage&&typeof e.storage=="object"?this.storage=e.storage:this.storage=new T;if(this.cacheTtl=e.cacheTtl??60,this.extractionOptions={headerNames:e.headerNames??["authorization","x-api-key"],extractBearer:e.extractBearer??!0},e.cache==="redis"){if(!e.redis)throw new Error("[keypal] Redis client required when cache is 'redis'");try{this.cache=new $(e.redis)}catch(a){throw d.error("CRITICAL: Failed to initialize Redis cache:",a),a}}else e.cache===!0?this.cache=new Y:e.cache&&typeof e.cache=="object"&&(this.cache=e.cache)}generateKey(){return D({prefix:this.config.prefix,length:this.config.length,alphabet:this.config.alphabet})}hashKey(e){return p(e,{algorithm:this.config.algorithm,salt:this.config.salt})}validateKey(e,t){return F(e,t,{algorithm:this.config.algorithm,salt:this.config.salt})}extractKey(e,t){const a={headerNames:t?.headerNames??this.extractionOptions.headerNames,extractBearer:t?.extractBearer??this.extractionOptions.extractBearer};return E(e,a)}hasKey(e,t){const a={headerNames:t?.headerNames??this.extractionOptions.headerNames,extractBearer:t?.extractBearer??this.extractionOptions.extractBearer};return m(e,a)}async verify(e,t={}){let a;if(typeof e=="string")a=e,e.startsWith("Bearer ")&&(a=e.slice(7).trim());else{const n={headerNames:t.headerNames??this.extractionOptions.headerNames,extractBearer:t.extractBearer??this.extractionOptions.extractBearer};a=this.extractKey(e,n)}if(!a)return l(i.MISSING_KEY);if(this.config.prefix&&!a.startsWith(this.config.prefix))return l(i.INVALID_FORMAT);const r=this.hashKey(a);if(this.cache&&!t.skipCache){const n=await this.cache.get(`apikey:${r}`);if(n)try{const h=JSON.parse(n);if(!U(h))d.error("CRITICAL: Invalid cache record shape, invalidating entry"),await this.cache.del(`apikey:${r}`);else{const u=h;if(u.expiresAt&&g(u.expiresAt))return await this.cache.del(`apikey:${r}`),l(i.EXPIRED);if(u.revokedAt)return await this.cache.del(`apikey:${r}`),l(i.REVOKED);if(u.enabled===!1)return l(i.DISABLED);const f=await this.storage.findById(u.id);return f?g(f.metadata.expiresAt)?(await this.cache.del(`apikey:${r}`),l(i.EXPIRED)):f.metadata.revokedAt?(await this.cache.del(`apikey:${r}`),l(i.REVOKED)):(this.autoTrackUsage&&!t.skipTracking&&this.updateLastUsed(f.id).catch(w=>{d.error("Failed to track usage:",w)}),{valid:!0,record:f}):(await this.cache.del(`apikey:${r}`),l(i.INVALID_KEY))}}catch(h){d.error("CRITICAL: Cache corruption detected, invalidating entry:",h),await this.cache.del(`apikey:${r}`)}}const o=await this.storage.findByHash(r);if(!o)return l(i.INVALID_KEY);if(g(o.metadata.expiresAt))return this.cache&&await this.cache.del(`apikey:${r}`),l(i.EXPIRED);if(o.metadata.revokedAt)return this.cache&&await this.cache.del(`apikey:${r}`),l(i.REVOKED);if(o.metadata.enabled===!1)return l(i.DISABLED);if(this.cache&&!t.skipCache)try{const n={id:o.id,expiresAt:o.metadata.expiresAt??null,revokedAt:o.metadata.revokedAt??null,enabled:o.metadata.enabled??!0};await this.cache.set(`apikey:${r}`,JSON.stringify(n),this.cacheTtl)}catch(n){d.error("CRITICAL: Failed to write to cache:",n)}return this.autoTrackUsage&&!t.skipTracking&&this.updateLastUsed(o.id).catch(n=>{d.error("Failed to track usage:",n)}),{valid:!0,record:o}}async create(e,t){const a=this.generateKey(),r=this.hashKey(a),o=new Date().toISOString(),n=e.tags?.map(u=>u.toLowerCase()),h={id:I(),keyHash:r,metadata:{ownerId:e.ownerId??"",name:e.name,description:e.description,scopes:e.scopes,resources:e.resources,expiresAt:e.expiresAt??null,createdAt:o,lastUsedAt:void 0,enabled:e.enabled??!0,revokedAt:null,rotatedTo:null,tags:n}};return await this.storage.save(h),await this.logAction("created",h.id,h.metadata.ownerId,{...t,metadata:{name:h.metadata.name,scopes:h.metadata.scopes,...t?.metadata}}),{key:a,record:h}}async findByHash(e){return await this.storage.findByHash(e)}async findById(e){return await this.storage.findById(e)}async findByTags(e,t){return await this.storage.findByTags(e,t)}async findByTag(e,t){return await this.storage.findByTag(e,t)}async list(e){return await this.storage.findByOwner(e)}async revoke(e,t){const a=await this.findById(e);if(!a)throw c(i.KEY_NOT_FOUND);if(a.metadata.revokedAt)throw c(i.ALREADY_REVOKED);if(await this.storage.updateMetadata(e,{revokedAt:new Date().toISOString()}),await this.logAction("revoked",e,a.metadata.ownerId,t),this.cache)try{await this.cache.del(`apikey:${a.keyHash}`)}catch(r){d.error("CRITICAL: Failed to invalidate cache on revoke:",r)}if(this.isRedisStorage&&this.revokedKeyTtl>0)try{this.storage instanceof y&&await this.storage.setTtl(e,this.revokedKeyTtl)}catch(r){d.error("Failed to set TTL on revoked key:",r)}}async revokeAll(e){const t=await this.list(e);await Promise.all(t.map(a=>this.revoke(a.id)))}async enable(e,t){const a=await this.findById(e);if(!a)throw c(i.KEY_NOT_FOUND);if(a.metadata.revokedAt)throw c(i.CANNOT_MODIFY_REVOKED);if(a.metadata.enabled)throw c(i.ALREADY_ENABLED);if(await this.storage.updateMetadata(e,{enabled:!0}),await this.logAction("enabled",e,a.metadata.ownerId,t),this.cache)try{await this.cache.del(`apikey:${a.keyHash}`)}catch(r){d.error("CRITICAL: Failed to invalidate cache on enable:",r)}}async disable(e,t){const a=await this.findById(e);if(!a)throw c(i.KEY_NOT_FOUND);if(a.metadata.revokedAt)throw c(i.CANNOT_MODIFY_REVOKED);if(!a.metadata.enabled)throw c(i.ALREADY_DISABLED);if(await this.storage.updateMetadata(e,{enabled:!1}),await this.logAction("disabled",e,a.metadata.ownerId,t),this.cache)try{await this.cache.del(`apikey:${a.keyHash}`)}catch(r){d.error("CRITICAL: Failed to invalidate cache on disable:",r)}}async rotate(e,t,a){const r=await this.findById(e);if(!r)throw c(i.KEY_NOT_FOUND);if(r.metadata.revokedAt)throw c(i.CANNOT_MODIFY_REVOKED);const{key:o,record:n}=await this.create({ownerId:r.metadata.ownerId,name:t?.name??r.metadata.name,description:t?.description??r.metadata.description,scopes:t?.scopes??r.metadata.scopes,resources:t?.resources??r.metadata.resources,expiresAt:t?.expiresAt??r.metadata.expiresAt,tags:t?.tags?t.tags.map(h=>h.toLowerCase()):r.metadata.tags});if(await this.storage.updateMetadata(e,{rotatedTo:n.id,revokedAt:new Date().toISOString()}),await this.logAction("rotated",e,r.metadata.ownerId,{...a,metadata:{rotatedTo:n.id,...a?.metadata}}),this.cache)try{await this.cache.del(`apikey:${r.keyHash}`)}catch(h){d.error("CRITICAL: Failed to invalidate cache on rotate:",h)}if(this.isRedisStorage&&this.revokedKeyTtl>0)try{this.storage instanceof y&&await this.storage.setTtl(e,this.revokedKeyTtl)}catch(h){d.error("Failed to set TTL on rotated key:",h)}return{key:o,record:n,oldRecord:r}}async updateLastUsed(e){await this.storage.updateMetadata(e,{lastUsedAt:new Date().toISOString()})}async logAction(e,t,a,r){if(!(this.auditLogsEnabled&&this.storage.saveLog))return;const o={...this.defaultContext,...r,...this.defaultContext?.metadata||r?.metadata?{metadata:{...this.defaultContext?.metadata,...r?.metadata}}:{}},n={id:I(),action:e,keyId:t,ownerId:a,timestamp:new Date().toISOString(),data:Object.keys(o).length>0?o:void 0};try{await this.storage.saveLog(n)}catch(h){d.error("Failed to save audit log:",h)}}async getLogs(e={}){if(!this.auditLogsEnabled)throw c(i.AUDIT_LOGGING_DISABLED);if(!this.storage.findLogs)throw c(i.STORAGE_NOT_SUPPORTED);return await this.storage.findLogs(e)}async countLogs(e={}){if(!this.auditLogsEnabled)throw c(i.AUDIT_LOGGING_DISABLED);if(!this.storage.countLogs)throw c(i.STORAGE_NOT_SUPPORTED);return await this.storage.countLogs(e)}async deleteLogs(e){if(!this.auditLogsEnabled)throw c(i.AUDIT_LOGGING_DISABLED);if(!this.storage.deleteLogs)throw c(i.STORAGE_NOT_SUPPORTED);return await this.storage.deleteLogs(e)}async clearLogs(e){return await this.deleteLogs({keyId:e})}async getLogStats(e){if(!this.auditLogsEnabled)throw c(i.AUDIT_LOGGING_DISABLED);if(!this.storage.getLogStats)throw c(i.STORAGE_NOT_SUPPORTED);return await this.storage.getLogStats(e)}async invalidateCache(e){if(this.cache)try{await this.cache.del(`apikey:${e}`)}catch(t){throw d.error("CRITICAL: Failed to invalidate cache:",t),t}}isExpired(e){return g(e.metadata.expiresAt)}hasScope(e,t,a){return C(e.metadata.scopes,e.metadata.resources,t,a)}hasAnyScope(e,t,a){return B(e.metadata.scopes,e.metadata.resources,t,a)}hasAllScopes(e,t,a){return b(e.metadata.scopes,e.metadata.resources,t,a)}async verifyFromHeaders(e,t){const a=await this.verify(e,t);return a.valid?a.record??null:null}checkResourceScope(e,t,a,r){return e?this.hasScope(e,r,{resource:`${t}:${a}`}):!1}checkResourceAnyScope(e,t,a,r){return e?this.hasAnyScope(e,r,{resource:`${t}:${a}`}):!1}checkResourceAllScopes(e,t,a,r){return e?this.hasAllScopes(e,r,{resource:`${t}:${a}`}):!1}}function V(s={}){return new M(s)}export{i as ApiKeyErrorCode,A as ResourceBuilder,c as createApiKeyError,V as createKeys,G as createResourceBuilder,E as extractKeyFromHeaders,L as getExpirationTime,K as hasAllScopes,x as hasAnyScope,m as hasApiKey,v as hasScope,g as isExpired};
7
+ */import{nanoid as I}from"nanoid";import{l as d,g as D}from"./shared/keypal.DYJcnXna.mjs";import{createHash as R,timingSafeEqual as O}from"node:crypto";import{MemoryStore as T}from"./storage/memory.mjs";import{RedisStore as y}from"./storage/redis.mjs";import"./shared/keypal.Brd64SyI.mjs";function g(s){return s?new Date(s)<=new Date:!1}function L(s){return s?new Date(s):null}const k=["authorization","x-api-key"],S="bearer ";function _(s,e){if(s instanceof Headers)return s.get(e);const t=e.toLowerCase();for(const a in s)if(a.toLowerCase()===t)return s[a]??null;return null}function N(s,e){const t=s.trim();if(!t)return null;const a=t.toLowerCase();return a==="bearer"?null:a.startsWith(S)&&e?t.slice(7).trim()||null:t}function E(s,e={}){const{headerNames:t=k,extractBearer:a=!0}=e;for(const r of t){const o=_(s,r);if(!o)continue;const n=N(o,a);if(n)return n}return null}function m(s,e={}){return E(s,e)!==null}function v(s,e,t){return s?.includes(e)?!0:(t?.resource,!1)}function x(s,e,t){return!s||s.length===0?!1:e.some(a=>s.includes(a))?!0:(t?.resource,!1)}function K(s,e,t){return!s||s.length===0?!1:e.every(a=>s.includes(a))?!0:(t?.resource,!1)}function C(s,e,t,a){return!!(s?.includes(t)||a?.resource&&e&&e[a.resource]?.includes(t))}function B(s,e,t,a){if(s&&t.some(r=>s.includes(r)))return!0;if(a?.resource&&e){const r=e[a.resource];if(r&&t.some(o=>r.includes(o)))return!0}return!1}function b(s,e,t,a){if(s&&t.every(r=>s.includes(r)))return!0;if(a?.resource&&e){const r=e[a.resource];if(!r)return!1;if(t.every(n=>r.includes(n)))return!0;const o=[...s||[],...r];return t.every(n=>o.includes(n))}return!1}class A{resources={};add(e,t,a){const r=`${e}:${t}`;if(this.resources[r]){const o=new Set(this.resources[r]);for(const n of a)o.add(n);this.resources[r]=Array.from(o)}else this.resources[r]=a;return this}addOne(e,t,a){return this.add(e,t,[a])}addMany(e,t,a){for(const r of t)this.add(e,r,a);return this}remove(e,t){const a=`${e}:${t}`;return delete this.resources[a],this}removeScopes(e,t,a){const r=`${e}:${t}`;if(this.resources[r]){const o=new Set(a);this.resources[r]=this.resources[r]?.filter(n=>!o.has(n))??[],this.resources[r]?.length===0&&delete this.resources[r]}return this}has(e,t){return`${e}:${t}`in this.resources}get(e,t){const a=`${e}:${t}`;return this.resources[a]||[]}clear(){return this.resources={},this}build(){return{...this.resources}}static from(e){const t=new A;return t.resources={...e},t}}function G(){return new A}class Y{cache=new Map;maxSize;cleanupTimer=null;constructor(e={}){this.maxSize=e.maxSize??1e4;const t=e.cleanupInterval??6e4;this.cleanupTimer=setInterval(()=>this.cleanup(),t),this.cleanupTimer.unref?.()}get(e){const t=this.cache.get(e);return t?t.expires<Date.now()?(this.cache.delete(e),null):t.value:null}set(e,t,a=60){if(this.cache.size>=this.maxSize&&!this.cache.has(e)&&(this.cleanup(),this.cache.size>=this.maxSize)){const r=this.cache.keys().next().value;r!==void 0&&this.cache.delete(r)}this.cache.set(e,{value:t,expires:Date.now()+a*1e3})}del(e){this.cache.delete(e)}clear(){this.cache.clear()}cleanup(){const e=Date.now();for(const[t,a]of this.cache)a.expires<e&&this.cache.delete(t)}dispose(){this.cleanupTimer&&(clearInterval(this.cleanupTimer),this.cleanupTimer=null)}get size(){return this.cache.size}}class ${client;constructor(e){this.client=e}async get(e){return this.client.get(e)}async set(e,t,a=60){await this.client.setex(e,a,t)}async del(e){await this.client.del(e)}}function p(s,e={}){const{algorithm:t="sha256",salt:a=""}=e,r=a?`${s}${a}`:s;return R(t).update(r).digest("hex")}function F(s,e,t={}){const a=p(s,t);return a.length!==e.length?!1:O(Buffer.from(a),Buffer.from(e))}const i={MISSING_KEY:"MISSING_KEY",INVALID_FORMAT:"INVALID_FORMAT",INVALID_KEY:"INVALID_KEY",EXPIRED:"EXPIRED",REVOKED:"REVOKED",DISABLED:"DISABLED",STORAGE_ERROR:"STORAGE_ERROR",CACHE_ERROR:"CACHE_ERROR",ALREADY_REVOKED:"ALREADY_REVOKED",ALREADY_ENABLED:"ALREADY_ENABLED",ALREADY_DISABLED:"ALREADY_DISABLED",CANNOT_MODIFY_REVOKED:"CANNOT_MODIFY_REVOKED",KEY_NOT_FOUND:"KEY_NOT_FOUND",AUDIT_LOGGING_DISABLED:"AUDIT_LOGGING_DISABLED",STORAGE_NOT_SUPPORTED:"STORAGE_NOT_SUPPORTED"},P={[i.MISSING_KEY]:"Missing API key",[i.INVALID_FORMAT]:"Invalid API key format",[i.INVALID_KEY]:"Invalid API key",[i.EXPIRED]:"API key has expired",[i.REVOKED]:"API key has been revoked",[i.DISABLED]:"API key is disabled",[i.STORAGE_ERROR]:"Storage error occurred",[i.CACHE_ERROR]:"Cache error occurred",[i.ALREADY_REVOKED]:"API key is already revoked",[i.ALREADY_ENABLED]:"API key is already enabled",[i.ALREADY_DISABLED]:"API key is already disabled",[i.CANNOT_MODIFY_REVOKED]:"Cannot modify a revoked key",[i.KEY_NOT_FOUND]:"API key not found",[i.AUDIT_LOGGING_DISABLED]:"Audit logging is not enabled",[i.STORAGE_NOT_SUPPORTED]:"Storage does not support this operation"};function c(s,e){return{code:s,message:P[s],details:e}}function l(s,e){const t=c(s,e);return{valid:!1,error:t.message,errorCode:t.code}}function U(s){if(typeof s!="object"||s===null)return!1;const e=s;return typeof e.id=="string"&&(e.expiresAt===null||typeof e.expiresAt=="string")&&(e.revokedAt===null||typeof e.revokedAt=="string")&&typeof e.enabled=="boolean"}class M{config;storage;cache;cacheTtl;extractionOptions;revokedKeyTtl;isRedisStorage;autoTrackUsage;auditLogsEnabled;defaultContext;constructor(e={}){const t=e.salt?p(e.salt,{algorithm:"sha256"}):"";if(this.config={prefix:e.prefix,length:e.length??32,algorithm:e.algorithm??"sha256",alphabet:e.alphabet,salt:t},this.revokedKeyTtl=e.revokedKeyTtl??604800,this.isRedisStorage=e.storage==="redis",this.autoTrackUsage=e.autoTrackUsage??!0,this.auditLogsEnabled=e.auditLogs??!1,this.defaultContext=e.auditContext,e.storage==="redis"){if(!e.redis)throw new Error('Redis client required when storage is "redis"');try{this.storage=new y({client:e.redis})}catch(a){throw d.error("CRITICAL: Failed to initialize Redis storage:",a),a}}else e.storage&&typeof e.storage=="object"?this.storage=e.storage:this.storage=new T;if(this.cacheTtl=e.cacheTtl??60,this.extractionOptions={headerNames:e.headerNames??["authorization","x-api-key"],extractBearer:e.extractBearer??!0},e.cache==="redis"){if(!e.redis)throw new Error("[keypal] Redis client required when cache is 'redis'");try{this.cache=new $(e.redis)}catch(a){throw d.error("CRITICAL: Failed to initialize Redis cache:",a),a}}else e.cache===!0?this.cache=new Y:e.cache&&typeof e.cache=="object"&&(this.cache=e.cache)}generateKey(){return D({prefix:this.config.prefix,length:this.config.length,alphabet:this.config.alphabet})}hashKey(e){return p(e,{algorithm:this.config.algorithm,salt:this.config.salt})}validateKey(e,t){return F(e,t,{algorithm:this.config.algorithm,salt:this.config.salt})}extractKey(e,t){const a={headerNames:t?.headerNames??this.extractionOptions.headerNames,extractBearer:t?.extractBearer??this.extractionOptions.extractBearer};return E(e,a)}hasKey(e,t){const a={headerNames:t?.headerNames??this.extractionOptions.headerNames,extractBearer:t?.extractBearer??this.extractionOptions.extractBearer};return m(e,a)}async verify(e,t={}){let a;if(typeof e=="string")a=e,e.startsWith("Bearer ")&&(a=e.slice(7).trim());else{const n={headerNames:t.headerNames??this.extractionOptions.headerNames,extractBearer:t.extractBearer??this.extractionOptions.extractBearer};a=this.extractKey(e,n)}if(!a)return l(i.MISSING_KEY);if(this.config.prefix&&!a.startsWith(this.config.prefix))return l(i.INVALID_FORMAT);const r=this.hashKey(a);if(this.cache&&!t.skipCache){const n=await this.cache.get(`apikey:${r}`);if(n)try{const h=JSON.parse(n);if(!U(h))d.error("CRITICAL: Invalid cache record shape, invalidating entry"),await this.cache.del(`apikey:${r}`);else{const u=h;if(u.expiresAt&&g(u.expiresAt))return await this.cache.del(`apikey:${r}`),l(i.EXPIRED);if(u.revokedAt)return await this.cache.del(`apikey:${r}`),l(i.REVOKED);if(u.enabled===!1)return l(i.DISABLED);const f=await this.storage.findById(u.id);return f?g(f.metadata.expiresAt)?(await this.cache.del(`apikey:${r}`),l(i.EXPIRED)):f.metadata.revokedAt?(await this.cache.del(`apikey:${r}`),l(i.REVOKED)):(this.autoTrackUsage&&!t.skipTracking&&this.updateLastUsed(f.id).catch(w=>{d.error("Failed to track usage:",w)}),{valid:!0,record:f}):(await this.cache.del(`apikey:${r}`),l(i.INVALID_KEY))}}catch(h){d.error("CRITICAL: Cache corruption detected, invalidating entry:",h),await this.cache.del(`apikey:${r}`)}}const o=await this.storage.findByHash(r);if(!o)return l(i.INVALID_KEY);if(g(o.metadata.expiresAt))return this.cache&&await this.cache.del(`apikey:${r}`),l(i.EXPIRED);if(o.metadata.revokedAt)return this.cache&&await this.cache.del(`apikey:${r}`),l(i.REVOKED);if(o.metadata.enabled===!1)return l(i.DISABLED);if(this.cache&&!t.skipCache)try{const n={id:o.id,expiresAt:o.metadata.expiresAt??null,revokedAt:o.metadata.revokedAt??null,enabled:o.metadata.enabled??!0};await this.cache.set(`apikey:${r}`,JSON.stringify(n),this.cacheTtl)}catch(n){d.error("CRITICAL: Failed to write to cache:",n)}return this.autoTrackUsage&&!t.skipTracking&&this.updateLastUsed(o.id).catch(n=>{d.error("Failed to track usage:",n)}),{valid:!0,record:o}}async create(e,t){const a=this.generateKey(),r=this.hashKey(a),o=new Date().toISOString(),n=e.tags?.map(u=>u.toLowerCase()),h={id:I(),keyHash:r,metadata:{ownerId:e.ownerId??"",name:e.name,description:e.description,scopes:e.scopes,resources:e.resources,expiresAt:e.expiresAt??null,createdAt:o,lastUsedAt:void 0,enabled:e.enabled??!0,revokedAt:null,rotatedTo:null,tags:n}};return await this.storage.save(h),await this.logAction("created",h.id,h.metadata.ownerId,{...t,metadata:{name:h.metadata.name,scopes:h.metadata.scopes,...t?.metadata}}),{key:a,record:h}}async findByHash(e){return await this.storage.findByHash(e)}async findById(e){return await this.storage.findById(e)}async findByTags(e,t){return await this.storage.findByTags(e,t)}async findByTag(e,t){return await this.storage.findByTag(e,t)}async list(e){return await this.storage.findByOwner(e)}async revoke(e,t){const a=await this.findById(e);if(!a)throw c(i.KEY_NOT_FOUND);if(a.metadata.revokedAt)throw c(i.ALREADY_REVOKED);if(await this.storage.updateMetadata(e,{revokedAt:new Date().toISOString()}),await this.logAction("revoked",e,a.metadata.ownerId,t),this.cache)try{await this.cache.del(`apikey:${a.keyHash}`)}catch(r){d.error("CRITICAL: Failed to invalidate cache on revoke:",r)}if(this.isRedisStorage&&this.revokedKeyTtl>0)try{this.storage instanceof y&&await this.storage.setTtl(e,this.revokedKeyTtl)}catch(r){d.error("Failed to set TTL on revoked key:",r)}}async revokeAll(e){const t=await this.list(e);await Promise.all(t.map(a=>this.revoke(a.id)))}async enable(e,t){const a=await this.findById(e);if(!a)throw c(i.KEY_NOT_FOUND);if(a.metadata.revokedAt)throw c(i.CANNOT_MODIFY_REVOKED);if(a.metadata.enabled)throw c(i.ALREADY_ENABLED);if(await this.storage.updateMetadata(e,{enabled:!0}),await this.logAction("enabled",e,a.metadata.ownerId,t),this.cache)try{await this.cache.del(`apikey:${a.keyHash}`)}catch(r){d.error("CRITICAL: Failed to invalidate cache on enable:",r)}}async disable(e,t){const a=await this.findById(e);if(!a)throw c(i.KEY_NOT_FOUND);if(a.metadata.revokedAt)throw c(i.CANNOT_MODIFY_REVOKED);if(!a.metadata.enabled)throw c(i.ALREADY_DISABLED);if(await this.storage.updateMetadata(e,{enabled:!1}),await this.logAction("disabled",e,a.metadata.ownerId,t),this.cache)try{await this.cache.del(`apikey:${a.keyHash}`)}catch(r){d.error("CRITICAL: Failed to invalidate cache on disable:",r)}}async rotate(e,t,a){const r=await this.findById(e);if(!r)throw c(i.KEY_NOT_FOUND);if(r.metadata.revokedAt)throw c(i.CANNOT_MODIFY_REVOKED);const{key:o,record:n}=await this.create({ownerId:r.metadata.ownerId,name:t?.name??r.metadata.name,description:t?.description??r.metadata.description,scopes:t?.scopes??r.metadata.scopes,resources:t?.resources??r.metadata.resources,expiresAt:t?.expiresAt??r.metadata.expiresAt,tags:t?.tags?t.tags.map(h=>h.toLowerCase()):r.metadata.tags});if(await this.storage.updateMetadata(e,{rotatedTo:n.id,revokedAt:new Date().toISOString()}),await this.logAction("rotated",e,r.metadata.ownerId,{...a,metadata:{rotatedTo:n.id,...a?.metadata}}),this.cache)try{await this.cache.del(`apikey:${r.keyHash}`)}catch(h){d.error("CRITICAL: Failed to invalidate cache on rotate:",h)}if(this.isRedisStorage&&this.revokedKeyTtl>0)try{this.storage instanceof y&&await this.storage.setTtl(e,this.revokedKeyTtl)}catch(h){d.error("Failed to set TTL on rotated key:",h)}return{key:o,record:n,oldRecord:r}}async updateLastUsed(e){await this.storage.updateMetadata(e,{lastUsedAt:new Date().toISOString()})}async logAction(e,t,a,r){if(!(this.auditLogsEnabled&&this.storage.saveLog))return;const o={...this.defaultContext,...r,...this.defaultContext?.metadata||r?.metadata?{metadata:{...this.defaultContext?.metadata,...r?.metadata}}:{}},n={id:I(),action:e,keyId:t,ownerId:a,timestamp:new Date().toISOString(),data:Object.keys(o).length>0?o:void 0};try{await this.storage.saveLog(n)}catch(h){d.error("Failed to save audit log:",h)}}async getLogs(e={}){if(!this.auditLogsEnabled)throw c(i.AUDIT_LOGGING_DISABLED);if(!this.storage.findLogs)throw c(i.STORAGE_NOT_SUPPORTED);return await this.storage.findLogs(e)}async countLogs(e={}){if(!this.auditLogsEnabled)throw c(i.AUDIT_LOGGING_DISABLED);if(!this.storage.countLogs)throw c(i.STORAGE_NOT_SUPPORTED);return await this.storage.countLogs(e)}async deleteLogs(e){if(!this.auditLogsEnabled)throw c(i.AUDIT_LOGGING_DISABLED);if(!this.storage.deleteLogs)throw c(i.STORAGE_NOT_SUPPORTED);return await this.storage.deleteLogs(e)}async clearLogs(e){return await this.deleteLogs({keyId:e})}async getLogStats(e){if(!this.auditLogsEnabled)throw c(i.AUDIT_LOGGING_DISABLED);if(!this.storage.getLogStats)throw c(i.STORAGE_NOT_SUPPORTED);return await this.storage.getLogStats(e)}async invalidateCache(e){if(this.cache)try{await this.cache.del(`apikey:${e}`)}catch(t){throw d.error("CRITICAL: Failed to invalidate cache:",t),t}}isExpired(e){return g(e.metadata.expiresAt)}hasScope(e,t,a){return C(e.metadata.scopes,e.metadata.resources,t,a)}hasAnyScope(e,t,a){return B(e.metadata.scopes,e.metadata.resources,t,a)}hasAllScopes(e,t,a){return b(e.metadata.scopes,e.metadata.resources,t,a)}async verifyFromHeaders(e,t){const a=await this.verify(e,t);return a.valid?a.record??null:null}checkResourceScope(e,t,a,r){return e?this.hasScope(e,r,{resource:`${t}:${a}`}):!1}checkResourceAnyScope(e,t,a,r){return e?this.hasAnyScope(e,r,{resource:`${t}:${a}`}):!1}checkResourceAllScopes(e,t,a,r){return e?this.hasAllScopes(e,r,{resource:`${t}:${a}`}):!1}}function V(s={}){return new M(s)}export{i as ApiKeyErrorCode,A as ResourceBuilder,c as createApiKeyError,V as createKeys,G as createResourceBuilder,E as extractKeyFromHeaders,L as getExpirationTime,K as hasAllScopes,x as hasAnyScope,m as hasApiKey,v as hasScope,g as isExpired};
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @donotlb/keypal v0.1.0
2
+ * @donotlb/keypal v0.1.1
3
3
  * A TypeScript library for secure API key management with cryptographic hashing, expiration, scopes, and pluggable storage
4
4
  * © 2026 "donotlb" <donotlb@gmail.com>
5
5
  * Released under the MIT License
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @donotlb/keypal v0.1.0
2
+ * @donotlb/keypal v0.1.1
3
3
  * A TypeScript library for secure API key management with cryptographic hashing, expiration, scopes, and pluggable storage
4
4
  * © 2026 "donotlb" <donotlb@gmail.com>
5
5
  * Released under the MIT License
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * @donotlb/keypal v0.1.0
2
+ * @donotlb/keypal v0.1.1
3
3
  * A TypeScript library for secure API key management with cryptographic hashing, expiration, scopes, and pluggable storage
4
4
  * © 2026 "donotlb" <donotlb@gmail.com>
5
5
  * Released under the MIT License
6
6
  * https://github.com/donotlb/keypal#readme
7
- */import{eq as g,and as p,arrayContains as w,or as N}from"drizzle-orm";import{g as O,l as I}from"../shared/keypal.lTVSZWgp.mjs";import{c as S,D as C}from"../shared/keypal.C-UeOmUF.mjs";import"nanoid";const v=f=>{const a={supportsDates:!0,supportsBooleans:!0,usePlural:!1,disableIdGeneration:!1,debugLogs:!1,...f.config},r={apiKeyTable:a.usePlural?"apikeys":"apikey",auditLogTable:a.usePlural?"auditlogs":"auditlog",flattenMetadata:!1,...f.schema},s=(...e)=>{if(!a.debugLogs)return;if(typeof a.debugLogs=="boolean"){I.info(`[${a.adapterName}]`,...e);return}const t=typeof e[0]=="string"?e[0]:null;t&&a.debugLogs[t]&&I.info(`[${a.adapterName}]`,...e)},m=e=>e==="apikey"?r.apiKeyTable:r.auditLogTable,n=(e,t)=>{if(e==="apikey"){const o=r.apiKeyColumns||{};if(o.metadataColumns&&t in o.metadataColumns)return o.metadataColumns[t]||t;if(t==="id")return o.id||"id";if(t==="keyHash")return o.keyHash||"keyHash";if(t==="metadata")return o.metadata||"metadata"}else return(r.auditLogColumns||{})[t]||t;return t},h={config:a,schema:r,debugLog:s,getColumnName:n,getTableName:m,transformApiKeyInput:e=>{const t={};if(r.flattenMetadata){t[n("apikey","id")]=e.id,t[n("apikey","keyHash")]=e.keyHash;for(const[o,i]of Object.entries(e.metadata)){const u=n("apikey",o);i==null?t[u]=null:typeof i=="object"?a.supportsJSON?t[u]=i:t[u]=JSON.stringify(i):i&&typeof i=="object"&&"toISOString"in i?a.supportsDates?t[u]=i:t[u]=i.toISOString():typeof i=="boolean"?a.supportsBooleans?t[u]=i:t[u]=i?1:0:t[u]=i}}else t[n("apikey","id")]=e.id,t[n("apikey","keyHash")]=e.keyHash,a.supportsJSON?t[n("apikey","metadata")]=e.metadata:t[n("apikey","metadata")]=JSON.stringify(e.metadata);return t},transformApiKeyOutput:e=>{const t=String(e[n("apikey","id")]),o=String(e[n("apikey","keyHash")]);let i;if(r.flattenMetadata){i={};const u=["ownerId","name","description","scopes","resources","rateLimit","expiresAt","revokedAt","lastUsedAt","createdAt","tags","allowedIps","allowedOrigins"];for(const d of u){const y=n("apikey",d),c=e[y];c!=null&&(d==="expiresAt"||d==="revokedAt"||d==="lastUsedAt"||d==="createdAt"?typeof c=="string"&&!a.supportsDates?i[d]=new Date(c):i[d]=c:d==="scopes"||d==="resources"||d==="tags"||d==="allowedIps"||d==="allowedOrigins"?typeof c=="string"&&!a.supportsJSON?i[d]=JSON.parse(c):i[d]=c:d==="rateLimit"&&typeof c=="string"&&!a.supportsJSON?i[d]=JSON.parse(c):i[d]=c)}}else{const u=e[n("apikey","metadata")];typeof u=="string"?i=JSON.parse(u):i=u}return{id:t,keyHash:o,metadata:i}},transformAuditLogInput:e=>{const t={};if(t[n("auditlog","id")]=e.id,t[n("auditlog","keyId")]=e.keyId,t[n("auditlog","ownerId")]=e.ownerId,t[n("auditlog","action")]=e.action,t[n("auditlog","timestamp")]=e.timestamp,e.data){const o=n("auditlog","data");a.supportsJSON?t[o]=e.data:t[o]=JSON.stringify(e.data)}return t},transformAuditLogOutput:e=>{const t={id:String(e[n("auditlog","id")]),keyId:String(e[n("auditlog","keyId")]),ownerId:String(e[n("auditlog","ownerId")]),action:e[n("auditlog","action")],timestamp:String(e[n("auditlog","timestamp")])},o=e[n("auditlog","data")];return o&&(typeof o=="string"?t.data=JSON.parse(o):t.data=o),t}},l=f.adapter(h);return{async save(e){s("save","Input:",e),!e.id&&!a.disableIdGeneration&&(e.id=a.customIdGenerator?a.customIdGenerator():O()),await l.save(e),s("save","Saved successfully")},async findByHash(e){s("findByHash","Hash:",e);const t=await l.findByHash(e);return s("findByHash","Result:",t),t},async findById(e){s("findById","ID:",e);const t=await l.findById(e);return s("findById","Result:",t),t},async findByOwner(e){s("findByOwner","Owner ID:",e);const t=await l.findByOwner(e);return s("findByOwner","Found:",t.length,"keys"),t},async findByTags(e,t){s("findByTags","Tags:",e,"Owner ID:",t);const o=await l.findByTags(e,t);return s("findByTags","Found:",o.length,"keys"),o},async findByTag(e,t){s("findByTag","Tag:",e,"Owner ID:",t);const o=await l.findByTag(e,t);return s("findByTag","Found:",o.length,"keys"),o},async updateMetadata(e,t){s("updateMetadata","ID:",e,"Metadata:",t),await l.updateMetadata(e,t),s("updateMetadata","Updated successfully")},async delete(e){s("delete","ID:",e),await l.delete(e),s("delete","Deleted successfully")},async deleteByOwner(e){s("deleteByOwner","Owner ID:",e),await l.deleteByOwner(e),s("deleteByOwner","Deleted successfully")},...l.saveLog&&{async saveLog(e){s("saveLog","Log:",e),await l.saveLog(e),s("saveLog","Saved successfully")}},...l.findLogs&&{async findLogs(e){s("findLogs","Query:",e);const t=await l.findLogs(e);return s("findLogs","Found:",t.length,"logs"),t}},...l.countLogs&&{async countLogs(e){s("countLogs","Query:",e);const t=await l.countLogs(e);return s("countLogs","Count:",t),t}},...l.deleteLogs&&{async deleteLogs(e){s("deleteLogs","Query:",e);const t=await l.deleteLogs(e);return s("deleteLogs","Deleted:",t,"logs"),t}},...l.getLogStats&&{async getLogStats(e){s("getLogStats","Owner ID:",e);const t=await l.getLogStats(e);return s("getLogStats","Stats:",t),t}}}};function b(f){const a=f.constructor?.name||"";return a.includes("Pg")||a.includes("Postgres")?"pg":a.includes("MySQL")||a.includes("Maria")?"mysql":a.includes("SQLite")||a.includes("Sqlite")?"sqlite":(I.warn("[Drizzle Store] Could not detect database provider, defaulting to PostgreSQL"),"pg")}function k(f){const{db:a,table:r,auditLogTable:s}=f,m=f.provider??b(a);return m==="mysql"&&s&&I.warn("[Drizzle Store] MySQL detected: RETURNING clause not supported, using fallback queries"),v({config:{adapterId:"drizzle",adapterName:"Drizzle ORM",supportsJSON:m!=="sqlite",supportsDates:!0,supportsBooleans:!0,debugLogs:f.debugLogs},schema:f.schema,adapter:n=>{const{transformApiKeyInput:h,transformApiKeyOutput:l}=n;return{async save(e){const t=h(e);await a.insert(r).values(t)},async findByHash(e){const t=n.getColumnName("apikey","keyHash"),o=await a.select().from(r).where(g(r[t],e)).limit(1);return o.length>0&&o[0]?l(o[0]):null},async findById(e){const t=n.getColumnName("apikey","id"),o=await a.select().from(r).where(g(r[t],e)).limit(1);return o.length>0&&o[0]?l(o[0]):null},async findByOwner(e){let t;if(n.schema.flattenMetadata){const o=n.getColumnName("apikey","ownerId");t=await a.select().from(r).where(g(r[o],e))}else{const o=n.getColumnName("apikey","metadata");t=await a.select().from(r).where(w(r[o],{ownerId:e}))}return t.map(l)},async findByTags(e,t){const o=[];if(n.schema.flattenMetadata){const i=n.getColumnName("apikey","tags"),u=e.map(d=>d.toLowerCase());for(const d of u)o.push(w(r[i],[d]));if(t!==void 0){const d=n.getColumnName("apikey","ownerId");o.push(g(r[d],t))}}else{const i=n.getColumnName("apikey","metadata");if(e.length>0){const u=e.map(d=>d.toLowerCase()).map(d=>w(r[i],{tags:[d]}));o.push(N(...u))}t!==void 0&&o.push(w(r[i],{ownerId:t}))}return o.length===0?[]:(await a.select().from(r).where(p(...o))).map(l)},async findByTag(e,t){return this.findByTags([e],t)},async updateMetadata(e,t){const o=await this.findById(e);if(!o)throw new Error(`API key with id ${e} not found`);const i={...o.metadata,...t},u={...o,metadata:i},d=h(u),y=n.getColumnName("apikey","id");if(n.schema.flattenMetadata){const c={};for(const[B]of Object.entries(t)){const L=n.getColumnName("apikey",B);c[L]=d[L]}await a.update(r).set(c).where(g(r[y],e))}else{const c=n.getColumnName("apikey","metadata");await a.update(r).set({[c]:i}).where(g(r[y],e))}},async delete(e){const t=n.getColumnName("apikey","id");await a.delete(r).where(g(r[t],e))},async deleteByOwner(e){if(n.schema.flattenMetadata){const t=n.getColumnName("apikey","ownerId");await a.delete(r).where(g(r[t],e))}else{const t=n.getColumnName("apikey","metadata");await a.delete(r).where(w(r[t],{ownerId:e}))}},...s&&n.transformAuditLogInput&&{async saveLog(e){const t=n.transformAuditLogInput(e);await a.insert(s).values(t)},async findLogs(e){const t=[];if(e.keyId){const y=n.getColumnName("auditlog","keyId");t.push(g(s[y],e.keyId))}if(e.ownerId){const y=n.getColumnName("auditlog","ownerId");t.push(g(s[y],e.ownerId))}if(e.action){const y=n.getColumnName("auditlog","action");t.push(g(s[y],e.action))}let o=a.select().from(s);t.length>0&&(o=o.where(p(...t)));const i=e.offset??0,u=e.limit??C,d=n.getColumnName("auditlog","timestamp");return(await o.orderBy(s[d]).limit(u).offset(i)).map(y=>n.transformAuditLogOutput?.(y))},async countLogs(e){const t=[];if(e.keyId){const i=n.getColumnName("auditlog","keyId");t.push(g(s[i],e.keyId))}if(e.ownerId){const i=n.getColumnName("auditlog","ownerId");t.push(g(s[i],e.ownerId))}if(e.action){const i=n.getColumnName("auditlog","action");t.push(g(s[i],e.action))}let o=a.select().from(s);return t.length>0&&(o=o.where(p(...t))),(await o).length},async deleteLogs(e){const t=[];if(e.keyId){const i=n.getColumnName("auditlog","keyId");t.push(g(s[i],e.keyId))}if(e.ownerId){const i=n.getColumnName("auditlog","ownerId");t.push(g(s[i],e.ownerId))}if(e.action){const i=n.getColumnName("auditlog","action");t.push(g(s[i],e.action))}if(t.length===0)return 0;if(m==="mysql"){const i=await a.select().from(s).where(p(...t));return await a.delete(s).where(p(...t)),i.length}const o=await a.delete(s).where(p(...t)).returning();return Array.isArray(o)?o.length:0},async getLogStats(e){const t=n.getColumnName("auditlog","ownerId"),o=(await a.select().from(s).where(g(s[t],e))).map(i=>n.transformAuditLogOutput?.(i));return S(o)}}}}})}class A{storage;constructor(a){this.storage=k(a)}save=a=>this.storage.save(a);findByHash=a=>this.storage.findByHash(a);findById=a=>this.storage.findById(a);findByOwner=a=>this.storage.findByOwner(a);findByTags=(a,r)=>this.storage.findByTags(a,r);findByTag=(a,r)=>this.storage.findByTag(a,r);updateMetadata=(a,r)=>this.storage.updateMetadata(a,r);delete=a=>this.storage.delete(a);deleteByOwner=a=>this.storage.deleteByOwner(a);saveLog=a=>this.storage.saveLog?.(a)??Promise.resolve();findLogs=a=>this.storage.findLogs?.(a)??Promise.resolve([]);countLogs=a=>this.storage.countLogs?.(a)??Promise.resolve(0);deleteLogs=a=>this.storage.deleteLogs?.(a)??Promise.resolve(0);getLogStats=a=>this.storage.getLogStats?.(a)??Promise.resolve({total:0,byAction:{},lastActivity:null})}export{A as DrizzleStore,k as createDrizzleStore};
7
+ */import{eq as g,and as p,arrayContains as w,or as N}from"drizzle-orm";import{g as O,l as I}from"../shared/keypal.DYJcnXna.mjs";import{c as S,D as C}from"../shared/keypal.Brd64SyI.mjs";import"nanoid";const v=f=>{const a={supportsDates:!0,supportsBooleans:!0,usePlural:!1,disableIdGeneration:!1,debugLogs:!1,...f.config},r={apiKeyTable:a.usePlural?"apikeys":"apikey",auditLogTable:a.usePlural?"auditlogs":"auditlog",flattenMetadata:!1,...f.schema},s=(...e)=>{if(!a.debugLogs)return;if(typeof a.debugLogs=="boolean"){I.info(`[${a.adapterName}]`,...e);return}const t=typeof e[0]=="string"?e[0]:null;t&&a.debugLogs[t]&&I.info(`[${a.adapterName}]`,...e)},m=e=>e==="apikey"?r.apiKeyTable:r.auditLogTable,n=(e,t)=>{if(e==="apikey"){const o=r.apiKeyColumns||{};if(o.metadataColumns&&t in o.metadataColumns)return o.metadataColumns[t]||t;if(t==="id")return o.id||"id";if(t==="keyHash")return o.keyHash||"keyHash";if(t==="metadata")return o.metadata||"metadata"}else return(r.auditLogColumns||{})[t]||t;return t},h={config:a,schema:r,debugLog:s,getColumnName:n,getTableName:m,transformApiKeyInput:e=>{const t={};if(r.flattenMetadata){t[n("apikey","id")]=e.id,t[n("apikey","keyHash")]=e.keyHash;for(const[o,i]of Object.entries(e.metadata)){const u=n("apikey",o);i==null?t[u]=null:typeof i=="object"?a.supportsJSON?t[u]=i:t[u]=JSON.stringify(i):i&&typeof i=="object"&&"toISOString"in i?a.supportsDates?t[u]=i:t[u]=i.toISOString():typeof i=="boolean"?a.supportsBooleans?t[u]=i:t[u]=i?1:0:t[u]=i}}else t[n("apikey","id")]=e.id,t[n("apikey","keyHash")]=e.keyHash,a.supportsJSON?t[n("apikey","metadata")]=e.metadata:t[n("apikey","metadata")]=JSON.stringify(e.metadata);return t},transformApiKeyOutput:e=>{const t=String(e[n("apikey","id")]),o=String(e[n("apikey","keyHash")]);let i;if(r.flattenMetadata){i={};const u=["ownerId","name","description","scopes","resources","rateLimit","expiresAt","revokedAt","lastUsedAt","createdAt","tags","allowedIps","allowedOrigins"];for(const d of u){const y=n("apikey",d),c=e[y];c!=null&&(d==="expiresAt"||d==="revokedAt"||d==="lastUsedAt"||d==="createdAt"?typeof c=="string"&&!a.supportsDates?i[d]=new Date(c):i[d]=c:d==="scopes"||d==="resources"||d==="tags"||d==="allowedIps"||d==="allowedOrigins"?typeof c=="string"&&!a.supportsJSON?i[d]=JSON.parse(c):i[d]=c:d==="rateLimit"&&typeof c=="string"&&!a.supportsJSON?i[d]=JSON.parse(c):i[d]=c)}}else{const u=e[n("apikey","metadata")];typeof u=="string"?i=JSON.parse(u):i=u}return{id:t,keyHash:o,metadata:i}},transformAuditLogInput:e=>{const t={};if(t[n("auditlog","id")]=e.id,t[n("auditlog","keyId")]=e.keyId,t[n("auditlog","ownerId")]=e.ownerId,t[n("auditlog","action")]=e.action,t[n("auditlog","timestamp")]=e.timestamp,e.data){const o=n("auditlog","data");a.supportsJSON?t[o]=e.data:t[o]=JSON.stringify(e.data)}return t},transformAuditLogOutput:e=>{const t={id:String(e[n("auditlog","id")]),keyId:String(e[n("auditlog","keyId")]),ownerId:String(e[n("auditlog","ownerId")]),action:e[n("auditlog","action")],timestamp:String(e[n("auditlog","timestamp")])},o=e[n("auditlog","data")];return o&&(typeof o=="string"?t.data=JSON.parse(o):t.data=o),t}},l=f.adapter(h);return{async save(e){s("save","Input:",e),!e.id&&!a.disableIdGeneration&&(e.id=a.customIdGenerator?a.customIdGenerator():O()),await l.save(e),s("save","Saved successfully")},async findByHash(e){s("findByHash","Hash:",e);const t=await l.findByHash(e);return s("findByHash","Result:",t),t},async findById(e){s("findById","ID:",e);const t=await l.findById(e);return s("findById","Result:",t),t},async findByOwner(e){s("findByOwner","Owner ID:",e);const t=await l.findByOwner(e);return s("findByOwner","Found:",t.length,"keys"),t},async findByTags(e,t){s("findByTags","Tags:",e,"Owner ID:",t);const o=await l.findByTags(e,t);return s("findByTags","Found:",o.length,"keys"),o},async findByTag(e,t){s("findByTag","Tag:",e,"Owner ID:",t);const o=await l.findByTag(e,t);return s("findByTag","Found:",o.length,"keys"),o},async updateMetadata(e,t){s("updateMetadata","ID:",e,"Metadata:",t),await l.updateMetadata(e,t),s("updateMetadata","Updated successfully")},async delete(e){s("delete","ID:",e),await l.delete(e),s("delete","Deleted successfully")},async deleteByOwner(e){s("deleteByOwner","Owner ID:",e),await l.deleteByOwner(e),s("deleteByOwner","Deleted successfully")},...l.saveLog&&{async saveLog(e){s("saveLog","Log:",e),await l.saveLog(e),s("saveLog","Saved successfully")}},...l.findLogs&&{async findLogs(e){s("findLogs","Query:",e);const t=await l.findLogs(e);return s("findLogs","Found:",t.length,"logs"),t}},...l.countLogs&&{async countLogs(e){s("countLogs","Query:",e);const t=await l.countLogs(e);return s("countLogs","Count:",t),t}},...l.deleteLogs&&{async deleteLogs(e){s("deleteLogs","Query:",e);const t=await l.deleteLogs(e);return s("deleteLogs","Deleted:",t,"logs"),t}},...l.getLogStats&&{async getLogStats(e){s("getLogStats","Owner ID:",e);const t=await l.getLogStats(e);return s("getLogStats","Stats:",t),t}}}};function b(f){const a=f.constructor?.name||"";return a.includes("Pg")||a.includes("Postgres")?"pg":a.includes("MySQL")||a.includes("Maria")?"mysql":a.includes("SQLite")||a.includes("Sqlite")?"sqlite":(I.warn("[Drizzle Store] Could not detect database provider, defaulting to PostgreSQL"),"pg")}function k(f){const{db:a,table:r,auditLogTable:s}=f,m=f.provider??b(a);return m==="mysql"&&s&&I.warn("[Drizzle Store] MySQL detected: RETURNING clause not supported, using fallback queries"),v({config:{adapterId:"drizzle",adapterName:"Drizzle ORM",supportsJSON:m!=="sqlite",supportsDates:!0,supportsBooleans:!0,debugLogs:f.debugLogs},schema:f.schema,adapter:n=>{const{transformApiKeyInput:h,transformApiKeyOutput:l}=n;return{async save(e){const t=h(e);await a.insert(r).values(t)},async findByHash(e){const t=n.getColumnName("apikey","keyHash"),o=await a.select().from(r).where(g(r[t],e)).limit(1);return o.length>0&&o[0]?l(o[0]):null},async findById(e){const t=n.getColumnName("apikey","id"),o=await a.select().from(r).where(g(r[t],e)).limit(1);return o.length>0&&o[0]?l(o[0]):null},async findByOwner(e){let t;if(n.schema.flattenMetadata){const o=n.getColumnName("apikey","ownerId");t=await a.select().from(r).where(g(r[o],e))}else{const o=n.getColumnName("apikey","metadata");t=await a.select().from(r).where(w(r[o],{ownerId:e}))}return t.map(l)},async findByTags(e,t){const o=[];if(n.schema.flattenMetadata){const i=n.getColumnName("apikey","tags"),u=e.map(d=>d.toLowerCase());for(const d of u)o.push(w(r[i],[d]));if(t!==void 0){const d=n.getColumnName("apikey","ownerId");o.push(g(r[d],t))}}else{const i=n.getColumnName("apikey","metadata");if(e.length>0){const u=e.map(d=>d.toLowerCase()).map(d=>w(r[i],{tags:[d]}));o.push(N(...u))}t!==void 0&&o.push(w(r[i],{ownerId:t}))}return o.length===0?[]:(await a.select().from(r).where(p(...o))).map(l)},async findByTag(e,t){return this.findByTags([e],t)},async updateMetadata(e,t){const o=await this.findById(e);if(!o)throw new Error(`API key with id ${e} not found`);const i={...o.metadata,...t},u={...o,metadata:i},d=h(u),y=n.getColumnName("apikey","id");if(n.schema.flattenMetadata){const c={};for(const[B]of Object.entries(t)){const L=n.getColumnName("apikey",B);c[L]=d[L]}await a.update(r).set(c).where(g(r[y],e))}else{const c=n.getColumnName("apikey","metadata");await a.update(r).set({[c]:i}).where(g(r[y],e))}},async delete(e){const t=n.getColumnName("apikey","id");await a.delete(r).where(g(r[t],e))},async deleteByOwner(e){if(n.schema.flattenMetadata){const t=n.getColumnName("apikey","ownerId");await a.delete(r).where(g(r[t],e))}else{const t=n.getColumnName("apikey","metadata");await a.delete(r).where(w(r[t],{ownerId:e}))}},...s&&n.transformAuditLogInput&&{async saveLog(e){const t=n.transformAuditLogInput(e);await a.insert(s).values(t)},async findLogs(e){const t=[];if(e.keyId){const y=n.getColumnName("auditlog","keyId");t.push(g(s[y],e.keyId))}if(e.ownerId){const y=n.getColumnName("auditlog","ownerId");t.push(g(s[y],e.ownerId))}if(e.action){const y=n.getColumnName("auditlog","action");t.push(g(s[y],e.action))}let o=a.select().from(s);t.length>0&&(o=o.where(p(...t)));const i=e.offset??0,u=e.limit??C,d=n.getColumnName("auditlog","timestamp");return(await o.orderBy(s[d]).limit(u).offset(i)).map(y=>n.transformAuditLogOutput?.(y))},async countLogs(e){const t=[];if(e.keyId){const i=n.getColumnName("auditlog","keyId");t.push(g(s[i],e.keyId))}if(e.ownerId){const i=n.getColumnName("auditlog","ownerId");t.push(g(s[i],e.ownerId))}if(e.action){const i=n.getColumnName("auditlog","action");t.push(g(s[i],e.action))}let o=a.select().from(s);return t.length>0&&(o=o.where(p(...t))),(await o).length},async deleteLogs(e){const t=[];if(e.keyId){const i=n.getColumnName("auditlog","keyId");t.push(g(s[i],e.keyId))}if(e.ownerId){const i=n.getColumnName("auditlog","ownerId");t.push(g(s[i],e.ownerId))}if(e.action){const i=n.getColumnName("auditlog","action");t.push(g(s[i],e.action))}if(t.length===0)return 0;if(m==="mysql"){const i=await a.select().from(s).where(p(...t));return await a.delete(s).where(p(...t)),i.length}const o=await a.delete(s).where(p(...t)).returning();return Array.isArray(o)?o.length:0},async getLogStats(e){const t=n.getColumnName("auditlog","ownerId"),o=(await a.select().from(s).where(g(s[t],e))).map(i=>n.transformAuditLogOutput?.(i));return S(o)}}}}})}class A{storage;constructor(a){this.storage=k(a)}save=a=>this.storage.save(a);findByHash=a=>this.storage.findByHash(a);findById=a=>this.storage.findById(a);findByOwner=a=>this.storage.findByOwner(a);findByTags=(a,r)=>this.storage.findByTags(a,r);findByTag=(a,r)=>this.storage.findByTag(a,r);updateMetadata=(a,r)=>this.storage.updateMetadata(a,r);delete=a=>this.storage.delete(a);deleteByOwner=a=>this.storage.deleteByOwner(a);saveLog=a=>this.storage.saveLog?.(a)??Promise.resolve();findLogs=a=>this.storage.findLogs?.(a)??Promise.resolve([]);countLogs=a=>this.storage.countLogs?.(a)??Promise.resolve(0);deleteLogs=a=>this.storage.deleteLogs?.(a)??Promise.resolve(0);getLogStats=a=>this.storage.getLogStats?.(a)??Promise.resolve({total:0,byAction:{},lastActivity:null})}export{A as DrizzleStore,k as createDrizzleStore};
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * @donotlb/keypal v0.1.0
2
+ * @donotlb/keypal v0.1.1
3
3
  * A TypeScript library for secure API key management with cryptographic hashing, expiration, scopes, and pluggable storage
4
4
  * © 2026 "donotlb" <donotlb@gmail.com>
5
5
  * Released under the MIT License
6
6
  * https://github.com/donotlb/keypal#readme
7
- */import{D as h,c}from"../shared/keypal.C-UeOmUF.mjs";class f{keys=new Map;hashIndex=new Map;ownerIndex=new Map;tagIndex=new Map;logs=new Map;async save(t){if(this.keys.has(t.id))throw new Error(`API key with id ${t.id} already exists`);if(this.hashIndex.has(t.keyHash))throw new Error("API key hash collision detected");this.keys.set(t.id,t),this.hashIndex.set(t.keyHash,t.id);const{ownerId:e,tags:s}=t.metadata;if(e){const n=this.ownerIndex.get(e)??new Set;n.add(t.id),this.ownerIndex.set(e,n)}if(s)for(const n of s){const o=this.tagIndex.get(n)??new Set;o.add(t.id),this.tagIndex.set(n,o)}}async findByHash(t){const e=this.hashIndex.get(t);return e?this.keys.get(e)??null:null}async findById(t){return this.keys.get(t)??null}async findByOwner(t){const e=this.ownerIndex.get(t);if(!e?.size)return[];const s=[];for(const n of e){const o=this.keys.get(n);o&&s.push(o)}return s}async findByTags(t,e){const s=t.map(a=>a.toLowerCase()),n=new Set;for(const a of s){const i=this.tagIndex.get(a);if(i)for(const d of i)n.add(d)}const o=[];for(const a of n){const i=this.keys.get(a);i&&(e===void 0||i.metadata.ownerId===e)&&o.push(i)}return o}async findByTag(t,e){const s=t.toLowerCase(),n=this.tagIndex.get(s);if(!n?.size)return[];const o=[];for(const a of n){const i=this.keys.get(a);i&&(e===void 0||i.metadata.ownerId===e)&&o.push(i)}return o}async updateMetadata(t,e){const s=this.keys.get(t);if(!s)throw new Error(`API key with id ${t} not found`);const n=s.metadata.ownerId,o=s.metadata.tags;s.metadata={...s.metadata,...e};const a=s.metadata.ownerId,i=s.metadata.tags;if(n!==a){if(n){const d=this.ownerIndex.get(n);d?.delete(t),d?.size===0&&this.ownerIndex.delete(n)}if(a){const d=this.ownerIndex.get(a)??new Set;d.add(t),this.ownerIndex.set(a,d)}}if(e.tags!==void 0){if(o)for(const d of o){const r=this.tagIndex.get(d);r?.delete(t),r?.size===0&&this.tagIndex.delete(d)}if(i)for(const d of i){const r=this.tagIndex.get(d)??new Set;r.add(t),this.tagIndex.set(d,r)}}}async delete(t){const e=this.keys.get(t);if(e){this.hashIndex.delete(e.keyHash);const{ownerId:s,tags:n}=e.metadata;if(s){const o=this.ownerIndex.get(s);o?.delete(t),o?.size===0&&this.ownerIndex.delete(s)}if(n)for(const o of n){const a=this.tagIndex.get(o);a?.delete(t),a?.size===0&&this.tagIndex.delete(o)}}this.keys.delete(t)}async deleteByOwner(t){const e=this.ownerIndex.get(t);if(e){for(const s of e){const n=this.keys.get(s);if(n){if(this.hashIndex.delete(n.keyHash),n.metadata.tags)for(const o of n.metadata.tags){const a=this.tagIndex.get(o);a?.delete(s),a?.size===0&&this.tagIndex.delete(o)}this.keys.delete(s)}}this.ownerIndex.delete(t)}}async saveLog(t){this.logs.set(t.id,t)}async findLogs(t){const e=this.filterLogs(t);e.sort((o,a)=>a.timestamp.localeCompare(o.timestamp));const s=t.offset??0,n=t.limit??h;return e.slice(s,s+n)}async countLogs(t){return this.filterLogs(t).length}async deleteLogs(t){const e=this.filterLogs(t);for(const s of e)this.logs.delete(s.id);return e.length}async getLogStats(t){const e=Array.from(this.logs.values()).filter(s=>s.ownerId===t);return c(e)}filterLogs(t){let e=Array.from(this.logs.values());return t.keyId&&(e=e.filter(s=>s.keyId===t.keyId)),t.ownerId&&(e=e.filter(s=>s.ownerId===t.ownerId)),t.action&&(e=e.filter(s=>s.action===t.action)),t.startDate&&(e=e.filter(s=>s.timestamp>=t.startDate)),t.endDate&&(e=e.filter(s=>s.timestamp<=t.endDate)),e}}export{f as MemoryStore};
7
+ */import{D as h,c}from"../shared/keypal.Brd64SyI.mjs";class f{keys=new Map;hashIndex=new Map;ownerIndex=new Map;tagIndex=new Map;logs=new Map;async save(t){if(this.keys.has(t.id))throw new Error(`API key with id ${t.id} already exists`);if(this.hashIndex.has(t.keyHash))throw new Error("API key hash collision detected");this.keys.set(t.id,t),this.hashIndex.set(t.keyHash,t.id);const{ownerId:e,tags:s}=t.metadata;if(e){const n=this.ownerIndex.get(e)??new Set;n.add(t.id),this.ownerIndex.set(e,n)}if(s)for(const n of s){const o=this.tagIndex.get(n)??new Set;o.add(t.id),this.tagIndex.set(n,o)}}async findByHash(t){const e=this.hashIndex.get(t);return e?this.keys.get(e)??null:null}async findById(t){return this.keys.get(t)??null}async findByOwner(t){const e=this.ownerIndex.get(t);if(!e?.size)return[];const s=[];for(const n of e){const o=this.keys.get(n);o&&s.push(o)}return s}async findByTags(t,e){const s=t.map(a=>a.toLowerCase()),n=new Set;for(const a of s){const i=this.tagIndex.get(a);if(i)for(const d of i)n.add(d)}const o=[];for(const a of n){const i=this.keys.get(a);i&&(e===void 0||i.metadata.ownerId===e)&&o.push(i)}return o}async findByTag(t,e){const s=t.toLowerCase(),n=this.tagIndex.get(s);if(!n?.size)return[];const o=[];for(const a of n){const i=this.keys.get(a);i&&(e===void 0||i.metadata.ownerId===e)&&o.push(i)}return o}async updateMetadata(t,e){const s=this.keys.get(t);if(!s)throw new Error(`API key with id ${t} not found`);const n=s.metadata.ownerId,o=s.metadata.tags;s.metadata={...s.metadata,...e};const a=s.metadata.ownerId,i=s.metadata.tags;if(n!==a){if(n){const d=this.ownerIndex.get(n);d?.delete(t),d?.size===0&&this.ownerIndex.delete(n)}if(a){const d=this.ownerIndex.get(a)??new Set;d.add(t),this.ownerIndex.set(a,d)}}if(e.tags!==void 0){if(o)for(const d of o){const r=this.tagIndex.get(d);r?.delete(t),r?.size===0&&this.tagIndex.delete(d)}if(i)for(const d of i){const r=this.tagIndex.get(d)??new Set;r.add(t),this.tagIndex.set(d,r)}}}async delete(t){const e=this.keys.get(t);if(e){this.hashIndex.delete(e.keyHash);const{ownerId:s,tags:n}=e.metadata;if(s){const o=this.ownerIndex.get(s);o?.delete(t),o?.size===0&&this.ownerIndex.delete(s)}if(n)for(const o of n){const a=this.tagIndex.get(o);a?.delete(t),a?.size===0&&this.tagIndex.delete(o)}}this.keys.delete(t)}async deleteByOwner(t){const e=this.ownerIndex.get(t);if(e){for(const s of e){const n=this.keys.get(s);if(n){if(this.hashIndex.delete(n.keyHash),n.metadata.tags)for(const o of n.metadata.tags){const a=this.tagIndex.get(o);a?.delete(s),a?.size===0&&this.tagIndex.delete(o)}this.keys.delete(s)}}this.ownerIndex.delete(t)}}async saveLog(t){this.logs.set(t.id,t)}async findLogs(t){const e=this.filterLogs(t);e.sort((o,a)=>a.timestamp.localeCompare(o.timestamp));const s=t.offset??0,n=t.limit??h;return e.slice(s,s+n)}async countLogs(t){return this.filterLogs(t).length}async deleteLogs(t){const e=this.filterLogs(t);for(const s of e)this.logs.delete(s.id);return e.length}async getLogStats(t){const e=Array.from(this.logs.values()).filter(s=>s.ownerId===t);return c(e)}filterLogs(t){let e=Array.from(this.logs.values());return t.keyId&&(e=e.filter(s=>s.keyId===t.keyId)),t.ownerId&&(e=e.filter(s=>s.ownerId===t.ownerId)),t.action&&(e=e.filter(s=>s.action===t.action)),t.startDate&&(e=e.filter(s=>s.timestamp>=t.startDate)),t.endDate&&(e=e.filter(s=>s.timestamp<=t.endDate)),e}}export{f as MemoryStore};
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * @donotlb/keypal v0.1.0
2
+ * @donotlb/keypal v0.1.1
3
3
  * A TypeScript library for secure API key management with cryptographic hashing, expiration, scopes, and pluggable storage
4
4
  * © 2026 "donotlb" <donotlb@gmail.com>
5
5
  * Released under the MIT License
6
6
  * https://github.com/donotlb/keypal#readme
7
- */import{D as w,c as u}from"../shared/keypal.C-UeOmUF.mjs";function p(h){if(typeof h!="object"||h===null)return!1;const t=h;return typeof t.id=="string"&&typeof t.keyHash=="string"&&typeof t.metadata=="object"&&t.metadata!==null}function I(h){if(typeof h!="object"||h===null)return!1;const t=h;return typeof t.id=="string"&&typeof t.keyId=="string"&&typeof t.ownerId=="string"&&typeof t.action=="string"&&typeof t.timestamp=="string"}async function y(h,t){const e=await h.exec();if(!e)throw new Error(`Redis pipeline returned null for ${t}`);for(let s=0;s<e.length;s++){const i=e[s];if(i?.[0])throw new Error(`Redis pipeline command ${s} failed in ${t}: ${i[0].message}`)}return e}function l(h){try{const t=JSON.parse(h);return p(t)?t:null}catch{return null}}function m(h){try{const t=JSON.parse(h);return I(t)?t:null}catch{return null}}class x{redis;prefix;constructor(t){this.redis=t.client,this.prefix=t.prefix??"apikey:"}key(t){return`${this.prefix}${t}`}tagKey(t){return`${this.prefix}tag:${t}`}hashKey(t){return`${this.prefix}hash:${t}`}ownerKey(t){return`${this.prefix}owner:${t}`}logKey(t){return`${this.prefix}log:${t}`}logsByKeyIndex(t){return`${this.prefix}logs:key:${t}`}logsByOwnerIndex(t){return`${this.prefix}logs:owner:${t}`}logsByActionIndex(t){return`${this.prefix}logs:action:${t}`}allLogsIndex(){return`${this.prefix}logs:all`}async save(t){if(await this.findById(t.id))throw new Error(`API key with id ${t.id} already exists`);const e=this.redis.pipeline();if(e.set(this.key(t.id),JSON.stringify(t)),e.set(this.hashKey(t.keyHash),t.id),e.sadd(this.ownerKey(t.metadata.ownerId),t.id),t.metadata.tags?.length)for(const s of t.metadata.tags)e.sadd(this.tagKey(s.toLowerCase()),t.id);await y(e,"save")}async findByHash(t){const e=await this.redis.get(this.hashKey(t));return e?this.findById(e):null}async findById(t){const e=await this.redis.get(this.key(t));return e?l(e):null}async findByOwner(t){const e=await this.redis.smembers(this.ownerKey(t));if(!e.length)return[];const s=this.redis.pipeline();for(const n of e)s.get(this.key(n));const i=await y(s,"findByOwner"),o=[];for(const n of i)if(n[1]){const d=l(n[1]);d&&o.push(d)}return o}async findByTags(t,e){const s=t.map(r=>this.tagKey(r.toLowerCase()));if(!s.length)return[];let i=s.length===1&&s[0]?await this.redis.smembers(s[0]):await this.redis.sunion(...s);if(e!==void 0&&i.length){const r=await this.redis.smembers(this.ownerKey(e));i=i.filter(a=>r.includes(a))}if(!i.length)return[];const o=this.redis.pipeline();for(const r of i)o.get(this.key(r));const n=await y(o,"findByTags"),d=[];for(const r of n)if(r[1]){const a=l(r[1]);a&&d.push(a)}return d}async findByTag(t,e){return this.findByTags([t],e)}async updateMetadata(t,e){const s=await this.findById(t);if(!s)throw new Error(`API key with id ${t} not found`);const i=s.metadata.tags??[];s.metadata={...s.metadata,...e};const o=s.metadata.tags??[],n=this.redis.pipeline();if(n.set(this.key(t),JSON.stringify(s)),e.revokedAt&&n.del(this.hashKey(s.keyHash)),e.tags!==void 0){const d=new Set(i.map(a=>a.toLowerCase())),r=new Set(o.map(a=>a.toLowerCase()));for(const a of d)r.has(a)||n.srem(this.tagKey(a),t);for(const a of r)d.has(a)||n.sadd(this.tagKey(a),t)}await y(n,"updateMetadata")}async delete(t){const e=await this.findById(t);if(!e)return;const s=this.redis.pipeline();if(s.del(this.key(t)),s.del(this.hashKey(e.keyHash)),s.srem(this.ownerKey(e.metadata.ownerId),t),e.metadata.tags?.length)for(const i of e.metadata.tags)s.srem(this.tagKey(i.toLowerCase()),t);await y(s,"delete")}async deleteByOwner(t){const e=await this.redis.smembers(this.ownerKey(t));if(!e.length)return;const s=this.redis.pipeline();for(const n of e)s.get(this.key(n));const i=await y(s,"deleteByOwner:fetch"),o=this.redis.pipeline();for(let n=0;n<e.length;n++){const d=e[n];if(!d)continue;o.del(this.key(d));const r=i[n];if(r?.[1]){const a=l(r[1]);if(a&&(o.del(this.hashKey(a.keyHash)),a.metadata.tags?.length))for(const f of a.metadata.tags)o.srem(this.tagKey(f.toLowerCase()),d)}}o.del(this.ownerKey(t)),await y(o,"deleteByOwner:delete")}async setTtl(t,e){const s=await this.findById(t);if(!s)return;const i=this.redis.pipeline();i.expire(this.key(t),e),i.expire(this.hashKey(s.keyHash),e),await y(i,"setTtl")}async saveLog(t){const e=new Date(t.timestamp).getTime(),s=this.redis.pipeline();s.set(this.logKey(t.id),JSON.stringify(t)),s.zadd(this.allLogsIndex(),e,t.id),s.zadd(this.logsByKeyIndex(t.keyId),e,t.id),s.zadd(this.logsByOwnerIndex(t.ownerId),e,t.id),s.zadd(this.logsByActionIndex(t.action),e,t.id),await y(s,"saveLog")}async findLogs(t){const e=t.offset??0,s=t.limit??w;let i;t.keyId?i=this.logsByKeyIndex(t.keyId):t.ownerId?i=this.logsByOwnerIndex(t.ownerId):t.action?i=this.logsByActionIndex(t.action):i=this.allLogsIndex();const o=t.startDate?new Date(t.startDate).getTime():"-inf",n=t.endDate?new Date(t.endDate).getTime():"+inf",d=await this.redis.zrevrangebyscore(i,n,o,"LIMIT",e,s);if(!d.length)return[];const r=this.redis.pipeline();for(const c of d)r.get(this.logKey(c));const a=await y(r,"findLogs"),f=[];for(const c of a)if(c[1]){const g=m(c[1]);g&&this.matchesQuery(g,t)&&f.push(g)}return f}async countLogs(t){let e;t.keyId?e=this.logsByKeyIndex(t.keyId):t.ownerId?e=this.logsByOwnerIndex(t.ownerId):t.action?e=this.logsByActionIndex(t.action):e=this.allLogsIndex();const s=t.startDate?new Date(t.startDate).getTime():"-inf",i=t.endDate?new Date(t.endDate).getTime():"+inf";return[t.keyId,t.ownerId,t.action].filter(Boolean).length>1?(await this.findLogs({...t,limit:Number.MAX_SAFE_INTEGER,offset:0})).length:this.redis.zcount(e,s,i)}async deleteLogs(t){const e=await this.findLogs({...t,limit:Number.MAX_SAFE_INTEGER,offset:0});if(!e.length)return 0;const s=this.redis.pipeline();for(const i of e)s.del(this.logKey(i.id)),s.zrem(this.allLogsIndex(),i.id),s.zrem(this.logsByKeyIndex(i.keyId),i.id),s.zrem(this.logsByOwnerIndex(i.ownerId),i.id),s.zrem(this.logsByActionIndex(i.action),i.id);return await y(s,"deleteLogs"),e.length}async getLogStats(t){const e=await this.findLogs({ownerId:t,limit:Number.MAX_SAFE_INTEGER});return u(e)}matchesQuery(t,e){return!(e.keyId&&t.keyId!==e.keyId||e.ownerId&&t.ownerId!==e.ownerId||e.action&&t.action!==e.action||e.startDate&&t.timestamp<e.startDate||e.endDate&&t.timestamp>e.endDate)}}export{x as RedisStore};
7
+ */import{D as w,c as u}from"../shared/keypal.Brd64SyI.mjs";function p(h){if(typeof h!="object"||h===null)return!1;const t=h;return typeof t.id=="string"&&typeof t.keyHash=="string"&&typeof t.metadata=="object"&&t.metadata!==null}function I(h){if(typeof h!="object"||h===null)return!1;const t=h;return typeof t.id=="string"&&typeof t.keyId=="string"&&typeof t.ownerId=="string"&&typeof t.action=="string"&&typeof t.timestamp=="string"}async function y(h,t){const e=await h.exec();if(!e)throw new Error(`Redis pipeline returned null for ${t}`);for(let s=0;s<e.length;s++){const i=e[s];if(i?.[0])throw new Error(`Redis pipeline command ${s} failed in ${t}: ${i[0].message}`)}return e}function l(h){try{const t=JSON.parse(h);return p(t)?t:null}catch{return null}}function m(h){try{const t=JSON.parse(h);return I(t)?t:null}catch{return null}}class x{redis;prefix;constructor(t){this.redis=t.client,this.prefix=t.prefix??"apikey:"}key(t){return`${this.prefix}${t}`}tagKey(t){return`${this.prefix}tag:${t}`}hashKey(t){return`${this.prefix}hash:${t}`}ownerKey(t){return`${this.prefix}owner:${t}`}logKey(t){return`${this.prefix}log:${t}`}logsByKeyIndex(t){return`${this.prefix}logs:key:${t}`}logsByOwnerIndex(t){return`${this.prefix}logs:owner:${t}`}logsByActionIndex(t){return`${this.prefix}logs:action:${t}`}allLogsIndex(){return`${this.prefix}logs:all`}async save(t){if(await this.findById(t.id))throw new Error(`API key with id ${t.id} already exists`);const e=this.redis.pipeline();if(e.set(this.key(t.id),JSON.stringify(t)),e.set(this.hashKey(t.keyHash),t.id),e.sadd(this.ownerKey(t.metadata.ownerId),t.id),t.metadata.tags?.length)for(const s of t.metadata.tags)e.sadd(this.tagKey(s.toLowerCase()),t.id);await y(e,"save")}async findByHash(t){const e=await this.redis.get(this.hashKey(t));return e?this.findById(e):null}async findById(t){const e=await this.redis.get(this.key(t));return e?l(e):null}async findByOwner(t){const e=await this.redis.smembers(this.ownerKey(t));if(!e.length)return[];const s=this.redis.pipeline();for(const n of e)s.get(this.key(n));const i=await y(s,"findByOwner"),o=[];for(const n of i)if(n[1]){const d=l(n[1]);d&&o.push(d)}return o}async findByTags(t,e){const s=t.map(r=>this.tagKey(r.toLowerCase()));if(!s.length)return[];let i=s.length===1&&s[0]?await this.redis.smembers(s[0]):await this.redis.sunion(...s);if(e!==void 0&&i.length){const r=await this.redis.smembers(this.ownerKey(e));i=i.filter(a=>r.includes(a))}if(!i.length)return[];const o=this.redis.pipeline();for(const r of i)o.get(this.key(r));const n=await y(o,"findByTags"),d=[];for(const r of n)if(r[1]){const a=l(r[1]);a&&d.push(a)}return d}async findByTag(t,e){return this.findByTags([t],e)}async updateMetadata(t,e){const s=await this.findById(t);if(!s)throw new Error(`API key with id ${t} not found`);const i=s.metadata.tags??[];s.metadata={...s.metadata,...e};const o=s.metadata.tags??[],n=this.redis.pipeline();if(n.set(this.key(t),JSON.stringify(s)),e.revokedAt&&n.del(this.hashKey(s.keyHash)),e.tags!==void 0){const d=new Set(i.map(a=>a.toLowerCase())),r=new Set(o.map(a=>a.toLowerCase()));for(const a of d)r.has(a)||n.srem(this.tagKey(a),t);for(const a of r)d.has(a)||n.sadd(this.tagKey(a),t)}await y(n,"updateMetadata")}async delete(t){const e=await this.findById(t);if(!e)return;const s=this.redis.pipeline();if(s.del(this.key(t)),s.del(this.hashKey(e.keyHash)),s.srem(this.ownerKey(e.metadata.ownerId),t),e.metadata.tags?.length)for(const i of e.metadata.tags)s.srem(this.tagKey(i.toLowerCase()),t);await y(s,"delete")}async deleteByOwner(t){const e=await this.redis.smembers(this.ownerKey(t));if(!e.length)return;const s=this.redis.pipeline();for(const n of e)s.get(this.key(n));const i=await y(s,"deleteByOwner:fetch"),o=this.redis.pipeline();for(let n=0;n<e.length;n++){const d=e[n];if(!d)continue;o.del(this.key(d));const r=i[n];if(r?.[1]){const a=l(r[1]);if(a&&(o.del(this.hashKey(a.keyHash)),a.metadata.tags?.length))for(const f of a.metadata.tags)o.srem(this.tagKey(f.toLowerCase()),d)}}o.del(this.ownerKey(t)),await y(o,"deleteByOwner:delete")}async setTtl(t,e){const s=await this.findById(t);if(!s)return;const i=this.redis.pipeline();i.expire(this.key(t),e),i.expire(this.hashKey(s.keyHash),e),await y(i,"setTtl")}async saveLog(t){const e=new Date(t.timestamp).getTime(),s=this.redis.pipeline();s.set(this.logKey(t.id),JSON.stringify(t)),s.zadd(this.allLogsIndex(),e,t.id),s.zadd(this.logsByKeyIndex(t.keyId),e,t.id),s.zadd(this.logsByOwnerIndex(t.ownerId),e,t.id),s.zadd(this.logsByActionIndex(t.action),e,t.id),await y(s,"saveLog")}async findLogs(t){const e=t.offset??0,s=t.limit??w;let i;t.keyId?i=this.logsByKeyIndex(t.keyId):t.ownerId?i=this.logsByOwnerIndex(t.ownerId):t.action?i=this.logsByActionIndex(t.action):i=this.allLogsIndex();const o=t.startDate?new Date(t.startDate).getTime():"-inf",n=t.endDate?new Date(t.endDate).getTime():"+inf",d=await this.redis.zrevrangebyscore(i,n,o,"LIMIT",e,s);if(!d.length)return[];const r=this.redis.pipeline();for(const c of d)r.get(this.logKey(c));const a=await y(r,"findLogs"),f=[];for(const c of a)if(c[1]){const g=m(c[1]);g&&this.matchesQuery(g,t)&&f.push(g)}return f}async countLogs(t){let e;t.keyId?e=this.logsByKeyIndex(t.keyId):t.ownerId?e=this.logsByOwnerIndex(t.ownerId):t.action?e=this.logsByActionIndex(t.action):e=this.allLogsIndex();const s=t.startDate?new Date(t.startDate).getTime():"-inf",i=t.endDate?new Date(t.endDate).getTime():"+inf";return[t.keyId,t.ownerId,t.action].filter(Boolean).length>1?(await this.findLogs({...t,limit:Number.MAX_SAFE_INTEGER,offset:0})).length:this.redis.zcount(e,s,i)}async deleteLogs(t){const e=await this.findLogs({...t,limit:Number.MAX_SAFE_INTEGER,offset:0});if(!e.length)return 0;const s=this.redis.pipeline();for(const i of e)s.del(this.logKey(i.id)),s.zrem(this.allLogsIndex(),i.id),s.zrem(this.logsByKeyIndex(i.keyId),i.id),s.zrem(this.logsByOwnerIndex(i.ownerId),i.id),s.zrem(this.logsByActionIndex(i.action),i.id);return await y(s,"deleteLogs"),e.length}async getLogStats(t){const e=await this.findLogs({ownerId:t,limit:Number.MAX_SAFE_INTEGER});return u(e)}matchesQuery(t,e){return!(e.keyId&&t.keyId!==e.keyId||e.ownerId&&t.ownerId!==e.ownerId||e.action&&t.action!==e.action||e.startDate&&t.timestamp<e.startDate||e.endDate&&t.timestamp>e.endDate)}}export{x as RedisStore};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donotlb/keypal",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A TypeScript library for secure API key management with cryptographic hashing, expiration, scopes, and pluggable storage",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -107,8 +107,12 @@
107
107
  "ioredis": "^5.8.2"
108
108
  },
109
109
  "peerDependenciesMeta": {
110
- "drizzle-orm": { "optional": true },
111
- "ioredis": { "optional": true }
110
+ "drizzle-orm": {
111
+ "optional": true
112
+ },
113
+ "ioredis": {
114
+ "optional": true
115
+ }
112
116
  },
113
117
  "dependencies": {
114
118
  "nanoid": "^5.1.6",