From 02999424382a7c531bcc2c0485d4ebbe76853d62 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 13 Aug 2023 17:30:35 +0200 Subject: [PATCH] feat: add websocket server hybrid pool example MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- .eslintrc.js | 2 + .github/dependabot.yml | 16 +- CHANGELOG.md | 6 +- README.md | 1 + .../typescript/http-client-pool/tsconfig.json | 4 +- .../fastify-cluster/src/main.ts | 4 +- .../fastify-hybrid/src/main.ts | 4 +- .../ws-cluster/src/main.ts | 7 +- .../ws-cluster/src/worker.ts | 2 +- .../ws-hybrid/package.json | 38 ++ .../ws-hybrid/pnpm-lock.yaml | 501 ++++++++++++++++++ .../ws-hybrid/requests.js | 21 + .../ws-hybrid/rollup.config.mjs | 36 ++ .../ws-hybrid/src/main.ts | 38 ++ .../ws-hybrid/src/request-handler-pool.ts | 26 + .../ws-hybrid/src/request-handler-worker.ts | 36 ++ .../ws-hybrid/src/types.ts | 30 ++ .../ws-hybrid/src/websocket-server-worker.ts | 80 +++ .../ws-hybrid/tsconfig.json | 15 + 19 files changed, 853 insertions(+), 14 deletions(-) create mode 100644 examples/typescript/websocket-server-pool/ws-hybrid/package.json create mode 100644 examples/typescript/websocket-server-pool/ws-hybrid/pnpm-lock.yaml create mode 100644 examples/typescript/websocket-server-pool/ws-hybrid/requests.js create mode 100644 examples/typescript/websocket-server-pool/ws-hybrid/rollup.config.mjs create mode 100644 examples/typescript/websocket-server-pool/ws-hybrid/src/main.ts create mode 100644 examples/typescript/websocket-server-pool/ws-hybrid/src/request-handler-pool.ts create mode 100644 examples/typescript/websocket-server-pool/ws-hybrid/src/request-handler-worker.ts create mode 100644 examples/typescript/websocket-server-pool/ws-hybrid/src/types.ts create mode 100644 examples/typescript/websocket-server-pool/ws-hybrid/src/websocket-server-worker.ts create mode 100644 examples/typescript/websocket-server-pool/ws-hybrid/tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js index a5382d80..e2fc35b2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -63,6 +63,7 @@ module.exports = defineConfig({ 'fp', 'fs', 'inheritDoc', + 'javascript', 'jsdoc', 'localhost', 'microjob', @@ -75,6 +76,7 @@ module.exports = defineConfig({ 'pnpm', 'poolifier', 'poolify', + 'readdir', 'readonly', 'req', 'resize', diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6c957f88..34c566ed 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -69,7 +69,7 @@ updates: - 'jerome-benoit' versioning-strategy: increase - package-ecosystem: 'npm' - directory: '/examples/typescript/http-server-pool/fastify-cluster' + directory: '/examples/typescript/http-server-pool/fastify-hybrid' schedule: interval: 'daily' labels: @@ -81,7 +81,7 @@ updates: - 'jerome-benoit' versioning-strategy: increase - package-ecosystem: 'npm' - directory: '/examples/typescript/http-server-pool/fastify-hybrid' + directory: '/examples/typescript/http-server-pool/fastify-cluster' schedule: interval: 'daily' labels: @@ -104,6 +104,18 @@ updates: - 'pioardi' - 'jerome-benoit' versioning-strategy: increase + - package-ecosystem: 'npm' + directory: '/examples/typescript/websocket-server-pool/ws-hybrid' + schedule: + interval: 'daily' + labels: + - 'dependencies' + - 'examples' + - 'nocombine' + reviewers: + - 'pioardi' + - 'jerome-benoit' + versioning-strategy: increases - package-ecosystem: 'npm' directory: '/examples/typescript/websocket-server-pool/ws-cluster' schedule: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c5e44f9..9bb8890c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,14 +10,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - HTTP server pool examples: fastify-cluster, fastify-hybrid. -- WebSocket server pool examples: ws-cluster +- WebSocket server pool examples: ws-cluster, ws-hybrid. ## [2.6.24] - 2023-08-12 ### Added - Add array of transferable objects to the `execute()` method arguments. -- WebSocket server pool examples: ws +- WebSocket server pool examples: ws-worker_threads. ## [2.6.23] - 2023-08-11 @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - HTTP client pool examples: fetch, node-fetch and axios with multiple task functions. -- HTTP server pool examples: express, fastify. +- HTTP server pool examples: express-worker_threads, fastify-worker_threads. ## [2.6.22] - 2023-08-10 diff --git a/README.md b/README.md index f2f67d83..50a2227c 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ You can do the same with the classes _ClusterWorker_, _FixedClusterPool_ and _Dy - [WebSocket server pool](./examples/typescript/websocket-server-pool/) - [ws worker_threads pool](./examples/typescript/websocket-server-pool/ws-worker_threads/) - [ws cluster pool](./examples/typescript/websocket-server-pool/ws-cluster/) + - [ws hybrid pool](./examples/typescript/websocket-server-pool/ws-hybrid/) Remember that workers can only send and receive structured-cloneable data. diff --git a/examples/typescript/http-client-pool/tsconfig.json b/examples/typescript/http-client-pool/tsconfig.json index c070ed24..57e49a1c 100644 --- a/examples/typescript/http-client-pool/tsconfig.json +++ b/examples/typescript/http-client-pool/tsconfig.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { - "target": "es2022", - "module": "es2022", + "target": "ES2022", + "module": "ES2022", "moduleResolution": "Node16", "verbatimModuleSyntax": true, "rootDir": "./src", diff --git a/examples/typescript/http-server-pool/fastify-cluster/src/main.ts b/examples/typescript/http-server-pool/fastify-cluster/src/main.ts index 0ac8f5ce..b462d8c5 100644 --- a/examples/typescript/http-server-pool/fastify-cluster/src/main.ts +++ b/examples/typescript/http-server-pool/fastify-cluster/src/main.ts @@ -19,13 +19,13 @@ const pool = new FixedClusterPool( if (response.status) { console.info( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Fastify is listening on worker on port ${response.port}` + `Fastify is listening on cluster worker on port ${response.port}` ) } return null }) .catch(error => { - console.error('Fastify failed to start on worker:', error) + console.error('Fastify failed to start on cluster worker:', error) }) }, errorHandler: (e: Error) => { diff --git a/examples/typescript/http-server-pool/fastify-hybrid/src/main.ts b/examples/typescript/http-server-pool/fastify-hybrid/src/main.ts index 8a17f893..795a358b 100644 --- a/examples/typescript/http-server-pool/fastify-hybrid/src/main.ts +++ b/examples/typescript/http-server-pool/fastify-hybrid/src/main.ts @@ -19,13 +19,13 @@ const pool = new FixedClusterPool( if (response.status) { console.info( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Fastify is listening on worker on port ${response.port}` + `Fastify is listening on cluster worker on port ${response.port}` ) } return null }) .catch(error => { - console.error('Fastify failed to start on worker:', error) + console.error('Fastify failed to start on cluster worker:', error) }) }, errorHandler: (e: Error) => { diff --git a/examples/typescript/websocket-server-pool/ws-cluster/src/main.ts b/examples/typescript/websocket-server-pool/ws-cluster/src/main.ts index 9a77c055..97124326 100644 --- a/examples/typescript/websocket-server-pool/ws-cluster/src/main.ts +++ b/examples/typescript/websocket-server-pool/ws-cluster/src/main.ts @@ -19,13 +19,16 @@ const pool = new FixedClusterPool( if (response.status) { console.info( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `WebSocket server is listening on worker on port ${response.port}` + `WebSocket server is listening on cluster worker on port ${response.port}` ) } return null }) .catch(error => { - console.error('WebSocket server failed to start on worker:', error) + console.error( + 'WebSocket server failed to start on cluster worker:', + error + ) }) }, errorHandler: (e: Error) => { diff --git a/examples/typescript/websocket-server-pool/ws-cluster/src/worker.ts b/examples/typescript/websocket-server-pool/ws-cluster/src/worker.ts index b489415f..48c6cc43 100644 --- a/examples/typescript/websocket-server-pool/ws-cluster/src/worker.ts +++ b/examples/typescript/websocket-server-pool/ws-cluster/src/worker.ts @@ -19,7 +19,7 @@ const startWebSocketServer = (workerData?: WorkerData): WorkerResponse => { const { port } = workerData as WorkerData const wss = new WebSocketServer({ port }, () => { console.info( - `⚡️[ws server]: WebSocket server is started on worker at ws://localhost:${port}/` + `⚡️[ws server]: WebSocket server is started on cluster worker at ws://localhost:${port}/` ) }) diff --git a/examples/typescript/websocket-server-pool/ws-hybrid/package.json b/examples/typescript/websocket-server-pool/ws-hybrid/package.json new file mode 100644 index 00000000..2607565a --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws-hybrid/package.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://json.schemastore.org/package", + "name": "ws-hybrid-pool", + "version": "1.0.0", + "description": "ws hybrid pool", + "main": "dist/main.js", + "type": "module", + "volta": { + "node": "20.5.1", + "pnpm": "8.6.12" + }, + "scripts": { + "build": "rollup --config", + "start": "node dist/main.cjs", + "start:esm": "node dist/main.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "poolifier": "^2.6.24", + "ws": "^8.13.0" + }, + "devDependencies": { + "@rollup/plugin-typescript": "^11.1.2", + "@types/node": "^20.4.10", + "@types/ws": "^8.5.5", + "rollup": "^3.28.0", + "rollup-plugin-delete": "^2.0.0", + "tslib": "^2.6.1", + "typescript": "^5.1.6" + }, + "optionalDependencies": { + "bufferutil": "^4.0.7", + "utf-8-validate": "^6.0.3" + } +} diff --git a/examples/typescript/websocket-server-pool/ws-hybrid/pnpm-lock.yaml b/examples/typescript/websocket-server-pool/ws-hybrid/pnpm-lock.yaml new file mode 100644 index 00000000..6485ddbc --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws-hybrid/pnpm-lock.yaml @@ -0,0 +1,501 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + poolifier: + specifier: ^2.6.24 + version: 2.6.24 + ws: + specifier: ^8.13.0 + version: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) + +optionalDependencies: + bufferutil: + specifier: ^4.0.7 + version: 4.0.7 + utf-8-validate: + specifier: ^6.0.3 + version: 6.0.3 + +devDependencies: + '@rollup/plugin-typescript': + specifier: ^11.1.2 + version: 11.1.2(rollup@3.28.0)(tslib@2.6.1)(typescript@5.1.6) + '@types/node': + specifier: ^20.4.10 + version: 20.4.10 + '@types/ws': + specifier: ^8.5.5 + version: 8.5.5 + rollup: + specifier: ^3.28.0 + version: 3.28.0 + rollup-plugin-delete: + specifier: ^2.0.0 + version: 2.0.0 + tslib: + specifier: ^2.6.1 + version: 2.6.1 + typescript: + specifier: ^5.1.6 + version: 5.1.6 + +packages: + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@rollup/plugin-typescript@11.1.2(rollup@3.28.0)(tslib@2.6.1)(typescript@5.1.6): + resolution: {integrity: sha512-0ghSOCMcA7fl1JM+0gYRf+Q/HWyg+zg7/gDSc+fRLmlJWcW5K1I+CLRzaRhXf4Y3DRyPnnDo4M2ktw+a6JcDEg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.14.0||^3.0.0 + tslib: '*' + typescript: '>=3.7.0' + peerDependenciesMeta: + rollup: + optional: true + tslib: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.2(rollup@3.28.0) + resolve: 1.22.4 + rollup: 3.28.0 + tslib: 2.6.1 + typescript: 5.1.6 + dev: true + + /@rollup/pluginutils@5.0.2(rollup@3.28.0): + resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.1 + estree-walker: 2.0.2 + picomatch: 2.3.1 + rollup: 3.28.0 + dev: true + + /@types/estree@1.0.1: + resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} + dev: true + + /@types/glob@7.2.0: + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 20.4.10 + dev: true + + /@types/minimatch@5.1.2: + resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} + dev: true + + /@types/node@20.4.10: + resolution: {integrity: sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg==} + dev: true + + /@types/ws@8.5.5: + resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==} + dependencies: + '@types/node': 20.4.10 + dev: true + + /aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /bufferutil@4.0.7: + resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.0 + dev: false + + /clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /del@5.1.0: + resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==} + engines: {node: '>=8'} + dependencies: + globby: 10.0.2 + graceful-fs: 4.2.11 + is-glob: 4.0.3 + is-path-cwd: 2.2.0 + is-path-inside: 3.0.3 + p-map: 3.0.0 + rimraf: 3.0.2 + slash: 3.0.0 + dev: true + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true + + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /globby@10.0.2: + resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} + engines: {node: '>=8'} + dependencies: + '@types/glob': 7.2.0 + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.1 + glob: 7.2.3 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: true + + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + + /indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} + dependencies: + has: 1.0.3 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-path-cwd@2.2.0: + resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} + engines: {node: '>=6'} + dev: true + + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /node-gyp-build@4.6.0: + resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} + hasBin: true + requiresBuild: true + dev: false + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /p-map@3.0.0: + resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} + engines: {node: '>=8'} + dependencies: + aggregate-error: 3.1.0 + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /poolifier@2.6.24: + resolution: {integrity: sha512-3sofqoocsvz7R6LshWJ1FvnjCsHdKytw29gsrVB7AJ0d7jVuE6dMd7ax2O0U03hg+yhxpeBXOaUGO025b40kjA==} + engines: {node: '>=16.14.0', pnpm: '>=8.6.0'} + requiresBuild: true + dev: false + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /resolve@1.22.4: + resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} + hasBin: true + dependencies: + is-core-module: 2.13.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rollup-plugin-delete@2.0.0: + resolution: {integrity: sha512-/VpLMtDy+8wwRlDANuYmDa9ss/knGsAgrDhM+tEwB1npHwNu4DYNmDfUL55csse/GHs9Q+SMT/rw9uiaZ3pnzA==} + engines: {node: '>=10'} + dependencies: + del: 5.1.0 + dev: true + + /rollup@3.28.0: + resolution: {integrity: sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /tslib@2.6.1: + resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==} + dev: true + + /typescript@5.1.6: + resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /utf-8-validate@6.0.3: + resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.0 + dev: false + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3): + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dependencies: + bufferutil: 4.0.7 + utf-8-validate: 6.0.3 + dev: false diff --git a/examples/typescript/websocket-server-pool/ws-hybrid/requests.js b/examples/typescript/websocket-server-pool/ws-hybrid/requests.js new file mode 100644 index 00000000..1f302a6c --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws-hybrid/requests.js @@ -0,0 +1,21 @@ +// eslint-disable-next-line import/no-unresolved, n/no-missing-import +import { WebSocket } from 'ws' + +const ws = new WebSocket('ws://localhost:8080') + +ws.on('error', console.error) + +ws.on('open', () => { + for (let i = 0; i < 60; i++) { + ws.send( + JSON.stringify({ type: 'echo', data: { key1: 'value1', key2: 'value2' } }) + ) + } + for (let i = 0; i < 60; i++) { + ws.send(JSON.stringify({ type: 'factorial', data: { number: 30 } })) + } +}) + +ws.on('message', message => { + console.info('message received: %s', message) +}) diff --git a/examples/typescript/websocket-server-pool/ws-hybrid/rollup.config.mjs b/examples/typescript/websocket-server-pool/ws-hybrid/rollup.config.mjs new file mode 100644 index 00000000..8082e084 --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws-hybrid/rollup.config.mjs @@ -0,0 +1,36 @@ +/* eslint-disable n/no-unpublished-import */ +import typescript from '@rollup/plugin-typescript' +import del from 'rollup-plugin-delete' + +export default { + input: [ + 'src/main.ts', + 'src/websocket-server-worker.ts', + 'src/request-handler-worker.ts' + ], + strictDeprecations: true, + output: [ + { + format: 'cjs', + dir: 'dist', + sourcemap: true, + entryFileNames: '[name].cjs', + preserveModules: true, + preserveModulesRoot: 'src' + }, + { + format: 'esm', + dir: 'dist', + sourcemap: true, + preserveModules: true, + preserveModulesRoot: 'src' + } + ], + external: ['node:path', 'node:url', 'poolifier', 'ws'], + plugins: [ + typescript(), + del({ + targets: ['dist/*'] + }) + ] +} diff --git a/examples/typescript/websocket-server-pool/ws-hybrid/src/main.ts b/examples/typescript/websocket-server-pool/ws-hybrid/src/main.ts new file mode 100644 index 00000000..ae303015 --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws-hybrid/src/main.ts @@ -0,0 +1,38 @@ +import { dirname, extname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { FixedClusterPool, availableParallelism } from 'poolifier' +import { type ClusterWorkerData, type ClusterWorkerResponse } from './types.js' + +const webSocketServerWorkerFile = join( + dirname(fileURLToPath(import.meta.url)), + `websocket-server-worker${extname(fileURLToPath(import.meta.url))}` +) + +const pool = new FixedClusterPool( + Math.round(availableParallelism() / 2), + webSocketServerWorkerFile, + { + onlineHandler: () => { + pool + .execute({ port: 8080 }) + .then(response => { + if (response.status) { + console.info( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `WebSocket server is listening on cluster worker on port ${response.port}` + ) + } + return null + }) + .catch(error => { + console.error( + 'WebSocket server failed to start on cluster worker:', + error + ) + }) + }, + errorHandler: (e: Error) => { + console.error('Cluster worker error', e) + } + } +) diff --git a/examples/typescript/websocket-server-pool/ws-hybrid/src/request-handler-pool.ts b/examples/typescript/websocket-server-pool/ws-hybrid/src/request-handler-pool.ts new file mode 100644 index 00000000..8fecfba5 --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws-hybrid/src/request-handler-pool.ts @@ -0,0 +1,26 @@ +import { dirname, extname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { DynamicThreadPool, availableParallelism } from 'poolifier' +import { + type DataPayload, + type ThreadWorkerData, + type ThreadWorkerResponse +} from './types.js' + +const requestHandlerWorkerFile = join( + dirname(fileURLToPath(import.meta.url)), + `request-handler-worker${extname(fileURLToPath(import.meta.url))}` +) + +export const requestHandlerPool = new DynamicThreadPool< +ThreadWorkerData, +ThreadWorkerResponse +>(1, Math.round(availableParallelism() / 2), requestHandlerWorkerFile, { + enableTasksQueue: true, + tasksQueueOptions: { + concurrency: 8 + }, + errorHandler: (e: Error) => { + console.error('Thread worker error:', e) + } +}) diff --git a/examples/typescript/websocket-server-pool/ws-hybrid/src/request-handler-worker.ts b/examples/typescript/websocket-server-pool/ws-hybrid/src/request-handler-worker.ts new file mode 100644 index 00000000..40cde50d --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws-hybrid/src/request-handler-worker.ts @@ -0,0 +1,36 @@ +import { ThreadWorker } from 'poolifier' +import { + type DataPayload, + type ThreadWorkerData, + type ThreadWorkerResponse +} from './types.js' + +const factorial: (n: number) => number = n => { + if (n === 0) { + return 1 + } + return factorial(n - 1) * n +} + +class RequestHandlerWorker< + Data extends ThreadWorkerData, + Response extends ThreadWorkerResponse +> extends ThreadWorker { + public constructor () { + super({ + echo: (workerData?: Data) => { + return workerData as unknown as Response + }, + factorial: (workerData?: Data) => { + return { + data: { number: factorial(workerData?.data?.number as number) } + } as unknown as Response + } + }) + } +} + +export const requestHandlerWorker = new RequestHandlerWorker< +ThreadWorkerData, +ThreadWorkerResponse +>() diff --git a/examples/typescript/websocket-server-pool/ws-hybrid/src/types.ts b/examples/typescript/websocket-server-pool/ws-hybrid/src/types.ts new file mode 100644 index 00000000..055b0456 --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws-hybrid/src/types.ts @@ -0,0 +1,30 @@ +export enum MessageType { + echo = 'echo', + factorial = 'factorial' +} + +export interface MessagePayload { + type: MessageType + data: T +} + +export interface DataPayload { + number?: number +} + +export interface ClusterWorkerData { + port: number +} + +export interface ClusterWorkerResponse { + status: boolean + port?: number +} + +export interface ThreadWorkerData { + data: T +} + +export interface ThreadWorkerResponse { + data: T +} diff --git a/examples/typescript/websocket-server-pool/ws-hybrid/src/websocket-server-worker.ts b/examples/typescript/websocket-server-pool/ws-hybrid/src/websocket-server-worker.ts new file mode 100644 index 00000000..dfcba667 --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws-hybrid/src/websocket-server-worker.ts @@ -0,0 +1,80 @@ +import { ClusterWorker } from 'poolifier' +import { type RawData, WebSocketServer } from 'ws' +import { + type ClusterWorkerData, + type ClusterWorkerResponse, + type DataPayload, + type MessagePayload, + MessageType +} from './types.js' +import { requestHandlerPool } from './request-handler-pool.js' + +const emptyFunction = (): void => { + /** Intentional */ +} + +const startWebSocketServer = ( + workerData?: ClusterWorkerData +): ClusterWorkerResponse => { + const { port } = workerData as ClusterWorkerData + const wss = new WebSocketServer({ port }, () => { + console.info( + `⚡️[ws server]: WebSocket server is started on cluster worker at ws://localhost:${port}/` + ) + }) + + wss.on('connection', ws => { + ws.on('error', console.error) + ws.on('message', (message: RawData) => { + const { type, data } = JSON.parse( + // eslint-disable-next-line @typescript-eslint/no-base-to-string + message.toString() + ) as MessagePayload + switch (type) { + case MessageType.echo: + requestHandlerPool + .execute({ data }, 'echo') + .then(response => { + ws.send( + JSON.stringify({ + type: MessageType.echo, + data: response.data + }) + ) + return null + }) + .catch(emptyFunction) + break + case MessageType.factorial: + requestHandlerPool + .execute({ data }, 'factorial') + .then(response => { + ws.send( + JSON.stringify({ + type: MessageType.factorial, + data: response.data + }) + ) + return null + }) + .catch(emptyFunction) + break + } + }) + }) + return { + status: true, + port: wss.options.port + } +} + +class WebSocketServerWorker extends ClusterWorker< +ClusterWorkerData, +ClusterWorkerResponse +> { + public constructor () { + super(startWebSocketServer) + } +} + +export const webSocketServerWorker = new WebSocketServerWorker() diff --git a/examples/typescript/websocket-server-pool/ws-hybrid/tsconfig.json b/examples/typescript/websocket-server-pool/ws-hybrid/tsconfig.json new file mode 100644 index 00000000..57e49a1c --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws-hybrid/tsconfig.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "Node16", + "verbatimModuleSyntax": true, + "rootDir": "./src", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} -- 2.34.1