From f2f261bc3f22e6fc0d6a4b3a726ad9f22b774451 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 23 Sep 2026 19:11:08 +0200 Subject: [PATCH] cultmesh-ts: infer LoadedNativeLibrary from buildLibrary's return instead of restating it The `LoadedNativeLibrary` interface just re-listed the sixteen fields of the object literal `loadBindings` already built, one liability the methods below never needed a second time. Split the library-building body into `buildLibrary()` and let `LoadedNativeLibrary` be `ReturnType`; `loadBindings` is now a thin cache wrapper around it. `nextEventAsync`'s signature stays explicit as `NextEventAsyncFn`, since `promisify` can't infer one from koffi's untyped `.async`. Co-Authored-By: Claude Sonnet 5 --- .../cultmesh-ts/src/realtime-quic-native.ts | 55 +++++++++---------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/packages/cultmesh-ts/src/realtime-quic-native.ts b/packages/cultmesh-ts/src/realtime-quic-native.ts index 507c84a2..bb9a5efb 100644 --- a/packages/cultmesh-ts/src/realtime-quic-native.ts +++ b/packages/cultmesh-ts/src/realtime-quic-native.ts @@ -88,35 +88,26 @@ interface NativeEventStruct { // eslint-disable-next-line @typescript-eslint/no-explicit-any type KoffiLib = any; +/** `nextEventAsync`'s signature, typed explicitly because `promisify` cannot + * infer one from koffi's untyped (`any`) `.async` function. */ +type NextEventAsyncFn = ( + runtime: CultMeshQuicNativeHandle, + timeoutMs: number, + outEvent: unknown, + payload: Uint8Array | null, + payloadCapacity: number, + outRequired: unknown, +) => Promise; + /** * The loaded native library: koffi itself, the declared struct, and the * twelve raw v2 exports. Untyped (koffi is `any`); the runtime class methods * below are the type surface for these calls, so wrapping them a second time - * in a typed interface would only restate what the methods already say. + * in a typed interface would only restate what the methods already say. The + * type is inferred from `buildLibrary`'s return value instead of restated. */ -interface LoadedNativeLibrary { - koffi: KoffiLib; - event: KoffiLib; - runtimeOpenFn: KoffiLib; - runtimeCloseFn: KoffiLib; - listenerOpenFn: KoffiLib; - listenerCloseFn: KoffiLib; - connectionOpenFn: KoffiLib; - connectionCertificateCompleteFn: KoffiLib; - connectionShutdownFn: KoffiLib; - streamOpenFn: KoffiLib; - streamSendFrameFn: KoffiLib; - streamShutdownFn: KoffiLib; - nextEventAsync: ( - runtime: CultMeshQuicNativeHandle, - timeoutMs: number, - outEvent: unknown, - payload: Uint8Array | null, - payloadCapacity: number, - outRequired: unknown, - ) => Promise; - lastErrorFn: KoffiLib; -} +// eslint-disable-next-line @typescript-eslint/no-use-before-define +type LoadedNativeLibrary = ReturnType; let libraryCache: LoadedNativeLibrary | undefined; @@ -140,10 +131,8 @@ function resolveNativeDir(): string { return join(__dirname, "..", "native", platformArchDir()); } -/** Lazily loads koffi, resolves the binary directory, and declares the ABI. */ -function loadBindings(): LoadedNativeLibrary { - if (libraryCache) return libraryCache; - +/** Loads koffi, resolves the binary directory, and declares the ABI. */ +function buildLibrary() { // Untyped on purpose: koffi's own type declarations are ESM-flavored and // fight a CommonJS `typeof import(...)` reference under Node16 resolution. // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-explicit-any @@ -222,9 +211,9 @@ function loadBindings(): LoadedNativeLibrary { // eslint-disable-next-line @typescript-eslint/no-var-requires const { promisify } = require("node:util") as typeof import("node:util"); - const nextEventAsync = promisify(nextEventFn.async) as LoadedNativeLibrary["nextEventAsync"]; + const nextEventAsync = promisify(nextEventFn.async) as NextEventAsyncFn; - libraryCache = { + return { koffi, event, runtimeOpenFn, @@ -240,6 +229,12 @@ function loadBindings(): LoadedNativeLibrary { nextEventAsync, lastErrorFn, }; +} + +/** Lazily builds and caches the loaded native library. */ +function loadBindings(): LoadedNativeLibrary { + if (libraryCache) return libraryCache; + libraryCache = buildLibrary(); return libraryCache; }