Skip to content
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: 5 additions & 0 deletions .changeset/dirty-bikes-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"capnweb": minor
---

The package now exports the type `RpcCompatible<T>` (previously called `Serializable<T>`, but not exported), which is needed when writing generic functions on `RpcStub` / `RpcPromise`.
20 changes: 10 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { RpcTarget as RpcTargetImpl, RpcStub as RpcStubImpl, RpcPromise as RpcPromiseImpl } from "./core.js";
import { serialize, deserialize } from "./serialize.js";
import { RpcTransport, RpcSession as RpcSessionImpl, RpcSessionOptions } from "./rpc.js";
import { RpcTargetBranded, Serializable, Stub, Stubify, __RPC_TARGET_BRAND } from "./types.js";
import { RpcTargetBranded, RpcCompatible, Stub, Stubify, __RPC_TARGET_BRAND } from "./types.js";
import { newWebSocketRpcSession as newWebSocketRpcSessionImpl,
newWorkersWebSocketRpcResponse } from "./websocket.js";
import { newHttpBatchRpcSession as newHttpBatchRpcSessionImpl,
Expand All @@ -18,7 +18,7 @@ forceInitMap();
// Re-export public API types.
export { serialize, deserialize, newWorkersWebSocketRpcResponse, newHttpBatchRpcResponse,
nodeHttpBatchRpcResponse };
export type { RpcTransport, RpcSessionOptions };
export type { RpcTransport, RpcSessionOptions, RpcCompatible };

// Hack the type system to make RpcStub's types work nicely!
/**
Expand All @@ -31,9 +31,9 @@ export type { RpcTransport, RpcSessionOptions };
* such method exists on the remote object, an exception is thrown back. But the client does not
* actually know, until that point, what methods exist.
*/
export type RpcStub<T extends Serializable<T>> = Stub<T>;
export type RpcStub<T extends RpcCompatible<T>> = Stub<T>;
export const RpcStub: {
new <T extends Serializable<T>>(value: T): RpcStub<T>;
new <T extends RpcCompatible<T>>(value: T): RpcStub<T>;
} = <any>RpcStubImpl;

/**
Expand All @@ -54,7 +54,7 @@ export const RpcStub: {
* if you only intend to use the promise for pipelining and you never await it, then there's no
* need to transmit the resolution!
*/
export type RpcPromise<T extends Serializable<T>> = Stub<T> & Promise<Stubify<T>>;
export type RpcPromise<T extends RpcCompatible<T>> = Stub<T> & Promise<Stubify<T>>;
export const RpcPromise: {
// Note: Cannot construct directly!
} = <any>RpcPromiseImpl;
Expand All @@ -64,7 +64,7 @@ export const RpcPromise: {
*
* Most people won't use this. You only need it if you've implemented your own `RpcTransport`.
*/
export interface RpcSession<T extends Serializable<T> = undefined> {
export interface RpcSession<T extends RpcCompatible<T> = undefined> {
getRemoteMain(): RpcStub<T>;
getStats(): {imports: number, exports: number};

Expand All @@ -73,7 +73,7 @@ export interface RpcSession<T extends Serializable<T> = undefined> {
drain(): Promise<void>;
}
export const RpcSession: {
new <T extends Serializable<T> = undefined>(
new <T extends RpcCompatible<T> = undefined>(
transport: RpcTransport, localMain?: any, options?: RpcSessionOptions): RpcSession<T>;
} = <any>RpcSessionImpl;

Expand Down Expand Up @@ -106,7 +106,7 @@ interface Empty {}
* @param localMain The main RPC interface to expose to the peer. Returns a stub for the main
* interface exposed from the peer.
*/
export let newWebSocketRpcSession:<T extends Serializable<T> = Empty>
export let newWebSocketRpcSession:<T extends RpcCompatible<T> = Empty>
(webSocket: WebSocket | string, localMain?: any, options?: RpcSessionOptions) => RpcStub<T> =
<any>newWebSocketRpcSessionImpl;

Expand All @@ -117,7 +117,7 @@ export let newWebSocketRpcSession:<T extends Serializable<T> = Empty>
* value is an RpcStub. You can customize anything about the request except for the method
* (it will always be set to POST) and the body (which the RPC system will fill in).
*/
export let newHttpBatchRpcSession:<T extends Serializable<T>>
export let newHttpBatchRpcSession:<T extends RpcCompatible<T>>
(urlOrRequest: string | Request, options?: RpcSessionOptions) => RpcStub<T> =
<any>newHttpBatchRpcSessionImpl;

Expand All @@ -126,7 +126,7 @@ export let newHttpBatchRpcSession:<T extends Serializable<T>>
* between an iframe and its parent frame in a browser context. Each side should call this function
* on its own end of the MessageChannel.
*/
export let newMessagePortRpcSession:<T extends Serializable<T> = Empty>
export let newMessagePortRpcSession:<T extends RpcCompatible<T> = Empty>
(port: MessagePort, localMain?: any, options?: RpcSessionOptions) => RpcStub<T> =
<any>newMessagePortRpcSessionImpl;

Expand Down
24 changes: 12 additions & 12 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,34 @@ export type Stubable = RpcTargetBranded | ((...args: any[]) => any);
// The reason for using a generic type here is to build a serializable subset of structured
// cloneable composite types. This allows types defined with the "interface" keyword to pass the
// serializable check as well. Otherwise, only types defined with the "type" keyword would pass.
export type Serializable<T> =
export type RpcCompatible<T> =
// Structured cloneables
| BaseType
// Structured cloneable composites
| Map<
T extends Map<infer U, unknown> ? Serializable<U> : never,
T extends Map<unknown, infer U> ? Serializable<U> : never
T extends Map<infer U, unknown> ? RpcCompatible<U> : never,
T extends Map<unknown, infer U> ? RpcCompatible<U> : never
>
| Set<T extends Set<infer U> ? Serializable<U> : never>
| Array<T extends Array<infer U> ? Serializable<U> : never>
| ReadonlyArray<T extends ReadonlyArray<infer U> ? Serializable<U> : never>
| Set<T extends Set<infer U> ? RpcCompatible<U> : never>
| Array<T extends Array<infer U> ? RpcCompatible<U> : never>
| ReadonlyArray<T extends ReadonlyArray<infer U> ? RpcCompatible<U> : never>
| {
[K in keyof T]: K extends number | string ? Serializable<T[K]> : never;
[K in keyof T]: K extends number | string ? RpcCompatible<T[K]> : never;
}
| Promise<T extends Promise<infer U> ? Serializable<U> : never>
| Promise<T extends Promise<infer U> ? RpcCompatible<U> : never>
// Special types
| Stub<Stubable>
// Serialized as stubs, see `Stubify`
| Stubable;

// Base type for all RPC stubs, including common memory management methods.
// `T` is used as a marker type for unwrapping `Stub`s later.
interface StubBase<T extends Serializable<T>> extends Disposable {
interface StubBase<T extends RpcCompatible<T>> extends Disposable {
[__RPC_STUB_BRAND]: T;
dup(): this;
onRpcBroken(callback: (error: any) => void): void;
}
export type Stub<T extends Serializable<T>> =
export type Stub<T extends RpcCompatible<T>> =
T extends object ? Provider<T> & StubBase<T> : StubBase<T>;

type TypedArray =
Expand Down Expand Up @@ -125,15 +125,15 @@ type MaybeDisposable<T> = T extends object ? Disposable : unknown;

// Type for method return or property on an RPC interface.
// - Stubable types are replaced by stubs.
// - Serializable types are passed by value, with stubable types replaced by stubs
// - RpcCompatible types are passed by value, with stubable types replaced by stubs
// and a top-level `Disposer`.
// Everything else can't be passed over RPC.
// Technically, we use custom thenables here, but they quack like `Promise`s.
// Intersecting with `(Maybe)Provider` allows pipelining.
// prettier-ignore
type Result<R> =
R extends Stubable ? Promise<Stub<R>> & Provider<R> & StubBase<R>
: R extends Serializable<R> ? Promise<Stubify<R> & MaybeDisposable<R>> & Provider<R> & StubBase<R>
: R extends RpcCompatible<R> ? Promise<Stubify<R> & MaybeDisposable<R>> & Provider<R> & StubBase<R>
: never;

// Type for method or property on an RPC interface.
Expand Down
Loading