diff --git a/esbuild.mjs b/esbuild.mjs index 619b536..dc021cf 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -1,12 +1,12 @@ import { build } from "esbuild"; -function config({ format, minify, ext = "js" }) { +function config({ format, minify, input, ext = "js" }) { const dir = `dist/${format}/`; const minifierSuffix = minify ? ".min" : ""; const globalName = format === "iife" ? "Synclink" : undefined; return build({ - entryPoints: [`./src/synclink.ts`], - outfile: `${dir}/synclink${minifierSuffix}.${ext}`, + entryPoints: [`./src/${input}.ts`], + outfile: `${dir}/${input}${minifierSuffix}.${ext}`, bundle: true, minify, keepNames: true, @@ -17,10 +17,14 @@ function config({ format, minify, ext = "js" }) { } [ - { format: "esm", minify: false, ext: "mjs" }, - { format: "esm", minify: true, ext: "mjs" }, - { format: "iife", minify: false }, - { format: "iife", minify: true }, - { format: "cjs", minify: false }, - { format: "cjs", minify: true }, + { input: "synclink", format: "esm", minify: false, ext: "mjs" }, + { input: "synclink", format: "esm", minify: true, ext: "mjs" }, + { input: "synclink", format: "iife", minify: false }, + { input: "synclink", format: "iife", minify: true }, + { input: "synclink", format: "cjs", minify: false }, + { input: "synclink", format: "cjs", minify: true }, + { input: "node-adapter", format: "esm", minify: false, ext: "mjs" }, + { input: "node-adapter", format: "esm", minify: true, ext: "mjs" }, + { input: "node-adapter", format: "cjs", minify: false }, + { input: "node-adapter", format: "cjs", minify: true }, ].map(config); diff --git a/package.json b/package.json index 24e9fa4..3df335f 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,14 @@ "types": "dist/types/synclink.d.ts", "exports": { ".": { + "types": "./dist/types/synclink.d.ts", "require": "./dist/cjs/synclink.js", "import": "./dist/esm/synclink.mjs" + }, + "./*": { + "types": "./dist/types/*.d.ts", + "require": "./dist/cjs/*.js", + "import": "./dist/esm/*.mjs" } }, "sideEffects": false, @@ -17,6 +23,7 @@ "rimraf": "rimraf ./dist", "tsc": "tsc --outDir ./dist/types", "test:unit": "karma start", + "test:node": "mocha ./tests/node/main.mjs", "test:types": "tsc -p ./tests/tsconfig.json", "test:types:watch": "npm run test:types -- --watch", "test": "npm run build && npm run test:types && npm run test:unit", @@ -37,7 +44,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "chai": "4.2.0", + "chai": "^4.3.7", "conditional-type-checks": "1.0.5", "esbuild": "^0.17.15", "husky": "4.2.5", @@ -50,9 +57,9 @@ "karma-mocha": "^2.0.1", "karma-safari-launcher": "1.0.0", "karma-safaritechpreview-launcher": "2.0.2", - "mocha": "^7.2.0", + "mocha": "^10.2.0", "prettier": "2.0.5", "rimraf": "3.0.2", - "typescript": "4.8" + "typescript": "4.9.4" } } diff --git a/src/task.ts b/src/task.ts index df2a6df..9a6cfca 100644 --- a/src/task.ts +++ b/src/task.ts @@ -365,6 +365,9 @@ class _Syncifier { pollTasks(task?: SynclinkTask): boolean { let result = false; + if (!task && this.tasks.size < 1) { + return true; + } for (let wokenTaskId of this.tasksIdsToWakeup()) { // console.log("poll task", wokenTaskId, "looking for",task); let wokenTask = this.tasks.get(wokenTaskId); @@ -398,7 +401,9 @@ export let Syncifier = new _Syncifier(); (async function syncifyPollLoop() { while (true) { - Syncifier.pollTasks(); + if (Syncifier.pollTasks()) { + return; + } await sleep(20); } })(); diff --git a/tests/node/main.mjs b/tests/node/main.mjs new file mode 100644 index 0000000..9f4061c --- /dev/null +++ b/tests/node/main.mjs @@ -0,0 +1,55 @@ +import { Worker, MessageChannel } from "node:worker_threads"; +import * as Synclink from "../../dist/esm/synclink.mjs"; +import nodeEndpoint from "../../dist/esm/node-adapter.mjs"; +import { expect } from "chai"; + +describe("node", () => { + describe("Synclink across workers", function () { + beforeEach(function () { + const worker = new Worker("./tests/node/worker.mjs"); + this.worker = worker; + }); + + afterEach(async function () { + await this.worker.terminate(); + }); + + it("can communicate", async function () { + const proxy = Synclink.wrap(nodeEndpoint(this.worker)); + expect(await proxy(1, 3)).to.equal(4); + }); + + it("can work with objects + syncify in same thread", function () { + let fakePort1, fakePort2; + const { port1, port2 } = new MessageChannel(); + ({ port1: fakePort1, port2: fakePort2 } = + new Synclink.FakeMessageChannel()); + port1.start(); + port2.start(); + const thing = Synclink.wrap(fakePort1); + Synclink.expose( + { + value: 4, + func(n) { + return n + 10; + }, + }, + fakePort2, + ); + expect(thing.value.syncify()).to.equal(4); + expect(thing.func(5).syncify()).to.equal(15); + }); + + it("can communicate synchronously", async function () { + const proxy = Synclink.wrap(nodeEndpoint(this.worker)); + expect(proxy(2, 4).syncify()).to.equal(6); + }); + + it("can tunnels a new endpoint with createEndpoint", async function () { + const proxy = Synclink.wrap(nodeEndpoint(this.worker)); + const otherEp = await proxy[Synclink.createEndpoint](); + const otherProxy = Synclink.wrap(otherEp); + expect(await otherProxy(20, 1)).to.equal(21); + }); + }); +}); diff --git a/tests/node/worker.mjs b/tests/node/worker.mjs new file mode 100644 index 0000000..aaae177 --- /dev/null +++ b/tests/node/worker.mjs @@ -0,0 +1,5 @@ +import { parentPort } from "node:worker_threads"; +import * as Synclink from "../../dist/esm/synclink.mjs"; +import nodeEndpoint from "../../dist/esm/node-adapter.mjs"; + +Synclink.expose((a, b) => Promise.resolve(a + b), nodeEndpoint(parentPort)); diff --git a/tests/worker.comlink.test.js b/tests/worker.comlink.test.js index 6b62dea..0d0e914 100644 --- a/tests/worker.comlink.test.js +++ b/tests/worker.comlink.test.js @@ -27,6 +27,13 @@ describe("Synclink across workers", function () { expect(await proxy(1, 3)).to.equal(4); }); + it("returns type error with syncify in main thread", async function () { + const proxy = Synclink.wrap(this.worker); + expect(() => { + proxy(2, 4).syncify(); + }).to.throw("Atomics.wait cannot be called in this context"); + }); + it("can tunnels a new endpoint with createEndpoint", async function () { const proxy = Synclink.wrap(this.worker); const otherEp = await proxy[Synclink.createEndpoint]();