import { assert, Has, NotHas, IsAny, IsExact } from "conditional-type-checks"; import * as Synclink from "../src/synclink.js"; import { SynclinkTask } from "../src/task.js"; async function closureSoICanUseAwait() { { function simpleNumberFunction() { return 4; } const proxy = Synclink.wrap(0 as any); assert>(false); const v = proxy(); assert>>(true); } { function simpleObjectFunction() { return { a: 3 }; } const proxy = Synclink.wrap(0 as any); const v = await proxy(); assert>(true); } { async function simpleAsyncFunction() { return { a: 3 }; } const proxy = Synclink.wrap(0 as any); const v = await proxy(); assert>(true); } { function functionWithProxy() { return Synclink.proxy({ a: 3 }); } const proxy = Synclink.wrap(0 as any); const subproxy = await proxy(); const prop = subproxy.a; assert>>(true); } { class X { static staticFunc() { return 4; } private f = 4; public g = 9; sayHi() { return "hi"; } } const proxy = Synclink.wrap(0 as any); assert SynclinkTask }>>(true); const instance = await new proxy(); assert SynclinkTask }>>(true); assert }>>(true); assert }>>(true); assert>(false); } { const x = { a: 4, b() { return 9; }, c: { d: 3, }, }; const proxy = Synclink.wrap(0 as any); assert>(false); const a = proxy.a; assert>>(true); assert>(false); const b = proxy.b; assert SynclinkTask>>(true); assert>(false); const subproxy = proxy.c; assert>>(true); assert>(false); const copy = await proxy.c; assert>(true); } { Synclink.wrap(new MessageChannel().port1); Synclink.expose({}, new MessageChannel().port2); interface Baz { baz: number; method(): number; } class Foo { constructor(cParam: string) { const self = this; assert>( true, ); } prop1: string = "abc"; proxyProp = Synclink.proxy(new Bar()); methodWithTupleParams(...args: [string] | [number, string]): number { return 123; } methodWithProxiedReturnValue(): Baz & Synclink.ProxyMarked { return Synclink.proxy({ baz: 123, method: () => 123 }); } methodWithProxyParameter(param: Baz & Synclink.ProxyMarked): void {} } class Bar { prop2: string | number = "abc"; method(param: string): number { return 123; } methodWithProxiedReturnValue(): Baz & Synclink.ProxyMarked { return Synclink.proxy({ baz: 123, method: () => 123 }); } } const proxy = Synclink.wrap(Synclink.windowEndpoint(self)); assert>>(true); proxy[Synclink.releaseProxy](); const endp = proxy[Synclink.createEndpoint](); assert>>(true); assert>(false); assert>>(true); const r1 = proxy.methodWithTupleParams(123, "abc"); assert>>(true); const r2 = proxy.methodWithTupleParams("abc"); assert>>(true); assert< IsExact< typeof proxy.proxyProp, Synclink.Remote > >(true); assert>(false); assert>>(true); assert>>(true); const r3 = proxy.proxyProp.method("param"); assert>(false); assert>>(true); // @ts-expect-error proxy.proxyProp.method(123); // @ts-expect-error proxy.proxyProp.method(); const r4 = proxy.methodWithProxiedReturnValue(); assert>(false); assert< IsExact< typeof r4, SynclinkTask> > >(true); const r5 = proxy.proxyProp.methodWithProxiedReturnValue(); assert< IsExact< typeof r5, SynclinkTask> > >(true); const r6 = (await proxy.methodWithProxiedReturnValue()).baz; assert>(false); assert>>(true); const r7 = (await proxy.methodWithProxiedReturnValue()).method(); assert>(false); assert>>(true); const ProxiedFooClass = Synclink.wrap( Synclink.windowEndpoint(self), ); const inst1 = await new ProxiedFooClass("test"); assert>>(true); inst1[Synclink.releaseProxy](); inst1[Synclink.createEndpoint](); // @ts-expect-error await new ProxiedFooClass(123); // @ts-expect-error await new ProxiedFooClass(); // // Tests for advanced proxy use cases // // Type round trips // This tests that Local is the exact inverse of Remote for objects: assert< IsExact< Synclink.Local>, Synclink.ProxyMarked > >(true); // This tests that Local is the exact inverse of Remote for functions, with one difference: // The local version of a remote function can be either implemented as a sync or async function, // because Remote always makes the function async. assert< IsExact< Synclink.Local string>>, (a: number) => string | Promise > >(true); interface Subscriber { closed?: boolean; next?: (value: T) => void; } interface Unsubscribable { unsubscribe(): void; } /** A Subscribable that can get proxied by Synclink */ interface ProxyableSubscribable extends Synclink.ProxyMarked { subscribe( subscriber: Synclink.Remote & Synclink.ProxyMarked>, ): Unsubscribable & Synclink.ProxyMarked; } /** Simple parameter object that gets cloned (not proxied) */ interface Params { textDocument: string; } class Registry { async registerProvider( provider: Synclink.Remote< ((params: Params) => ProxyableSubscribable) & Synclink.ProxyMarked >, ) { const resultPromise = provider({ textDocument: "foo" }); assert< IsExact< typeof resultPromise, SynclinkTask>> > >(true); const result = await resultPromise; const subscriptionPromise = result.subscribe({ [Synclink.proxyMarker]: true, next: (value) => { assert>(true); }, }); assert< IsExact< typeof subscriptionPromise, SynclinkTask> > >(true); const subscriber = Synclink.proxy({ next: (value: string) => console.log(value), }); result.subscribe(subscriber); const r1 = (await subscriptionPromise).unsubscribe(); assert>>(true); } } const proxy2 = Synclink.wrap(Synclink.windowEndpoint(self)); proxy2.registerProvider( // Synchronous callback Synclink.proxy(({ textDocument }: Params) => { const subscribable = Synclink.proxy({ subscribe( subscriber: Synclink.Remote< Subscriber & Synclink.ProxyMarked >, ): Unsubscribable & Synclink.ProxyMarked { // Important to test here is that union types (such as Function | undefined) distribute properly // when wrapped in Promises/proxied assert>(false); assert< IsExact< typeof subscriber.closed, | SynclinkTask | SynclinkTask | SynclinkTask | undefined > >(true); assert>(false); assert< IsExact< typeof subscriber.next, | Synclink.Remote<(value: string) => void> | SynclinkTask | undefined > >(true); // @ts-expect-error subscriber.next(); if (subscriber.next) { // Only checking for presence is not enough, since it could be a Promise // @ts-expect-error subscriber.next(); } if (typeof subscriber.next === "function") { subscriber.next("abc"); } return Synclink.proxy({ unsubscribe() {} }); }, }); assert>(true); return subscribable; }), ); proxy2.registerProvider( // Async callback Synclink.proxy(async ({ textDocument }: Params) => { const subscribable = Synclink.proxy({ subscribe( subscriber: Synclink.Remote< Subscriber & Synclink.ProxyMarked >, ): Unsubscribable & Synclink.ProxyMarked { assert>(false); assert< IsExact< typeof subscriber.next, | Synclink.Remote<(value: string) => void> | SynclinkTask | undefined > >(true); // Only checking for presence is not enough, since it could be a Promise if (typeof subscriber.next === "function") { subscriber.next("abc"); } return Synclink.proxy({ unsubscribe() {} }); }, }); return subscribable; }), ); } // Transfer handlers { const urlTransferHandler: Synclink.TransferHandler = { canHandle: (val): val is URL => { assert>(true); return val instanceof URL; }, serialize: (url) => { assert>(true); return [url.href, []]; }, deserialize: (str) => { assert>(true); return new URL(str); }, }; Synclink.transferHandlers.set("URL", urlTransferHandler); } }