11/** biome-ignore-all lint/suspicious/noConsole: benchmarking */
22/** biome-ignore-all lint/style/noMagicNumbers: benchmarking */
33import chalk from "chalk" ;
4+ import { nanoid } from "nanoid" ;
45import { MemoryCache } from "./src/core/cache" ;
56import { getExpirationTime , isExpired } from "./src/core/expiration" ;
67import { extractKeyFromHeaders , hasApiKey } from "./src/core/extract-key" ;
@@ -16,6 +17,9 @@ import {
1617 hasScopeWithResources ,
1718} from "./src/core/scopes" ;
1819import { validateKey } from "./src/core/validate" ;
20+ import { MemoryStore } from "./src/storage/memory" ;
21+ import type { ApiKeyRecord } from "./src/types/api-key-types" ;
22+ import type { AuditLog } from "./src/types/audit-log-types" ;
1923import type { PermissionScope } from "./src/types/permissions-types" ;
2024
2125type BenchResult = {
@@ -75,6 +79,30 @@ function bench(
7579 return { name, opsPerSec, avgNs } ;
7680}
7781
82+ async function benchAsync (
83+ name : string ,
84+ fn : ( ) => Promise < unknown > ,
85+ iterations = 10_000
86+ ) : Promise < BenchResult > {
87+ // Warmup
88+ for ( let i = 0 ; i < 100 ; i ++ ) {
89+ await fn ( ) ;
90+ }
91+
92+ const times : number [ ] = [ ] ;
93+ for ( let i = 0 ; i < iterations ; i ++ ) {
94+ const start = performance . now ( ) ;
95+ await fn ( ) ;
96+ const end = performance . now ( ) ;
97+ times . push ( ( end - start ) * 1_000_000 ) ; // to ns
98+ }
99+
100+ const avgNs = times . reduce ( ( a , b ) => a + b , 0 ) / iterations ;
101+ const opsPerSec = 1_000_000_000 / avgNs ;
102+
103+ return { name, opsPerSec, avgNs } ;
104+ }
105+
78106function printGroup ( group : BenchGroup ) {
79107 console . log ( `\n${ group . icon } ${ chalk . bold . white ( group . title ) } ` ) ;
80108 console . log ( chalk . dim ( "─" . repeat ( 80 ) ) ) ;
@@ -86,10 +114,12 @@ function printGroup(group: BenchGroup) {
86114 }
87115}
88116
89- function main ( ) {
117+ async function main ( ) {
90118 console . log ( chalk . bold . magenta ( "\n⚡ Core Performance Benchmarks\n" ) ) ;
91119
92- // Setup
120+ // ============================================================
121+ // Setup test data
122+ // ============================================================
93123 const key = "sk_test_abcdef1234567890ABCDEF1234567890" ;
94124 const keyHash = hashKey ( key ) ;
95125 const scopes : PermissionScope [ ] = [ "read" , "write" , "delete" ] ;
@@ -108,6 +138,51 @@ function main() {
108138 const cache = new MemoryCache ( ) ;
109139 cache . set ( "cached-key" , "value" , 60 ) ;
110140
141+ // Setup storage with test data for tag/delete benchmarks
142+ const storage = new MemoryStore ( ) ;
143+ const testRecords : ApiKeyRecord [ ] = [ ] ;
144+
145+ console . log ( chalk . dim ( "Setting up test data..." ) ) ;
146+
147+ for ( let i = 0 ; i < 100 ; i ++ ) {
148+ const record : ApiKeyRecord = {
149+ id : nanoid ( ) ,
150+ keyHash : hashKey ( `sk_test_${ i } _${ nanoid ( ) } ` ) ,
151+ metadata : {
152+ ownerId : `user_${ i % 10 } ` ,
153+ name : `Test Key ${ i } ` ,
154+ scopes : [ "read" , "write" ] ,
155+ tags : [ `env:${ i % 2 === 0 ? "prod" : "dev" } ` , `team:${ i % 5 } ` ] ,
156+ createdAt : new Date ( ) . toISOString ( ) ,
157+ expiresAt : null ,
158+ revokedAt : null ,
159+ enabled : true ,
160+ rotatedTo : null ,
161+ } ,
162+ } ;
163+ await storage . save ( record ) ;
164+ testRecords . push ( record ) ;
165+ }
166+
167+ // Create audit logs for benchmarking
168+ const auditLogs : AuditLog [ ] = [ ] ;
169+ for ( let i = 0 ; i < 500 ; i ++ ) {
170+ const log : AuditLog = {
171+ id : nanoid ( ) ,
172+ action : [ "created" , "revoked" , "rotated" , "enabled" , "disabled" ] [
173+ i % 5
174+ ] as AuditLog [ "action" ] ,
175+ keyId : testRecords [ i % 100 ] ?. id ?? nanoid ( ) ,
176+ ownerId : `user_${ i % 10 } ` ,
177+ timestamp : new Date ( Date . now ( ) - i * 60000 ) . toISOString ( ) ,
178+ data : { ip : "127.0.0.1" , userAgent : "benchmark" } ,
179+ } ;
180+ await storage . saveLog ( log ) ;
181+ auditLogs . push ( log ) ;
182+ }
183+
184+ console . log ( chalk . dim ( "Setup complete.\n" ) ) ;
185+
111186 const groups : BenchGroup [ ] = [
112187 {
113188 title : "Key Generation" ,
@@ -271,12 +346,115 @@ function main() {
271346 } ,
272347 ] ;
273348
349+ // Async benchmarks need separate handling
350+ const asyncGroups : BenchGroup [ ] = [ ] ;
351+
352+ // Storage: Tag-based Lookups
353+ const tagResults : BenchResult [ ] = [ ] ;
354+ tagResults . push ( await benchAsync ( "findByTag (single)" , ( ) => storage . findByTag ( "env:prod" ) ) ) ;
355+ tagResults . push ( await benchAsync ( "findByTag (with owner)" , ( ) => storage . findByTag ( "env:prod" , "user_0" ) ) ) ;
356+ tagResults . push ( await benchAsync ( "findByTags (multiple)" , ( ) => storage . findByTags ( [ "env:prod" , "team:1" ] ) ) ) ;
357+ tagResults . push ( await benchAsync ( "findByTags (with owner)" , ( ) => storage . findByTags ( [ "env:prod" , "team:1" ] , "user_0" ) ) ) ;
358+ asyncGroups . push ( { title : "Tag-based Lookups" , icon : "🏷️" , results : tagResults } ) ;
359+
360+ // Storage: Core Operations
361+ const storageResults : BenchResult [ ] = [ ] ;
362+ storageResults . push ( await benchAsync ( "findById" , ( ) => storage . findById ( testRecords [ 0 ] ?. id ?? nanoid ( ) ) ) ) ;
363+ storageResults . push ( await benchAsync ( "findByHash" , ( ) => storage . findByHash ( testRecords [ 0 ] ?. keyHash ?? nanoid ( ) ) ) ) ;
364+ storageResults . push ( await benchAsync ( "findByOwner" , ( ) => storage . findByOwner ( "user_0" ) ) ) ;
365+ storageResults . push ( await benchAsync ( "updateMetadata" , ( ) => storage . updateMetadata ( testRecords [ 0 ] ?. id ?? nanoid ( ) , { lastUsedAt : new Date ( ) . toISOString ( ) } ) ) ) ;
366+ asyncGroups . push ( { title : "Storage Core Operations" , icon : "📦" , results : storageResults } ) ;
367+
368+ // Storage: Delete Operations
369+ const deleteResults : BenchResult [ ] = [ ] ;
370+ let deleteCounter = 0 ;
371+ deleteResults . push ( await benchAsync ( "delete (single)" , async ( ) => {
372+ const record : ApiKeyRecord = {
373+ id : `delete_${ deleteCounter ++ } ` ,
374+ keyHash : hashKey ( `delete_key_${ deleteCounter } ` ) ,
375+ metadata : {
376+ ownerId : "delete_user" ,
377+ name : "Delete Test" ,
378+ createdAt : new Date ( ) . toISOString ( ) ,
379+ expiresAt : null ,
380+ revokedAt : null ,
381+ enabled : true ,
382+ rotatedTo : null ,
383+ } ,
384+ } ;
385+ await storage . save ( record ) ;
386+ await storage . delete ( record . id ) ;
387+ } , 10_000 ) ) ;
388+ asyncGroups . push ( { title : "Storage Delete Operations" , icon : "🗑️" , results : deleteResults } ) ;
389+
390+ // Audit Log Operations
391+ const auditResults : BenchResult [ ] = [ ] ;
392+ auditResults . push ( await benchAsync ( "saveLog" , async ( ) => {
393+ const log : AuditLog = {
394+ id : nanoid ( ) ,
395+ action : "created" ,
396+ keyId : testRecords [ 0 ] ?. id ?? nanoid ( ) ,
397+ ownerId : "user_0" ,
398+ timestamp : new Date ( ) . toISOString ( ) ,
399+ } ;
400+ await storage . saveLog ( log ) ;
401+ } , 10_000 ) ) ;
402+ auditResults . push ( await benchAsync ( "findLogs (no filter)" , ( ) => storage . findLogs ( { } ) ) ) ;
403+ auditResults . push ( await benchAsync ( "findLogs (by keyId)" , ( ) => storage . findLogs ( { keyId : testRecords [ 0 ] ?. id ?? nanoid ( ) } ) ) ) ;
404+ auditResults . push ( await benchAsync ( "findLogs (by ownerId)" , ( ) => storage . findLogs ( { ownerId : "user_0" } ) ) ) ;
405+ auditResults . push ( await benchAsync ( "findLogs (by action)" , ( ) => storage . findLogs ( { action : "created" } ) ) ) ;
406+ auditResults . push ( await benchAsync ( "findLogs (date range)" , ( ) => storage . findLogs ( {
407+ startDate : new Date ( Date . now ( ) - 3600000 ) . toISOString ( ) ,
408+ endDate : new Date ( ) . toISOString ( ) ,
409+ } ) ) ) ;
410+ auditResults . push ( await benchAsync ( "countLogs" , ( ) => storage . countLogs ( { ownerId : "user_0" } ) ) ) ;
411+ auditResults . push ( await benchAsync ( "getLogStats" , ( ) => storage . getLogStats ( "user_0" ) ) ) ;
412+ asyncGroups . push ( { title : "Audit Log Operations" , icon : "📋" , results : auditResults } ) ;
413+
414+ // Concurrency Tests
415+ const concurrencyResults : BenchResult [ ] = [ ] ;
416+ concurrencyResults . push ( await benchAsync ( "10 concurrent findById" , async ( ) => {
417+ const ids = testRecords . slice ( 0 , 10 ) . map ( r => r . id ) ;
418+ await Promise . all ( ids . map ( id => storage . findById ( id ) ) ) ;
419+ } , 5_000 ) ) ;
420+ concurrencyResults . push ( await benchAsync ( "50 concurrent findById" , async ( ) => {
421+ const ids = testRecords . slice ( 0 , 50 ) . map ( r => r . id ) ;
422+ await Promise . all ( ids . map ( id => storage . findById ( id ) ) ) ;
423+ } , 1_000 ) ) ;
424+ concurrencyResults . push ( await benchAsync ( "100 concurrent findById" , async ( ) => {
425+ await Promise . all ( testRecords . map ( r => storage . findById ( r . id ) ) ) ;
426+ } , 500 ) ) ;
427+ concurrencyResults . push ( await benchAsync ( "50 concurrent findByHash" , async ( ) => {
428+ const hashes = testRecords . slice ( 0 , 50 ) . map ( r => r . keyHash ) ;
429+ await Promise . all ( hashes . map ( h => storage . findByHash ( h ) ) ) ;
430+ } , 1_000 ) ) ;
431+ concurrencyResults . push ( await benchAsync ( "mixed operations (50 concurrent)" , async ( ) => {
432+ const ops = Array . from ( { length : 50 } , ( _ , i ) => {
433+ const record = testRecords [ i % 100 ] ;
434+ const op = i % 4 ;
435+ if ( op === 0 ) return storage . findById ( record ?. id ?? "" ) ;
436+ if ( op === 1 ) return storage . findByHash ( record ?. keyHash ?? "" ) ;
437+ if ( op === 2 ) return storage . findByOwner ( `user_${ i % 10 } ` ) ;
438+ return storage . findByTag ( "env:prod" ) ;
439+ } ) ;
440+ await Promise . all ( ops ) ;
441+ } , 1_000 ) ) ;
442+ asyncGroups . push ( { title : "Concurrency Stress Tests" , icon : "⚡" , results : concurrencyResults } ) ;
443+
444+ // Print sync benchmarks
445+ console . log ( chalk . bold . cyan ( "\n━━━ Synchronous Operations ━━━" ) ) ;
274446 for ( const group of groups ) {
275447 printGroup ( group ) ;
276448 }
277449
450+ // Print async benchmarks
451+ console . log ( chalk . bold . cyan ( "\n━━━ Asynchronous Operations ━━━" ) ) ;
452+ for ( const group of asyncGroups ) {
453+ printGroup ( group ) ;
454+ }
455+
278456 // Summary
279- const allResults = groups . flatMap ( ( g ) => g . results ) ;
457+ const allResults = [ ... groups , ... asyncGroups ] . flatMap ( ( g ) => g . results ) ;
280458 const fastest = allResults . reduce ( ( a , b ) =>
281459 a . opsPerSec > b . opsPerSec ? a : b
282460 ) ;
@@ -297,4 +475,4 @@ function main() {
297475 ) ;
298476}
299477
300- main ( ) ;
478+ main ( ) . catch ( console . error ) ;
0 commit comments