#What's going wrong here?

19 messages · Page 1 of 1 (latest)

slate coral
#
  const settingsSocketRef = ref(socketManagerRef.value?.settingsSocket)

 const loadSettings = async (): Promise<Result<void, NetworkError>> => {
    isLoading.value = true
    error.value = undefined

    const connectionResult = await ensureConnected()
    if (connectionResult.isErr()) {
      error.value = connectionResult.error.message
      isLoading.value = false
      return err(connectionResult.error)
    }

    const emitResult = await emitWithTimeout<UserSettingWire[]>(
      settingsSocketRef, // Here is the error
      'settings:getAll',
      undefined,
      {
        signal: abortController.signal,
        timeoutMs: socketTimeoutMs.settings,
      }
    )

    if (emitResult.isErr()) {
      error.value = emitResult.error.message
      isLoading.value = false
      return err(emitResult.error)
    }

    settings.value = emitResult.value.map(fromWire)
    isLoading.value = false
    return ok(undefined)
  }
#
Argument of type 'Ref<{ readonly isConnected: boolean; init: () => Promise<Result<void, SocketError>>; updateAuthToken: (token: string) => void; waitForConnection: (timeoutMilliseconds?: number) => Promise<...>; ... 9 more ...; readonly subscriptionSocket: ...; } | undefined, SocketManager | ... 1 more ... | undefined>' is not assignable to parameter of type 'Ref<Socket<DefaultEventsMap, DefaultEventsMap> | null | undefined, Socket<DefaultEventsMap, DefaultEventsMap> | null | undefined>'.
  Type '{ readonly isConnected: boolean; init: () => Promise<Result<void, SocketError>>; updateAuthToken: (token: string) => void; waitForConnection: (timeoutMilliseconds?: number) => Promise<...>; ... 9 more ...; readonly subscriptionSocket: ...; } | undefined' is not assignable to type 'Socket<DefaultEventsMap, DefaultEventsMap> | null | undefined'.
    Type '{ readonly isConnected: boolean; init: () => Promise<Result<void, SocketError>>; updateAuthToken: (token: string) => void; waitForConnection: (timeoutMilliseconds?: number) => Promise<...>; ... 9 more ...; readonly subscriptionSocket: ...; }' is missing the following properties from type 'Socket<DefaultEventsMap, DefaultEventsMap>': io, id, _pid, _lastOffset, and 58 more.
#

Shared

export type UserSettingWithValidation = {
  readonly settingKey: string
  readonly settingValue: unknown
  readonly dataType: 'string' | 'number' | 'boolean'
  readonly updatedAt: Date
  readonly category: string
  readonly encrypted: boolean
  readonly required: boolean
  readonly description: string
  readonly minLength?: number
  readonly maxLength?: number
  readonly minValue?: number
  readonly maxValue?: number
}

/**
 * Wire-safe type for socket transmission (Date → ISO string)
 */
export type UserSettingWire = Omit<UserSettingWithValidation, 'updatedAt'> & {
  readonly updatedAt: string
}
#

emit-with-timeout.ts

import type { Ref } from 'vue'
import { ResultAsync } from 'neverthrow'
import { NetworkError, SocketError } from '@/errors'
import { socketTimeoutMs } from '@/constants'
import type { Socket } from 'socket.io-client'

type SocketResponse<T> = {
  success: boolean
  data?: T
  error?: string
}

const defaultTimeout = socketTimeoutMs.default

export function emitWithTimeout<T = void>(
  socketRef: Ref<Socket | null | undefined>,
  event: string,
  payload?: unknown,
  options?: { timeoutMs?: number; signal?: AbortSignal }
): ResultAsync<T, NetworkError> {
  const socket = socketRef.value
  if (!socket) {
    return ResultAsync.fromSafePromise(Promise.reject(new SocketError('Socket not connected')))
  }

  // We assume the socket is already ready

  return ResultAsync.fromPromise(
    new Promise<T>((resolve, reject) => {
      const timeoutMs = options?.timeoutMs ?? defaultTimeout
      const signal = options?.signal

      if (signal?.aborted) {
        reject(new NetworkError('Request aborted'))
        return
      }

      const abortHandler = () => {
        reject(new NetworkError('Request aborted'))
      }
      signal?.addEventListener('abort', abortHandler, { once: true })

      socket
        .timeout(timeoutMs)
        .emit(event, payload, (err: unknown, response: SocketResponse<T>) => {
          signal?.removeEventListener('abort', abortHandler)

          if (err) {
            const message = err instanceof Error ? err.message : String(err)
            reject(new NetworkError(message))
          } else if (!response.success) {
            reject(new NetworkError(response.error ?? `Failed to ${event}`))
          } else {
            resolve(response.data as T)
          }
        })
    }),
    (error) => (error instanceof NetworkError ? error : new NetworkError(String(error)))
  )
}
tulip apex
#

it really helps to have a playground someone can use to see the real error

#

no idea if this is reproduced correctly, or if its fixed correctly, but for future helpers:

vague plinthBOT
#
webstrand#0

Preview:```ts
import type {Ref} from "vue"
import {ResultAsync} from "neverthrow"
import type {Socket} from "socket.io-client"

const socketTimeoutMs = {default: 500, settings: 900}
class NetworkError extends Error {}
class SocketError extends Error {}

export type UserSettingWithValidation = {
...```

tulip apex
#

does that fix your issue for real?

slate coral
#

eh what do you mean

tulip apex
slate coral
#

That's what I see

tulip apex
slate coral
#

AH thanks, I am about to take a look

tulip apex
#

its a pure "solve the types solution" so don't assume it was solved correctly or with much intelligence

#

I stubbed a ton of stuff too, just trying to get a working reproduction

slate coral
#

i made a better reproduction in the new thread Having Fun with Generics. There's the current state.

#

that computed workaround is weird i swear
this feels rather like i did something else not lean enough