{
files: ['examples/typescript/**/*.ts'],
rules: {
- 'import/no-unresolved': 'off',
- '@typescript-eslint/no-unsafe-argument': 'off',
+ // 'import/no-unresolved': 'off',
+ // '@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
- '@typescript-eslint/no-unsafe-return': 'off',
+ // '@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
- '@typescript-eslint/no-unsafe-member-access': 'off',
- '@typescript-eslint/no-unnecessary-type-assertion': 'off',
- '@typescript-eslint/strict-boolean-expressions': 'off',
- '@typescript-eslint/restrict-template-expressions': 'off',
- '@typescript-eslint/return-await': 'off'
+ '@typescript-eslint/no-unsafe-member-access': 'off'
+ // '@typescript-eslint/no-unnecessary-type-assertion': 'off',
+ // '@typescript-eslint/strict-boolean-expressions': 'off',
+ // '@typescript-eslint/restrict-template-expressions': 'off',
+ // '@typescript-eslint/return-await': 'off'
}
},
{
- 'pioardi'
- 'jerome-benoit'
versioning-strategy: increase
+ - package-ecosystem: 'npm'
+ directory: '/examples/typescript/websocket-server-pool/ws'
+ schedule:
+ interval: 'daily'
+ labels:
+ - 'dependencies'
+ - 'examples'
+ - 'nocombine'
+ reviewers:
+ - 'pioardi'
+ - 'jerome-benoit'
+ versioning-strategy: increase
### Added
- Add array of transferable objects to the `execute()` method arguments.
+- WebSocket server pool examples: ws
## [2.6.23] - 2023-08-11
- [HTTP server pool](./examples/typescript/http-server-pool/)
- [Express](./examples/typescript/http-server-pool/express/)
- [Fastify](./examples/typescript/http-server-pool/fastify/)
+ - [Websocket server pool](./examples/typescript/websocket-server-pool/)
+ - [ws](./examples/typescript/websocket-server-pool/ws/)
Remember that workers can only send and receive structured-cloneable data.
"target": "es2022",
"module": "es2022",
"moduleResolution": "Node16",
+ "verbatimModuleSyntax": true,
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
expressApp.get('/api/factorial/:number', (req: Request, res: Response) => {
const { number } = req.params
requestHandlerPool
- .execute({ body: { number } }, 'factorial')
+ .execute({ body: { number: parseInt(number) } }, 'factorial')
.then(response => {
return res.send(response.body).end()
})
import { dirname, extname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { DynamicThreadPool, availableParallelism } from 'poolifier'
-import { type WorkerData, type WorkerResponse } from './types.js'
+import {
+ type BodyPayload,
+ type WorkerData,
+ type WorkerResponse
+} from './types.js'
const workerFile = join(
dirname(fileURLToPath(import.meta.url)),
)
export const requestHandlerPool = new DynamicThreadPool<
-WorkerData,
-WorkerResponse
+WorkerData<BodyPayload>,
+WorkerResponse<BodyPayload>
>(1, availableParallelism(), workerFile, {
enableTasksQueue: true,
tasksQueueOptions: {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Node16",
+ "verbatimModuleSyntax": true,
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
{
"$schema": "https://json.schemastore.org/package",
- "name": "express-request-pool",
+ "name": "fastify-plugin-request-pool",
"version": "1.0.0",
- "description": "Express request pool",
+ "description": "fastify plugin request pool",
"main": "dist/main.js",
"type": "module",
"volta": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Node16",
+ "verbatimModuleSyntax": true,
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
--- /dev/null
+{
+ "$schema": "https://json.schemastore.org/package",
+ "name": "ws-request-pool",
+ "version": "1.0.0",
+ "description": "ws request pool",
+ "main": "dist/main.js",
+ "type": "module",
+ "volta": {
+ "node": "20.5.1",
+ "pnpm": "8.6.12"
+ },
+ "scripts": {
+ "build": "pnpm build:clean && tsc",
+ "build:clean": "tsc --build --clean",
+ "start": "node dist/main.js",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "poolifier": "^2.6.23",
+ "ws": "^8.13.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20.4.9",
+ "@types/ws": "^8.5.5",
+ "typescript": "^5.1.6"
+ },
+ "optionalDependencies": {
+ "bufferutil": "^4.0.7",
+ "utf-8-validate": "^6.0.3"
+ }
+}
--- /dev/null
+lockfileVersion: '6.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+dependencies:
+ poolifier:
+ specifier: ^2.6.23
+ version: 2.6.23
+ 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:
+ '@types/node':
+ specifier: ^20.4.9
+ version: 20.4.10
+ '@types/ws':
+ specifier: ^8.5.5
+ version: 8.5.5
+ typescript:
+ specifier: ^5.1.6
+ version: 5.1.6
+
+packages:
+
+ /@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
+
+ /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
+
+ /node-gyp-build@4.6.0:
+ resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
+ hasBin: true
+ requiresBuild: true
+ dev: false
+
+ /poolifier@2.6.23:
+ resolution: {integrity: sha512-QQagtUD4DCzeEEq2SjoxDRsPLqr5qzJm3Wur29rWDepG935BXlGHVZLFlqUoaHXkcILIBKvLxEFs+SB2Op4TIw==}
+ engines: {node: '>=16.14.0', pnpm: '>=8.6.0'}
+ requiresBuild: true
+ dev: false
+
+ /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
+
+ /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
--- /dev/null
+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)
+})
--- /dev/null
+import { type RawData, WebSocketServer } from 'ws'
+import { type DataPayload, type MessagePayload, MessageType } from './types.js'
+import { requestHandlerPool } from './pool.js'
+
+const port = 8080
+const wss = new WebSocketServer({ port }, () => {
+ console.info(
+ `⚡️[ws server]: WebSocket server is started at http://localhost:${port}/`
+ )
+})
+
+const emptyFunction = (): void => {
+ /** Intentional */
+}
+
+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
+ }
+ })
+})
--- /dev/null
+import { dirname, extname, join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { DynamicThreadPool, availableParallelism } from 'poolifier'
+import {
+ type DataPayload,
+ type WorkerData,
+ type WorkerResponse
+} from './types.js'
+
+const workerFile = join(
+ dirname(fileURLToPath(import.meta.url)),
+ `worker${extname(fileURLToPath(import.meta.url))}`
+)
+
+export const requestHandlerPool = new DynamicThreadPool<
+WorkerData<DataPayload>,
+WorkerResponse<DataPayload>
+>(1, availableParallelism(), workerFile, {
+ enableTasksQueue: true,
+ tasksQueueOptions: {
+ concurrency: 8
+ },
+ errorHandler: (e: Error) => {
+ console.error(e)
+ }
+})
--- /dev/null
+export enum MessageType {
+ echo = 'echo',
+ factorial = 'factorial'
+}
+
+export interface MessagePayload<T = unknown> {
+ type: MessageType
+ data: T
+}
+
+export interface DataPayload {
+ number?: number
+}
+
+export interface WorkerData<T = unknown> {
+ data: T
+}
+
+export interface WorkerResponse<T = unknown> {
+ data: T
+}
--- /dev/null
+import { ThreadWorker } from 'poolifier'
+import {
+ type DataPayload,
+ type WorkerData,
+ type WorkerResponse
+} from './types.js'
+
+const factorial: (n: number) => number = n => {
+ if (n === 0) {
+ return 1
+ }
+ return factorial(n - 1) * n
+}
+
+class RequestHandlerWorker<
+ Data extends WorkerData<DataPayload>,
+ Response extends WorkerResponse<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<
+WorkerData<DataPayload>,
+WorkerResponse<DataPayload>
+>()
--- /dev/null
+{
+ "$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
+ }
+}