All files / packages/coordination/src/runtime session-registry.ts

100% Statements 55/55
100% Branches 20/20
100% Functions 14/14
100% Lines 55/55

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146                    5x 5x       13x     1100x 1100x 1100x       1100x   1100x                               430x 430x 430x 430x 430x       413x         154x 154x 154x 154x     426x 4x     422x 422x   422x       435x 5x     430x 430x   430x   430x       414x       417x 417x 11x     406x 406x   406x       95x 88x     7x 5x     7x       93x 1x     92x   92x 4x     92x       137x 1x     136x 136x   136x 4x     136x       2x      
import type { SessionResponse } from '@ydbjs/api/coordination'
 
export type Deferred<T> = {
	promise: Promise<T>
	resolve(value: T | PromiseLike<T>): void
	reject(reason?: unknown): void
}
 
export class SessionReconnectError extends Error {
	constructor() {
		super('Session reconnecting')
		this.name = 'SessionReconnectError'
	}
}
 
export let createDeferred = function createDeferred<T>(): Deferred<T> {
	let resolve!: (value: T | PromiseLike<T>) => void
	let reject!: (reason?: unknown) => void
	let promise = new Promise<T>((res, rej) => {
		resolve = res
		reject = rej
	})
 
	// Prevent UnhandledPromiseRejection when rejected before anyone awaits.
	promise.catch(() => {})
 
	return { promise, resolve, reject }
}
 
export class PendingRequest implements Disposable {
	#registry: SessionRequestRegistry
	#reqId: bigint
 
	promise: Promise<SessionResponse>
	resolve: (value: SessionResponse | PromiseLike<SessionResponse>) => void
	reject: (reason?: unknown) => void
 
	constructor(
		registry: SessionRequestRegistry,
		reqId: bigint,
		deferred: Deferred<SessionResponse>
	) {
		this.#registry = registry
		this.#reqId = reqId
		this.promise = deferred.promise
		this.resolve = deferred.resolve
		this.reject = deferred.reject
	}
 
	[Symbol.dispose](): void {
		this.#registry.delete(this.#reqId)
	}
}
 
export class SessionRequestRegistry implements Disposable {
	#nextReqId = 1n
	#closed = false
	#destroyed = false
	#pending = new Map<bigint, PendingRequest>()
 
	nextReqId(): bigint {
		if (this.#closed || this.#destroyed) {
			throw new Error('Session request registry is closed')
		}
 
		let reqId = this.#nextReqId
		this.#nextReqId += 1n
 
		return reqId
	}
 
	register(reqId: bigint): PendingRequest {
		if (this.#closed || this.#destroyed) {
			throw new Error('Session request registry is closed')
		}
 
		let deferred = createDeferred<SessionResponse>()
		let pending = new PendingRequest(this, reqId, deferred)
 
		this.#pending.set(reqId, pending)
 
		return pending
	}
 
	delete(reqId: bigint): void {
		this.#pending.delete(reqId)
	}
 
	resolve(reqId: bigint, response: SessionResponse): boolean {
		let pending = this.#pending.get(reqId)
		if (!pending) {
			return false
		}
 
		this.#pending.delete(reqId)
		pending.resolve(response)
 
		return true
	}
 
	reconnect(): void {
		if (this.#closed || this.#destroyed) {
			return
		}
 
		for (let [, pending] of this.#pending) {
			pending.reject(new SessionReconnectError())
		}
 
		this.#pending.clear()
	}
 
	close(): void {
		if (this.#closed || this.#destroyed) {
			return
		}
 
		this.#closed = true
 
		for (let [, pending] of this.#pending) {
			pending.reject(new Error('Session closed'))
		}
 
		this.#pending.clear()
	}
 
	destroy(reason: unknown): void {
		if (this.#destroyed) {
			return
		}
 
		this.#destroyed = true
		this.#closed = true
 
		for (let [, pending] of this.#pending) {
			pending.reject(reason)
		}
 
		this.#pending.clear()
	}
 
	[Symbol.dispose](): void {
		this.destroy(new Error('Session request registry disposed'))
	}
}