import type { AnyElysia } from 'elysia' import { createChain } from './links/internal/create-chain' import { promisifyObservable, type Unsubscribable } from './links/internal/observable' import type { EdenClientError, EdenClientOptions, EdenClientRuntime, OperationContext, OperationLink, OperationType, } from './links/internal/operation' import { share } from './links/internal/operators' import type { EdenResponse } from './request' import type { EdenRequestParams } from './resolve' export type EdenSubscriptionObserver = { onStarted: () => void onData: (value: TValue) => void onError: (err: TError) => void onStopped: () => void onComplete: () => void } export type EdenCreateClient = ( options: EdenClientOptions, ) => EdenClient export type EdenClientInternalRequestOptions = { type: OperationType context?: OperationContext params: EdenRequestParams } export type EdenClientPromisifyRequestOptions = EdenClientInternalRequestOptions & { signal?: AbortSignal } export type EdenClientRequestOptions = { context?: OperationContext signal?: AbortSignal } export class EdenClient { private readonly links: OperationLink[] public readonly runtime: EdenClientRuntime private requestId: number constructor(options: EdenClientOptions) { this.requestId = 0 this.runtime = {} this.links = options.links.map((link) => link(this.runtime)) } private $request( options: EdenClientInternalRequestOptions, ) { const chain$ = createChain({ links: this.links as OperationLink[], operation: { id: ++this.requestId, ...options, context: options.context ?? {}, }, }) return chain$.pipe(share()) } private promisifyRequest( options: EdenClientPromisifyRequestOptions, ): Promise { // Forward the signal if (options.signal != null) { options.params.fetch ??= {} options.params.fetch.signal = options.signal } const signal = options.params.fetch?.signal const req$ = this.$request(options) const { promise, abort } = promisifyObservable(req$ as any) const abortablePromise = new Promise((resolve, reject) => { signal?.addEventListener('abort', abort) promise.then(resolve).catch(reject) }) return abortablePromise } public query(params: EdenRequestParams, options?: EdenClientRequestOptions) { return this.promisifyRequest({ type: 'query', params, context: options?.context, signal: options?.signal, }) } public mutation(params: EdenRequestParams, options?: EdenClientRequestOptions) { return this.promisifyRequest({ type: 'mutation', params, context: options?.context, signal: options?.signal, }) } public subscription( params: EdenRequestParams, options?: Partial>> & EdenClientRequestOptions, ): Unsubscribable { const observable = this.$request({ type: 'subscription', params, context: options?.context, }) const $observable = observable.subscribe({ next: (envelope) => { if (envelope.type === 'started') { options?.onStarted?.() } else if (envelope.type === 'stopped') { options?.onStopped?.() } else { options?.onData?.(envelope.data) } }, error: (err) => { options?.onError?.(err) }, complete: () => { options?.onComplete?.() }, }) return $observable } }