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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ Unfortunately, garbage collection does not work well when remote resources are i

1. Many JavaScript runtimes only run the garbage collector when they sense "memory pressure" -- if memory is not running low, then they figure there's no need to try to reclaim any. However, the runtime has no way to know if the other side of an RPC connection is suffering memory pressure.

2. Garbage collectors need to trace the full object graph in order to detect which objects are unreachable, especially when those objects contain cyclic refereces. However, the garbage collector can only see local objects; it has no ability to trace through the remote graph to discover cycles that may cross RPC connections.
2. Garbage collectors need to trace the full object graph in order to detect which objects are unreachable, especially when those objects contain cyclic references. However, the garbage collector can only see local objects; it has no ability to trace through the remote graph to discover cycles that may cross RPC connections.

Both of these problems might be solvable with sufficient work, but the problem seems exceedingly difficult. We make no attempt to solve it in this library.

Expand Down Expand Up @@ -535,7 +535,7 @@ A server on Node.js is a bit more involved, due to the awkward handling of WebSo
```ts
import http from "node:http";
import { WebSocketServer } from 'ws'; // npm package
import { RpcTarget, newWebSocketRpcSession, nodeHttpBatchRpcResponse } from "capnpweb";
import { RpcTarget, newWebSocketRpcSession, nodeHttpBatchRpcResponse } from "capnweb";

class MyApiImpl extends RpcTarget implements MyApi {
// ... define API, same as above ...
Expand Down
8 changes: 4 additions & 4 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ describe("basic rpc", () => {
// object's version of that property. We really want to generate messages sent to the other
// end to access the remote version, but there's no legitimate way to do this via the JS-level
// API. Fortunately, our transport implements a hack: the string "$remove$" will be excised
// from any message. So, we can use this as a prefix on property names to create a prpoperty
// from any message. So, we can use this as a prefix on property names to create a property
// that does not match anything locally, but by the time it reaches the remote end, will name
// a common object property.

Expand Down Expand Up @@ -1073,7 +1073,7 @@ describe("stub disposal over RPC", () => {
[Symbol.dispose]() { targetDisposed = true; }
}

// Intentionally dont use `using` here because we expect the stats to be wrong after a
// Intentionally don't use `using` here because we expect the stats to be wrong after a
// disconnect.
let harness = new TestHarness(new DisposableTarget());
let stub = harness.stub;
Expand All @@ -1096,7 +1096,7 @@ describe("stub disposal over RPC", () => {
});

it("shuts down the connection if the main capability is disposed", async () => {
// Intentionally dont use `using` here because we expect the stats to be wrong after a
// Intentionally don't use `using` here because we expect the stats to be wrong after a
// disconnect.
let harness = new TestHarness(new TestTarget());
let stub = harness.stub;
Expand Down Expand Up @@ -1250,7 +1250,7 @@ describe("onRpcBroken", () => {
throwError(): Promise<Counter> { throw new Error("test error"); }
}

// Intentionally dont use `using` here because we expect the stats to be wrong after a
// Intentionally don't use `using` here because we expect the stats to be wrong after a
// disconnect.
let harness = new TestHarness(new TestBroken());
let stub = harness.stub;
Expand Down
2 changes: 1 addition & 1 deletion __tests__/test-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function setup(project: TestProject) {

// Provide the server address to tests.
//
// We use the Node-specific `url.format` here becaues it automatically handles adding brackets to
// We use the Node-specific `url.format` here because it automatically handles adding brackets to
// IPv6 addresses. Unfortunately, the standard `URL` class doesn't seem to provide this.
project.provide("testServerHost", url.format({hostname: addr.address, port: addr.port}));
}
Expand Down
8 changes: 4 additions & 4 deletions src/batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ class BatchClientTransport implements RpcTransport {
async #scheduleBatch(sendBatch: SendBatchFunc) {
// Wait for microtask queue to clear before sending a batch.
//
// Note that simply waiting for one turn of the microtask qeueue (await Promise.resolve()) is
// Note that simply waiting for one turn of the microtask queue (await Promise.resolve()) is
// not good enough here as the application needs a chance to call `.then()` on every RPC
// promise in order to explicitly indicate they want the results. Unfortuntaely, `await`ing
// promise in order to explicitly indicate they want the results. Unfortunately, `await`ing
// a thenable does not call `.then()` immediately -- for some reason it waits for a turn of
// the microtask queue first, *then* calls `.then()`.
await new Promise(resolve => setTimeout(resolve, 0));
Expand Down Expand Up @@ -132,7 +132,7 @@ class BatchServerTransport implements RpcTransport {
*
* @param request The request received from the client initiating the session.
* @param localMain The main stub or RpcTarget which the server wishes to expose to the client.
* @param options Optional RPC sesison options.
* @param options Optional RPC session options.
* @returns The HTTP response to return to the client. Note that the returned object has mutable
* headers, so you can modify them using e.g. `response.headers.set("Foo", "bar")`.
*/
Expand Down Expand Up @@ -169,7 +169,7 @@ export async function newHttpBatchRpcResponse(
* @param request The request received from the client initiating the session.
* @param response The response object, to which the response should be written.
* @param localMain The main stub or RpcTarget which the server wishes to expose to the client.
* @param options Optional RPC sesison options. You can also pass headers to set on the response.
* @param options Optional RPC session options. You can also pass headers to set on the response.
*/
export async function nodeHttpBatchRpcResponse(
request: IncomingMessage, response: ServerResponse,
Expand Down
26 changes: 13 additions & 13 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export abstract class StubHook {
// Called to prevent this stub from generating unhandled rejection events if it throws without
// having been pulled. Without this, if a client "push"es a call that immediately throws before
// the client manages to "pull" it or use it in a pipeline, this may be treated by the system as
// an unhandled rejection. Unfortuntaely, this unhandled rejection would be reported in the
// an unhandled rejection. Unfortunately, this unhandled rejection would be reported in the
// callee rather than the caller, possibly causing the callee to crash or log spurious errors,
// even though it's really up to the caller to deal with the exception!
abstract ignoreUnhandledRejections(): void;
Expand Down Expand Up @@ -472,11 +472,11 @@ export function unwrapStubTakingOwnership(stub: RpcStub): StubHook {
}
}

// Given a stub (still wrapped in a Proxy), extract the underlying `StubHook`, and dulpicate it,
// Given a stub (still wrapped in a Proxy), extract the underlying `StubHook`, and duplicate it,
// returning the duplicate.
//
// The caller is responsible for disposing the returned hook, but the original stub also still
// needs to be disposed by its owner (unless it is a proprety, which never needs disposal).
// needs to be disposed by its owner (unless it is a property, which never needs disposal).
//
// The result is a promise (i.e. can be pull()ed) if and only if the input is a promise. Note that
// this differs from the semantics of the actual `dup()` method.
Expand All @@ -493,7 +493,7 @@ export function unwrapStubAndDup(stub: RpcStub): StubHook {
// Unwrap a stub returning the underlying `StubHook`, returning `undefined` if it is a property
// stub.
//
// This function is agnostic to ownership transfer. Excatly one of `stub` or the return `hook` must
// This function is agnostic to ownership transfer. Exactly one of `stub` or the return `hook` must
// eventually be disposed (unless `undefined` is returned, in which case neither need to be
// disposed, as properties are not normally disposable).
export function unwrapStubNoProperties(stub: RpcStub): StubHook | undefined {
Expand All @@ -507,17 +507,17 @@ export function unwrapStubNoProperties(stub: RpcStub): StubHook | undefined {
}

// Unwrap a stub returning the underlying `StubHook`. If it's a property, return the `StubHook`
// representing the stub or promise of which is is a proprety.
// representing the stub or promise of which is is a property.
//
// This function is agnostic to ownership transfer. Excatly one of `stub` or the return `hook` must
// This function is agnostic to ownership transfer. Exactly one of `stub` or the return `hook` must
// eventually be disposed.
export function unwrapStubOrParent(stub: RpcStub): StubHook {
return stub[RAW_STUB].hook;
}

// Given a stub (still wrapped in a Proxy), extract the `hook` and `pathIfPromise` properties.
//
// This function is agnostic to ownership transfer. Excatly one of `stub` or the return `hook` must
// This function is agnostic to ownership transfer. Exactly one of `stub` or the return `hook` must
// eventually be disposed.
export function unwrapStubAndPath(stub: RpcStub): {hook: StubHook, pathIfPromise?: PropertyPath} {
return stub[RAW_STUB];
Expand All @@ -530,7 +530,7 @@ async function pullPromise(promise: RpcPromise): Promise<unknown> {
if (pathIfPromise!.length > 0) {
// If this isn't the root promise, we have to clone it and pull the clone. This is a little
// weird in terms of disposal: There's no way for the app to dispose/cancel the promise while
// waiting becaues it never actually got a direct disposable reference. It has to dispose
// waiting because it never actually got a direct disposable reference. It has to dispose
// the result.
hook = hook.get(pathIfPromise!);
}
Expand Down Expand Up @@ -616,7 +616,7 @@ export class RpcPayload {
// Create a payload from a value return from an RPC implementation by the app.
//
// Unlike fromAppParams(), in this case the payload takes ownership of all stubs in `value`, and
// may hold onto `value` for an arbitarily long time (e.g. to serve pipelined requests). It
// may hold onto `value` for an arbitrarily long time (e.g. to serve pipelined requests). It
// will still avoid modifying `value` and will make a deep copy if it is delivered locally.
public static fromAppReturn(value: unknown): RpcPayload {
return new RpcPayload(value, "return");
Expand Down Expand Up @@ -687,15 +687,15 @@ export class RpcPayload {
// The payload value.
public value: unknown,

// What is the provinance of `value`?
// What is the provenance of `value`?
// "params": It came from the app, in params to a call. We must dupe any stubs within.
// "return": It came from the app, returned from a call. We take ownership of all stubs within.
// "owned": This value belongs fully to us, either because it was deserialized from the wire
// or because we deep-copied a value from the app.
private source: "params" | "return" | "owned",

// `stubs` and `promises` are filled in only if `value` belongs to us (`source` is "owned") and
// so can safely be delivered to the app. If `value` came from thne app in the first place,
// so can safely be delivered to the app. If `value` came from then app in the first place,
// then it cannot be delivered back to the app nor modified by us without first deep-copying
// it. `stubs` and `promises` will be computed as part of the deep-copy.

Expand All @@ -707,7 +707,7 @@ export class RpcPayload {
private promises?: LocatedPromise[]
) {}

// For `soruce === "return"` payloads only, this tracks any StubHooks created around RpcTargets
// For `source === "return"` payloads only, this tracks any StubHooks created around RpcTargets
// found in the payload at the time that it is serialized (or deep-copied) for return, so that we
// can make sure they are not disposed before the pipeline ends.
//
Expand Down Expand Up @@ -1214,7 +1214,7 @@ function followPath(value: unknown, parent: object | undefined,
break;

case "array":
// For arrays, restricrt specifically to numeric indexes, to be consistent with
// For arrays, restrict specifically to numeric indexes, to be consistent with
// serialization, which only sends a flat list.
if (Number.isInteger(part) && <number>part >= 0) {
value = (<any>value)[part];
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const RpcStub: {
/**
* Represents the result of an RPC call.
*
* Also used to represent propreties. That is, `stub.foo` evaluates to an `RpcPromise` for the
* Also used to represent properties. That is, `stub.foo` evaluates to an `RpcPromise` for the
* value of `foo`.
*
* This isn't actually a JavaScript `Promise`. It does, however, have `then()`, `catch()`, and
Expand Down Expand Up @@ -81,7 +81,7 @@ export const RpcSession: {
// conditionally being imported from "cloudflare:workers".
/**
* Classes which are intended to be passed by reference and called over RPC must extend
* `RpcTarget`. A class which does not extend `RpcTarget` (and which dosen't have built-in support
* `RpcTarget`. A class which does not extend `RpcTarget` (and which doesn't have built-in support
* from the RPC system) cannot be passed in an RPC message at all; an exception will be thrown.
*
* Note that on Cloudflare Workers, this `RpcTarget` is an alias for the one exported from the
Expand Down Expand Up @@ -144,7 +144,7 @@ export let newMessagePortRpcSession:<T extends Serializable<T> = Empty>
export async function newWorkersRpcResponse(request: Request, localMain: any) {
if (request.method === "POST") {
let response = await newHttpBatchRpcResponse(request, localMain);
// Since we're exposing the same API over WebSocket, too, and WebScoket always allows
// Since we're exposing the same API over WebSocket, too, and WebSocket always allows
// cross-origin requests, the API necessarily must be safe for cross-origin use (e.g. because
// it uses in-band authorization, as recommended in the readme). So, we might as well allow
// batch requests to be made cross-origin as well.
Expand Down
6 changes: 3 additions & 3 deletions src/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ mapImpl.sendMap = (hook: StubHook, path: PropertyPath, func: (promise: RpcPromis

// Detect misuse: Map callbacks cannot be async.
if (result instanceof Promise) {
// Squelch unhanlded rejections from the map function itself -- it'll probably just throw
// Squelch unhandled rejections from the map function itself -- it'll probably just throw
// something about pulling a MapVariableHook.
result.catch(err => {});

Expand Down Expand Up @@ -205,12 +205,12 @@ class MapVariableHook extends StubHook {

// Other methods should never be called.
call(path: PropertyPath, args: RpcPayload): StubHook {
// Can't be called; all calls are incercepted.
// Can't be called; all calls are intercepted.
throwMapperBuilderUseError();
}

map(path: PropertyPath, captures: StubHook[], instructions: unknown[]): StubHook {
// Can't be called; all map()s are incercepted.
// Can't be called; all map()s are intercepted.
throwMapperBuilderUseError();
}

Expand Down
2 changes: 1 addition & 1 deletion src/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ class RpcMainHook extends RpcImportHook {
export type RpcSessionOptions = {
/**
* If provided, this function will be called whenever an `Error` object is serialized (for any
* resaon, not just because it was thrown). This can be used to log errors, and also to redact
* reason, not just because it was thrown). This can be used to log errors, and also to redact
* them.
*
* If `onSendError` returns an Error object, than object will be substituted in place of the
Expand Down
2 changes: 1 addition & 1 deletion src/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export class Devaluator {

// TODO:
// - Determine type by checking prototype rather than `name`, which can be overridden?
// - Serialize cause / supressed error / etc.
// - Serialize cause / suppressed error / etc.
// - Serialize added properties.

let rewritten = this.exporter.onSendError(e);
Expand Down
2 changes: 1 addition & 1 deletion src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ type BaseType =
| Request
| Response
| Headers;
// Recursively rewrite all `Stubable` types with `Stub`s, and resolve promsies.
// Recursively rewrite all `Stubable` types with `Stub`s, and resolve promises.
// prettier-ignore
export type Stubify<T> =
T extends Stubable ? Stub<T>
Expand Down