11import type { Knex } from 'knex'
22
3- import { E_LOCK_NOT_OWNED } from '../errors .js'
4- import type { KnexStoreOptions , LockStore } from '../types/main.js'
3+ import { DatabaseStore } from './database .js'
4+ import type { DatabaseAdapter , KnexStoreOptions } from '../types/main.js'
55
66/**
7- * Create a new database store
7+ * Create a new knex store
88 */
99export function knexStore ( config : KnexStoreOptions ) {
10- return { config, factory : ( ) => new KnexStore ( config ) }
10+ return {
11+ config,
12+ factory : ( ) => {
13+ const adapter = new KnexAdapter ( config . connection )
14+ return new DatabaseStore ( adapter , config )
15+ } ,
16+ }
1117}
1218
13- export class KnexStore implements LockStore {
14- /**
15- * Knex connection instance
16- */
19+ /**
20+ * Knex adapter for the DatabaseStore
21+ */
22+ export class KnexAdapter implements DatabaseAdapter {
1723 #connection: Knex
24+ #tableName! : string
1825
19- /**
20- * The name of the table used to store locks
21- */
22- #tableName = 'verrou'
23-
24- /**
25- * A promise that resolves when the table is created
26- */
27- #initialized: Promise < void >
26+ constructor ( connection : Knex ) {
27+ this . #connection = connection
28+ }
2829
29- constructor ( config : KnexStoreOptions ) {
30- this . #connection = config . connection
31- this . #tableName = config . tableName || this . #tableName
32- if ( config . autoCreateTable !== false ) {
33- this . #initialized = this . #createTableIfNotExists( )
34- } else {
35- this . #initialized = Promise . resolve ( )
36- }
30+ setTableName ( tableName : string ) {
31+ this . #tableName = tableName
3732 }
3833
39- /**
40- * Create the locks table if it doesn't exist
41- */
42- async #createTableIfNotExists( ) {
34+ async createTableIfNotExists ( ) {
4335 const hasTable = await this . #connection. schema . hasTable ( this . #tableName)
4436 if ( hasTable ) return
4537
@@ -50,109 +42,47 @@ export class KnexStore implements LockStore {
5042 } )
5143 }
5244
53- /**
54- * Compute the expiration date based on the provided TTL
55- */
56- #computeExpiresAt( ttl : number | null ) {
57- if ( ttl ) return Date . now ( ) + ttl
58- return null
45+ async insertLock ( lock : { key : string ; owner : string ; expiration : number | null } ) {
46+ await this . #connection. table ( this . #tableName) . insert ( lock )
5947 }
6048
61- /**
62- * Get the current owner of given lock key
63- */
64- async #getCurrentOwner( key : string ) {
65- await this . #initialized
66- const result = await this . #connection
49+ async acquireLock ( lock : { key : string ; owner : string ; expiration : number | null } ) {
50+ const updated = await this . #connection
6751 . table ( this . #tableName)
68- . where ( 'key' , key )
69- . select ( 'owner' )
70- . first ( )
52+ . where ( 'key' , lock . key )
53+ . where ( 'expiration' , '<=' , Date . now ( ) )
54+ . update ( { owner : lock . owner , expiration : lock . expiration } )
7155
72- return result ?. owner
56+ return updated
7357 }
7458
75- /**
76- * Save the lock in the store if not already locked by another owner
77- *
78- * We basically rely on primary key constraint to ensure the lock is
79- * unique.
80- *
81- * If the lock already exists, we check if it's expired. If it is, we
82- * update it with the new owner and expiration date.
83- */
84- async save ( key : string , owner : string , ttl : number | null ) {
85- try {
86- await this . #initialized
87- await this . #connection
88- . table ( this . #tableName)
89- . insert ( { key, owner, expiration : this . #computeExpiresAt( ttl ) } )
90-
91- return true
92- } catch ( error ) {
93- const updated = await this . #connection
94- . table ( this . #tableName)
95- . where ( 'key' , key )
96- . where ( 'expiration' , '<=' , Date . now ( ) )
97- . update ( { owner, expiration : this . #computeExpiresAt( ttl ) } )
98-
99- return updated >= 1
100- }
101- }
102-
103- /**
104- * Delete the lock from the store if it is owned by the owner
105- * Otherwise throws a E_LOCK_NOT_OWNED error
106- */
107- async delete ( key : string , owner : string ) : Promise < void > {
108- const currentOwner = await this . #getCurrentOwner( key )
109- if ( currentOwner !== owner ) throw new E_LOCK_NOT_OWNED ( )
110-
111- await this . #connection. table ( this . #tableName) . where ( 'key' , key ) . where ( 'owner' , owner ) . delete ( )
112- }
113-
114- /**
115- * Force delete the lock from the store. No check is made on the owner
116- */
117- async forceDelete ( key : string ) {
118- await this . #connection. table ( this . #tableName) . where ( 'key' , key ) . delete ( )
59+ async deleteLock ( key : string , owner ?: string | undefined ) {
60+ await this . #connection
61+ . table ( this . #tableName)
62+ . where ( 'key' , key )
63+ . modify ( ( query ) => {
64+ if ( owner ) query . where ( 'owner' , owner )
65+ } )
66+ . delete ( )
11967 }
12068
121- /**
122- * Extend the lock expiration. Throws an error if the lock is not owned by the owner
123- * Duration is in milliseconds
124- */
125- async extend ( key : string , owner : string , duration : number ) {
69+ async extendLock ( key : string , owner : string , duration : number ) {
12670 const updated = await this . #connection
12771 . table ( this . #tableName)
12872 . where ( 'key' , key )
12973 . where ( 'owner' , owner )
13074 . update ( { expiration : Date . now ( ) + duration } )
13175
132- if ( updated === 0 ) throw new E_LOCK_NOT_OWNED ( )
76+ return updated
13377 }
13478
135- /**
136- * Check if the lock exists
137- */
138- async exists ( key : string ) {
139- await this . #initialized
79+ async getLock ( key : string ) {
14080 const result = await this . #connection
14181 . table ( this . #tableName)
14282 . where ( 'key' , key )
143- . select ( ' expiration')
83+ . select ( [ 'owner' , ' expiration'] )
14484 . first ( )
14585
146- if ( ! result ) return false
147- if ( result . expiration === null ) return true
148-
149- return result . expiration > Date . now ( )
150- }
151-
152- /**
153- * Disconnect knex connection
154- */
155- disconnect ( ) {
156- return this . #connection. destroy ( )
86+ return result
15787 }
15888}
0 commit comments