feat: add websocket server hybrid pool example
authorJérôme Benoit <jerome.benoit@sap.com>
Sun, 13 Aug 2023 15:30:35 +0000 (17:30 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Sun, 13 Aug 2023 15:30:35 +0000 (17:30 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
19 files changed:
.eslintrc.js
.github/dependabot.yml
CHANGELOG.md
README.md
examples/typescript/http-client-pool/tsconfig.json
examples/typescript/http-server-pool/fastify-cluster/src/main.ts
examples/typescript/http-server-pool/fastify-hybrid/src/main.ts
examples/typescript/websocket-server-pool/ws-cluster/src/main.ts
examples/typescript/websocket-server-pool/ws-cluster/src/worker.ts
examples/typescript/websocket-server-pool/ws-hybrid/package.json [new file with mode: 0644]
examples/typescript/websocket-server-pool/ws-hybrid/pnpm-lock.yaml [new file with mode: 0644]
examples/typescript/websocket-server-pool/ws-hybrid/requests.js [new file with mode: 0644]
examples/typescript/websocket-server-pool/ws-hybrid/rollup.config.mjs [new file with mode: 0644]
examples/typescript/websocket-server-pool/ws-hybrid/src/main.ts [new file with mode: 0644]
examples/typescript/websocket-server-pool/ws-hybrid/src/request-handler-pool.ts [new file with mode: 0644]
examples/typescript/websocket-server-pool/ws-hybrid/src/request-handler-worker.ts [new file with mode: 0644]
examples/typescript/websocket-server-pool/ws-hybrid/src/types.ts [new file with mode: 0644]
examples/typescript/websocket-server-pool/ws-hybrid/src/websocket-server-worker.ts [new file with mode: 0644]
examples/typescript/websocket-server-pool/ws-hybrid/tsconfig.json [new file with mode: 0644]

index a5382d80588c29850bc8b8ede8b30b0e7b24bad0..e2fc35b2c1264d4c08e5af3de0a3cc3fb36d924b 100644 (file)
@@ -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',
index 6c957f8826e511a0d14486d3b1402049f7cfd3df..34c566ed1d4130ea1991bc51ac65e69af159c9c7 100644 (file)
@@ -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:
index 8c5e44f9c6d7d7106713d99b86d5a1b29bb470f4..9bb8890c79917919892e89c03dc9d5f3e1ec1f8b 100644 (file)
@@ -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
 
index f2f67d83f4773cf2bebcd0aa68815781e6192b4a..50a2227c27209680a44eae3efcb5c8f5437325a8 100644 (file)
--- 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.
 
index c070ed24e50c67b81a44c644272d4087d544fc5c..57e49a1c4adf10b70b32bac5d52516025336d16b 100644 (file)
@@ -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",
index 0ac8f5ceac06007176dc425c7410223f2a12df2e..b462d8c587448ab5825fcce07051c7dbeba4a0b8 100644 (file)
@@ -19,13 +19,13 @@ const pool = new FixedClusterPool<WorkerData, WorkerResponse>(
           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) => {
index 8a17f89318da14164c4b3a144fe95e823677900d..795a358bd52d85acedf2f3c87c4edd4e15d4626f 100644 (file)
@@ -19,13 +19,13 @@ const pool = new FixedClusterPool<ClusterWorkerData, ClusterWorkerResponse>(
           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) => {
index 9a77c0551f247c87c1176819017ef9f507edd627..9712432642eb2b7a3cff7312dd806d3eea5cfce1 100644 (file)
@@ -19,13 +19,16 @@ const pool = new FixedClusterPool<WorkerData, WorkerResponse>(
           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) => {
index b489415ffd798a7ed404e5b993d5e7556278f5dd..48c6cc43cb8363a20befd68d10a60f8482f6ac51 100644 (file)
@@ -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 (file)
index 0000000..2607565
--- /dev/null
@@ -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 (file)
index 0000000..6485ddb
--- /dev/null
@@ -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 (file)
index 0000000..1f302a6
--- /dev/null
@@ -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 (file)
index 0000000..8082e08
--- /dev/null
@@ -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 (file)
index 0000000..ae30301
--- /dev/null
@@ -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<ClusterWorkerData, ClusterWorkerResponse>(
+  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 (file)
index 0000000..8fecfba
--- /dev/null
@@ -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<DataPayload>,
+ThreadWorkerResponse<DataPayload>
+>(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 (file)
index 0000000..40cde50
--- /dev/null
@@ -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<DataPayload>,
+  Response extends ThreadWorkerResponse<DataPayload>
+> extends ThreadWorker<Data, Response> {
+  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<DataPayload>,
+ThreadWorkerResponse<DataPayload>
+>()
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 (file)
index 0000000..055b045
--- /dev/null
@@ -0,0 +1,30 @@
+export enum MessageType {
+  echo = 'echo',
+  factorial = 'factorial'
+}
+
+export interface MessagePayload<T = unknown> {
+  type: MessageType
+  data: T
+}
+
+export interface DataPayload {
+  number?: number
+}
+
+export interface ClusterWorkerData {
+  port: number
+}
+
+export interface ClusterWorkerResponse {
+  status: boolean
+  port?: number
+}
+
+export interface ThreadWorkerData<T = unknown> {
+  data: T
+}
+
+export interface ThreadWorkerResponse<T = unknown> {
+  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 (file)
index 0000000..dfcba66
--- /dev/null
@@ -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<DataPayload>
+      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 (file)
index 0000000..57e49a1
--- /dev/null
@@ -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
+  }
+}