diff --git a/client.js b/client.js index 32f3c09..b2f3161 100644 --- a/client.js +++ b/client.js @@ -178,6 +178,8 @@ function createClient(channel, { timeout = 5000 } = {}) { emitter.removeAllListeners() } + const subClientCache = new Map() + return createSubClient([], {}) /** @@ -185,6 +187,9 @@ function createClient(channel, { timeout = 5000 } = {}) { * @param {Function | {}} target * @returns {ClientApi} */ function createSubClient(propArray, target) { + const cached = subClientCache.get(JSON.stringify(propArray)) + if (cached) return cached + /** @type {ProxyHandler} */ const handler = { get(target, prop) { @@ -275,6 +280,14 @@ function createClient(channel, { timeout = 5000 } = {}) { const proxy = new Proxy(target, handler) + if (propArray.length > 0) { + // In some ways this is a memory leak, but only if a client tries to + // access large numbers of methods. If the client respects the client + // type, then this is just lazily creating the API object referenced on + // the server. + subClientCache.set(JSON.stringify(propArray), proxy) + } + return proxy } } diff --git a/test/e2e.test.js b/test/e2e.test.js index 4965362..87beb3c 100644 --- a/test/e2e.test.js +++ b/test/e2e.test.js @@ -106,19 +106,21 @@ runTests(function setup(api, opts) { } }) -// Run tests with Duplex Stream -// @ts-ignore -runTests(function setup(api, opts) { - const { socket1, socket2 } = new DuplexPair({ objectMode: true }) - - const serverStream = socket1 - const clientStream = socket2 - - return { - client: createClient(clientStream, opts), - server: createServer(api, serverStream), - } -}) +if (!process.env.TAP_ONLY) { + // Run tests with Duplex Stream + // @ts-ignore + runTests(function setup(api, opts) { + const { socket1, socket2 } = new DuplexPair({ objectMode: true }) + + const serverStream = socket1 + const clientStream = socket2 + + return { + client: createClient(clientStream, opts), + server: createServer(api, serverStream), + } + }) +} /** * @typedef {(api: T, opts?: Parameters[1]) => { client: ClientApi, server: ReturnType}} SetupFunction @@ -551,10 +553,21 @@ function runTests(setup) { t.end() }) - test('Can await client', async (t) => { + test('client properties are stable', (t) => { + // If we don't cache the proxy returned by accessing a property like + // `client.namespace`, then each time you access it a new Proxy will be + // created. + const { client } = setup(myApi) + t.is(client.namespace, client.namespace) + t.is(client.deep.nested, client.deep.nested) + t.end() + }) + + test('Can await client and subclients', async (t) => { const { client } = setup(myApi) - const awaited = await client - t.is(awaited, client, 'Same object is returned when awaiting') + t.is(await client, client, 'Same object is returned when awaiting') + t.is(await client.deep, client.deep) + t.is(await client.deep.nested, client.deep.nested) t.end() }) }