diff --git a/packages/core/src/Injector.ts b/packages/core/src/Injector.ts index 935cbb4..423824a 100644 --- a/packages/core/src/Injector.ts +++ b/packages/core/src/Injector.ts @@ -120,6 +120,10 @@ export function provide< getOwnPropertyDescriptor(target, prop) { if (prop === inject) return { configurable: true }; return Reflect.getOwnPropertyDescriptor(target, prop); + }, + has(target, prop) { + if (prop === inject) return true; + return Reflect.has(target, prop); } }) as InjectableFunction; } diff --git a/packages/core/src/JsObject.ts b/packages/core/src/JsObject.ts index 4ca88a8..919ca2a 100644 --- a/packages/core/src/JsObject.ts +++ b/packages/core/src/JsObject.ts @@ -122,7 +122,7 @@ export function has( value: object, property: Prop ): value is Record { - return Object.hasOwn(value, property); + return property in value; } /** diff --git a/packages/core/src/JsPromise.test.ts b/packages/core/src/JsPromise.test.ts new file mode 100644 index 0000000..1858ae8 --- /dev/null +++ b/packages/core/src/JsPromise.test.ts @@ -0,0 +1,11 @@ +import { expect, test } from "bun:test"; + +import * as JsPromise from "./JsPromise.js"; + +test("checking if a promise is a promise", () => { + expect(JsPromise.isPromise(Promise.resolve("👌"))).toBe(true); +}); + +test("checking if a value with a then method is a promise", () => { + expect(JsPromise.isPromise({ then() {} })).toBe(true); +}); diff --git a/packages/core/src/Protocol/ClientAgent.ts b/packages/core/src/Protocol/ClientAgent.ts index 5c1bd93..96bc4c6 100644 --- a/packages/core/src/Protocol/ClientAgent.ts +++ b/packages/core/src/Protocol/ClientAgent.ts @@ -179,6 +179,14 @@ export class ClientAgent extends Fiber.t { return Reflect.getOwnPropertyDescriptor(target, property); } }, + has(target, property) { + switch (property) { + case Metadata.symbol: + return true; + default: + return Reflect.has(target, property); + } + }, ownKeys(_target) { // Prevents enumeration of the proxy. Attempting to enumerate the proxy // will throw a `TypeError`, this includes using the rest syntax when diff --git a/packages/core/src/Protocol/Session.ts b/packages/core/src/Protocol/Session.ts index 54ddf2b..30b28da 100644 --- a/packages/core/src/Protocol/Session.ts +++ b/packages/core/src/Protocol/Session.ts @@ -232,8 +232,8 @@ export class ServerSession extends Session { * type safety. */ type Subprotocol = Subprotocol.t< - JsFunction.Input>, - JsFunction.Output> + JsFunction.Input>>, + Awaited>>> >; interface Options { @@ -316,6 +316,14 @@ type Local = T extends (...args: infer Args) => infer Return */ type MaybePromise = Awaited | Promise>; +/** + * Distributes over a union of function types and omits their injected + * dependencies. + */ +type OmitDependencies = T extends Injector.InjectableFunction + ? Injector.InjectedFunction + : T; + /** * Represents a remote value. A remote value is the inverse of a local value. */ diff --git a/packages/core/src/Protocol/Session.typetest.ts b/packages/core/src/Protocol/Session.typetest.ts index 109c035..e75b81f 100644 --- a/packages/core/src/Protocol/Session.typetest.ts +++ b/packages/core/src/Protocol/Session.typetest.ts @@ -1,8 +1,8 @@ -import * as Injector from '../Injector.js'; -import * as Json from '../Json.js'; -import * as Proxy from '../Proxy.js'; -import * as Session from './Session.js'; -import * as Subprotocol from './Subprotocol.js'; +import * as Injector from "../Injector.js"; +import * as Json from "../Json.js"; +import * as Proxy from "../Proxy.js"; +import * as Session from "./Session.js"; +import * as Subprotocol from "./Subprotocol.js"; const protocol = Subprotocol.init({ connectionMode: Subprotocol.ConnectionMode.ConnectionOriented, @@ -48,6 +48,12 @@ test("remote function returning object", () => { // ^? const _proxy: RemoteFunction<() => {}> }); +test("remote function returning a promise", () => { + const session = Session.client<() => Promise<{ cat: "🐈" }>>({ protocol }); + const _proxy = session.createProxy(); + // ^? const _proxy: RemoteFunction<() => Promise<{ cat: "🐈"; }>> +}); + test("remote function returning function", async () => { const session = Session.client<() => () => null>({ protocol }); const proxy = session.createProxy(); @@ -272,6 +278,20 @@ test("an injected dependency is removed from the call signature", () => { // ^? const _proxy: RemoteFunction<(a: boolean) => null> }); +test("an injected dependency is excluded from protocol type checking", () => { + type Context = { clientId: string }; + + const session = Session.client< + Injector.InjectableFunction< + (context: Context | undefined, a: boolean) => null, + [Injector.Tag] + > + >({ protocol }); + + const _proxy = session.createProxy(); + // ^? const _proxy: RemoteFunction<(a: boolean) => null> +}); + test("returning a proxy from a function", () => { class Foo { a!: number; diff --git a/packages/core/src/Proxy.ts b/packages/core/src/Proxy.ts index 66d0195..29bce6a 100644 --- a/packages/core/src/Proxy.ts +++ b/packages/core/src/Proxy.ts @@ -23,6 +23,10 @@ export function proxy(value: T) { getOwnPropertyDescriptor(target, prop) { if (prop === symbol) return { configurable: true }; return Reflect.getOwnPropertyDescriptor(target, prop); + }, + has(target, prop) { + if (prop === symbol) return true; + return Reflect.has(target, prop); } }) as Proxy; }