diff --git a/packages/core/src/QueryBuilder.ts b/packages/core/src/QueryBuilder.ts index a5d364f6..ecff3402 100644 --- a/packages/core/src/QueryBuilder.ts +++ b/packages/core/src/QueryBuilder.ts @@ -1,31 +1,29 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { ReDotDescriptor } from "@reactive-dot/types"; import type { ChainDefinition, TypedApi } from "polkadot-api"; -import { Observable } from "rxjs"; +import type { Observable } from "rxjs"; type InferPapiStorageEntry = T extends { - watchValue: (...args: infer Args) => Observable; + watchValue: (...args: infer Args) => infer Response; } - ? { args: Args; payload: Payload } - : { args: unknown[]; payload: unknown }; + ? { args: Args; response: Response } + : { args: unknown[]; response: unknown }; type InferPapiStorageEntryWithKeys = T extends { - getEntries: (...args: infer Args) => Promise; + getEntries: (...args: infer Args) => infer Response; } - ? { args: Args; payload: Payload } - : { args: unknown[]; payload: unknown }; + ? { args: Args; response: Response } + : { args: unknown[]; response: unknown }; -type InferPapiRuntimeCall = T extends ( - ...args: infer Args -) => Promise - ? { args: Args; payload: Payload } - : { args: unknown[]; payload: unknown }; +type InferPapiRuntimeCall = T extends (...args: infer Args) => infer Response + ? { args: Args; response: Response } + : { args: unknown[]; response: unknown }; type InferPapiConstantEntry = T extends { (): Promise; (runtime: infer _): infer Payload; } - ? Payload + ? Promise : unknown; type BaseInstruction = { @@ -109,7 +107,7 @@ type StorageReadPayload< TDescriptor extends ChainDefinition = ReDotDescriptor, > = InferPapiStorageEntry< TypedApi["query"][TInstruction["pallet"]][TInstruction["storage"]] ->["payload"]; +>["response"]; type StorageEntriesReadPayload< TInstruction extends StorageEntriesReadInstruction< @@ -121,7 +119,7 @@ type StorageEntriesReadPayload< TDescriptor extends ChainDefinition = ReDotDescriptor, > = InferPapiStorageEntryWithKeys< TypedApi["query"][TInstruction["pallet"]][TInstruction["storage"]] ->["payload"]; +>["response"]; type ApiCallPayload< TInstruction extends @@ -130,9 +128,9 @@ type ApiCallPayload< TDescriptor extends ChainDefinition = ReDotDescriptor, > = InferPapiRuntimeCall< TypedApi["apis"][TInstruction["pallet"]][TInstruction["api"]] ->["payload"]; +>["response"]; -export type InferInstruction< +export type InferInstructionResponse< TInstruction extends QueryInstruction, TDescriptor extends ChainDefinition = ReDotDescriptor, > = @@ -164,22 +162,61 @@ export type InferInstruction< ? ApiCallPayload : never; -export type InferInstructions< +export type InferInstructionPayload< + TInstruction extends QueryInstruction, + TDescriptor extends ChainDefinition = ReDotDescriptor, +> = + InferInstructionResponse extends Promise< + infer Payload + > + ? Payload + : InferInstructionResponse extends Observable< + infer Payload + > + ? Payload + : InferInstructionResponse; + +export type InferInstructionsResponse< + TInstructions extends QueryInstruction[], + TDescriptor extends ChainDefinition = ReDotDescriptor, +> = { + [P in keyof TInstructions]: InferInstructionResponse< + TInstructions[P], + TDescriptor + >; +}; + +export type InferInstructionsPayload< TInstructions extends QueryInstruction[], TDescriptor extends ChainDefinition = ReDotDescriptor, > = { - [P in keyof TInstructions]: InferInstruction; + [P in keyof TInstructions]: InferInstructionPayload< + TInstructions[P], + TDescriptor + >; }; -export type InferQueryBuilder = +export type InferQueryBuilderResponse = T extends QueryBuilder - ? InferInstructions + ? InferInstructionsResponse : never; +export type InferQueryBuilderPayload = + T extends QueryBuilder + ? InferInstructionsPayload + : never; + +export type Query< + TInstructions extends QueryInstruction[] = QueryInstruction[], +> = { + readonly instructions: TInstructions; +}; + export default class QueryBuilder< const TInstructions extends QueryInstruction[] = QueryInstruction[], TDescriptor extends ChainDefinition = ReDotDescriptor, -> { +> implements Query +{ #instructions: TInstructions; constructor(instructions: TInstructions) { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index df0e3174..daa101c7 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,8 +1,10 @@ export { default as QueryBuilder, - type InferQueryBuilder, + type InferQueryBuilderPayload, + type InferQueryBuilderResponse, type MultiInstruction, type QueryInstruction, } from "./QueryBuilder.js"; export * from "./errors.js"; +export { default as query } from "./query.js"; export * from "./symbols.js"; diff --git a/packages/core/src/query.ts b/packages/core/src/query.ts new file mode 100644 index 00000000..bcbc98c7 --- /dev/null +++ b/packages/core/src/query.ts @@ -0,0 +1,45 @@ +import type { + InferInstructionResponse, + QueryInstruction, +} from "./QueryBuilder.js"; +import type { ReDotDescriptor } from "@reactive-dot/types"; +import type { ChainDefinition, TypedApi } from "polkadot-api"; + +const query = < + TInstruction extends QueryInstruction, + TDescriptor extends ChainDefinition = ReDotDescriptor, +>( + api: TypedApi, + instruction: TInstruction, + options?: { signal?: AbortSignal }, +): InferInstructionResponse => { + switch (instruction.instruction) { + case "fetch-constant": + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (api as any).constants[instruction.pallet][ + instruction.constant + ]() as InferInstructionResponse; + case "call-api": + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (api as any).apis[instruction.pallet][instruction.api]( + ...instruction.args, + { signal: options?.signal }, + ) as InferInstructionResponse; + case "read-storage": + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (api as any).query[instruction.pallet][ + instruction.storage + ].watchValue( + ...instruction.args, + ) as InferInstructionResponse; + case "read-storage-entries": + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (api as any).query[instruction.pallet][ + instruction.storage + ].getEntries(...instruction.args, { + signal: options?.signal, + }) as InferInstructionResponse; + } +}; + +export default query; diff --git a/packages/react/src/hooks/useQuery.ts b/packages/react/src/hooks/useQuery.ts index c4b1b3ca..f7d5fa68 100644 --- a/packages/react/src/hooks/useQuery.ts +++ b/packages/react/src/hooks/useQuery.ts @@ -4,10 +4,10 @@ import type { Falsy, FalsyGuard, FlatHead } from "../types.js"; import { flatHead, stringify } from "../utils.js"; import type { ChainHookOptions } from "./types.js"; import { - InferQueryBuilder, QueryBuilder, QueryError, QueryInstruction, + type InferQueryBuilderPayload, } from "@reactive-dot/core"; import type { ChainId, Chains, ReDotDescriptor } from "@reactive-dot/types"; import { atom, useAtomValue } from "jotai"; @@ -38,7 +38,9 @@ export const useQuery = < : FalsyGuard< ReturnType>, FlatHead< - InferQueryBuilder>, Falsy>> + InferQueryBuilderPayload< + Exclude>, Falsy> + > > > => { const contextChainId = useContext(ChainIdContext); diff --git a/packages/react/src/stores/query.ts b/packages/react/src/stores/query.ts index 4b7142e2..61695c75 100644 --- a/packages/react/src/stores/query.ts +++ b/packages/react/src/stores/query.ts @@ -1,15 +1,15 @@ import { stringify } from "../utils.js"; import { typedApiAtomFamily } from "./client.js"; import { - QueryInstruction, MultiInstruction, QueryError, + QueryInstruction, + query, } from "@reactive-dot/core"; import type { ChainId } from "@reactive-dot/types"; import { atom } from "jotai"; import { atomFamily, atomWithObservable } from "jotai/utils"; -import { from, type Observable } from "rxjs"; -import { switchMap } from "rxjs/operators"; +import { from, switchMap, type Observable } from "rxjs"; const _queryAtomFamily = atomFamily( (param: { @@ -20,62 +20,17 @@ const _queryAtomFamily = atomFamily( // eslint-disable-next-line @typescript-eslint/ban-types {}> >; - }) => { - switch (param.instruction.instruction) { - case "fetch-constant": { - const { pallet, constant } = param.instruction; - - return atom(async (get) => { - const api = await get(typedApiAtomFamily(param.chainId)); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (api as any).constants[pallet][constant]() as Promise; - }); - } - - case "read-storage": { - const { pallet, storage, args } = param.instruction; - - return atomWithObservable((get) => - from(get(typedApiAtomFamily(param.chainId))).pipe( - switchMap( - (api) => - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (api as any).query[pallet][storage].watchValue( - ...args, - ) as Observable, - ), - ), - ); - } - - case "read-storage-entries": { - const { pallet, storage, args } = param.instruction; - - return atom(async (get, { signal }) => { - const api = await get(typedApiAtomFamily(param.chainId)); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (api as any).query[pallet][storage].getEntries(...args, { - signal, - }); - }); - } - - case "call-api": { - const { pallet, api, args } = param.instruction; - - return atom(async (get, { signal }) => { - const typedApi = await get(typedApiAtomFamily(param.chainId)); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (typedApi as any).apis[pallet][api](...args, { - signal, - }) as Promise; - }); - } - } - }, + }) => + atomWithObservable((get) => + from(get(typedApiAtomFamily(param.chainId))).pipe( + switchMap( + (api) => + query(api, param.instruction) as + | Promise + | Observable, + ), + ), + ), (a, b) => stringify(a) === stringify(b), );