Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

chore: fix rpc message typings & clean up some tsdocs #118

Merged
merged 3 commits into from
Jun 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions _tasks/download_frame_metadata.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as asserts from "../_deps/asserts.ts";
import * as fs from "../_deps/fs.ts";
import * as path from "../_deps/path.ts";
import { CHAIN_URL_LOOKUP } from "../constants/chains/url.ts";
Expand All @@ -10,9 +11,7 @@ await Promise.all(
const client = await rpc.wsRpcClient(url);
try {
const metadata = await client.call("state_getMetadata", []);
if (metadata instanceof Error) {
throw metadata;
}
asserts.assert(metadata.result);
const outPath = path.join(outDir, `${name}.scale`);
console.log(`Downloading ${name} metadata to "${outPath}".`);
await Deno.writeTextFile(outPath, metadata.result);
Expand Down
6 changes: 5 additions & 1 deletion effect/std/rpcCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const rpcCall = effector.async.generic(
) =>
effect(args, () =>
async (client, methodName, ...params) => {
return await client.call(methodName, params);
const result = await client.call(methodName, params);
if (result.error) {
return new rpc.RpcServerError(result);
}
return result;
}),
);
2 changes: 1 addition & 1 deletion examples/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const [client, sr25519, hashers] = await Promise.all([
]);

const metadataRaw = await client.call("state_getMetadata", []);
asserts.assert(!(metadataRaw instanceof Error));
asserts.assert(metadataRaw.result);
const metadata = C.M.fromPrefixedHex(metadataRaw.result);
const deriveCodec = C.M.DeriveCodec(metadata);

Expand Down
2 changes: 1 addition & 1 deletion frame_metadata/Codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type DeriveCodec = (typeI: number) => $.Codec<unknown>;

/**
* All derived codecs for ZSTs will use this exact codec,
* so `derivedCodec === $null` is true iff the type is a ZST.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was not a typo – iff in logic is an abbreviation for "if and only if", which is the most accurate description of the contract of $null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, apologies

* so `derivedCodec === $null` is true if the type is a ZST.
*/
export const $null = $.dummy(null);

Expand Down
56 changes: 36 additions & 20 deletions rpc/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,48 @@ export abstract class RpcClient<RpcError extends E.RpcError> {
listeners = new Map<ListenerCb, boolean>();
#errors: RpcError[] = [];

/** Send a message to the RPC server
* @param egressMessage the message you wish to send to the RPC server */
/**
* Send a message to the RPC server
*
* @param egressMessage the message you wish to send to the RPC server
*/
abstract send: (egressMessage: M.InitMessage) => void;

/** Parse messages returned from the RPC server (this includes RPC server errors)
/**
* Parse messages returned from the RPC server (this includes RPC server errors)
*
* @param rawIngressMessage the raw response from the given provider, likely in need of some sanitization
* @returns the sanitized ingress message, common to all providers */
* @returns the sanitized ingress message, common to all providers
*/
abstract parseMessage: (rawIngressMessage: unknown) => M.IngressMessage;

/** Parse errors of the given client, such as an error `Event` in the case of `WebSocket`s
/**
* Parse errors of the given client, such as an error `Event` in the case of `WebSocket`s
*
* @param rawError the raw error from the given provider
* @returns an instance of `RpcError`, typed via the client's sole generic type param */
* @returns an instance of `RpcError`, typed via the client's sole generic type param
*/
abstract parseError: (rawError: any) => RpcError;

// TODO: introduce `FailedToClose` error in the return type (union with `undefined`)
/** Close the connection and free up resources
* @returns a promise, which resolved to `undefined` upon successful cancelation */
/**
* Close the connection and free up resources
*
* @returns a promise, which resolved to `undefined` upon successful cancelation
*/
abstract close: () => Promise<void>;

/** @returns a new ID, unique to the client instance */
uid = (): string => {
return (this.#nextId++).toString();
};

/** Attach a listener to handle ingress messages
/**
* Attach a listener to handle ingress messages
*
* @param listener the callback to be triggered upon arrival of ingress messages
* @returns a function to detach the listener */
* @returns a function to detach the listener
*/
listen = (listener: ListenerCb): StopListening => {
if (this.listeners.has(listener)) {
throw new Error();
Expand All @@ -65,29 +80,28 @@ export abstract class RpcClient<RpcError extends E.RpcError> {
this.#errors.push(this.parseError(error));
};

/** Call an RPC method and return a promise resolving to an ingress message with an ID that matches the egress message
/**
* Call an RPC method and return a promise resolving to an ingress message with an ID that matches the egress message
*
* @param method the name of the method you wish to call
* @param params the params with which to call the method
* @returns an ingress message corresponding to the given method (or a message-agnostic error) */
* @returns an ingress message corresponding to the given method (or a message-agnostic error)
*/
call = async <Method extends M.MethodName>(
method: Method,
params: M.InitMessageByMethodName[Method]["params"],
): Promise<M.OkMessage<Method> | E.RpcServerError> => {
): Promise<M.OkMessage<Method> | M.ErrMessage> => {
const init = <M.InitMessage<Method>> {
jsonrpc: "2.0",
id: this.uid(),
method,
params,
};
const isCorrespondingRes = IsCorrespondingRes(init);
const pending = deferred<M.OkMessage<Method> | E.RpcServerError>();
const pending = deferred<M.OkMessage<Method> | M.ErrMessage>();
const stopListening = this.listen((res) => {
if (isCorrespondingRes(res)) {
if (res.error) {
pending.resolve(new E.RpcServerError(res));
} else {
pending.resolve(res);
}
pending.resolve(res);
}
});
this.send(init);
Expand All @@ -96,7 +110,9 @@ export abstract class RpcClient<RpcError extends E.RpcError> {
return result;
};

/** Initialize an RPC subscription
/**
* Initialize an RPC subscription
*
* @param method the method name of the subscription you wish to init
* @param params the params with which to init the subscription
* @param listenerCb the callback to which notifications should be supplied
Expand Down
1 change: 1 addition & 0 deletions rpc/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type IngressMessage = OkMessage | ErrMessage | NotifMessage;
* The following is modeled closely after the method definitions of Smoldot. This `Lookup` type serves as a source of
* truth, from which we map to init, notification and ok response types. Error types are––unfortunately––not defined as
* method-specific on the Rust side, although perhaps we could create represent them as such.
*
* @see https://github.com/paritytech/smoldot/blob/82836f4f2af4dd1716c57c14a4f591c7b1043950/src/json_rpc/methods.rs#L338-L479
*/
type MethodLookup = EnsureLookup<string, (...args: any[]) => any, {
Expand Down
7 changes: 3 additions & 4 deletions util/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ export type ValueOf<T> = T[keyof T];
export type U2I<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R
: never;

// Sometimes, the checker isn't wise enough, and we must summon dark forces.
export type AsKeyof<K, T> = K extends keyof T ? K : never;

export type EnsureLookup<
K extends PropertyKey,
ValueConstraint,
Expand All @@ -14,7 +11,9 @@ export type EnsureLookup<
},
> = Lookup;

export type Flatten<T> = T extends Function ? T : { [K in keyof T]: T[K] };
export type Flatten<T> = T extends (infer E)[] ? Flatten<E>[]
: T extends object ? { [K in keyof T]: Flatten<T[K]> }
: T;
Comment on lines +14 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks Flatten for tuples. I've recently adopted

export type Flatten<T> = [{ [K in keyof T]: Flatten<T[K]> }][0]

with the caveat that it should only be used to wrap things known to be simple objects or arrays


export type Narrow<T> =
| (T extends infer U ? U : never)
Expand Down