Offload functions to worker threads with shared memory primitives for Node.js.
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(serve): framework (express) + TLS-on-worker smoke tests

+240
+1
package.json
··· 39 39 }, 40 40 "devDependencies": { 41 41 "@changesets/cli": "^2.30.0", 42 + "@types/express": "^5.0.6", 42 43 "@types/node": "^25.5.2", 43 44 "prettier": "^3.8.2", 44 45 "typescript": "^6.0.2"
+67
pnpm-lock.yaml
··· 11 11 '@changesets/cli': 12 12 specifier: ^2.30.0 13 13 version: 2.30.0(@types/node@25.5.2) 14 + '@types/express': 15 + specifier: ^5.0.6 16 + version: 5.0.6 14 17 '@types/node': 15 18 specifier: ^25.5.2 16 19 version: 25.5.2 ··· 109 112 resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 110 113 engines: {node: '>= 8'} 111 114 115 + '@types/body-parser@1.19.6': 116 + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} 117 + 118 + '@types/connect@3.4.38': 119 + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} 120 + 121 + '@types/express-serve-static-core@5.1.1': 122 + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} 123 + 124 + '@types/express@5.0.6': 125 + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} 126 + 127 + '@types/http-errors@2.0.5': 128 + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} 129 + 112 130 '@types/node@12.20.55': 113 131 resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} 114 132 115 133 '@types/node@25.5.2': 116 134 resolution: {integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==} 135 + 136 + '@types/qs@6.15.0': 137 + resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} 138 + 139 + '@types/range-parser@1.2.7': 140 + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} 141 + 142 + '@types/send@1.2.1': 143 + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} 144 + 145 + '@types/serve-static@2.2.0': 146 + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} 117 147 118 148 ansi-colors@4.1.3: 119 149 resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} ··· 592 622 '@nodelib/fs.scandir': 2.1.5 593 623 fastq: 1.20.1 594 624 625 + '@types/body-parser@1.19.6': 626 + dependencies: 627 + '@types/connect': 3.4.38 628 + '@types/node': 25.5.2 629 + 630 + '@types/connect@3.4.38': 631 + dependencies: 632 + '@types/node': 25.5.2 633 + 634 + '@types/express-serve-static-core@5.1.1': 635 + dependencies: 636 + '@types/node': 25.5.2 637 + '@types/qs': 6.15.0 638 + '@types/range-parser': 1.2.7 639 + '@types/send': 1.2.1 640 + 641 + '@types/express@5.0.6': 642 + dependencies: 643 + '@types/body-parser': 1.19.6 644 + '@types/express-serve-static-core': 5.1.1 645 + '@types/serve-static': 2.2.0 646 + 647 + '@types/http-errors@2.0.5': {} 648 + 595 649 '@types/node@12.20.55': {} 596 650 597 651 '@types/node@25.5.2': 598 652 dependencies: 599 653 undici-types: 7.18.2 654 + 655 + '@types/qs@6.15.0': {} 656 + 657 + '@types/range-parser@1.2.7': {} 658 + 659 + '@types/send@1.2.1': 660 + dependencies: 661 + '@types/node': 25.5.2 662 + 663 + '@types/serve-static@2.2.0': 664 + dependencies: 665 + '@types/http-errors': 2.0.5 666 + '@types/node': 25.5.2 600 667 601 668 ansi-colors@4.1.3: {} 602 669
+41
test/serve/express.test.ts
··· 1 + import { describe, it } from 'node:test'; 2 + import assert from 'node:assert/strict'; 3 + import { once } from 'node:events'; 4 + import { createServer as createNetServer } from 'node:net'; 5 + import { request } from 'node:http'; 6 + import { workers, assign } from 'moroutine'; 7 + import { serverThreads } from 'moroutine/serve'; 8 + import { runExpressServer } from './fixtures/express-server.ts'; 9 + 10 + function httpGetJson(port: number, path: string): Promise<unknown> { 11 + return new Promise((resolve, reject) => { 12 + const req = request({ host: '127.0.0.1', port, path }, (res) => { 13 + const chunks: Buffer[] = []; 14 + res.on('data', (c) => chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c))); 15 + res.on('end', () => resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')))); 16 + }); 17 + req.on('error', reject); 18 + req.end(); 19 + }); 20 + } 21 + 22 + describe('express composition', () => { 23 + it('routes requests via express middleware on the worker', { timeout: 15_000 }, async () => { 24 + const server = createNetServer(); 25 + server.listen(0); 26 + await once(server, 'listening'); 27 + const port = (server.address() as any).port; 28 + 29 + { 30 + using run = workers(1); 31 + using threads = serverThreads(run.workers, server); 32 + const serving = run(threads.map(([w, args]) => assign(w, runExpressServer(...args)))); 33 + 34 + const body = await httpGetJson(port, '/greet/world'); 35 + assert.deepEqual(body, { hello: 'world' }); 36 + 37 + server.close(); 38 + await serving; 39 + } 40 + }); 41 + });
+19
test/serve/fixtures/cert.pem
··· 1 + -----BEGIN CERTIFICATE----- 2 + MIIDGjCCAgKgAwIBAgIUD6FhoIYDpi3UXzIAYwl7rq97EwIwDQYJKoZIhvcNAQEL 3 + BQAwFDESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTI2MDQyNTE2NTcyMFoXDTM2MDQy 4 + MjE2NTcyMFowFDESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEF 5 + AAOCAQ8AMIIBCgKCAQEAp34QTuLOYUJ1pQfr/ua8Nrtt/FXZoxJiczCP3kk4xVre 6 + xRK7n9GMAREEbHS2TpDnFICelPlR+7gJ+fxax3mYYb0+JB9YF1bBeDZoeDi0p4kA 7 + iU02pxGJyaFUmAwmIzHbE4vGezLctz8nilUx+v3mdWNJrkDItxH0uars/BAbTxua 8 + nzdD1Zb7flbQfWYdsjnpD3v08iN0nHYkMLG/v/tozELpVkItPU7DiWnbVsswYbdW 9 + FFr8avDyc7SViUIuXLEPYAsNqCB66MFER7p6XN7FgMW8UwitAAPp08NbIGuS2t9q 10 + MrVyUVeG39vgmK3xdw9mIr3vMI2PUE8sJY1IISFPgwIDAQABo2QwYjAdBgNVHQ4E 11 + FgQU5ztFPdc00Dgkjl9EL0jVwcFkho0wHwYDVR0jBBgwFoAU5ztFPdc00Dgkjl9E 12 + L0jVwcFkho0wDwYDVR0TAQH/BAUwAwEB/zAPBgNVHREECDAGhwR/AAABMA0GCSqG 13 + SIb3DQEBCwUAA4IBAQBKRLXssbEgt3XpKZgcdRp2XqG9HKXkbFEoNYtzLfEjI1o5 14 + mJ7u8WdNt/sBqNKDrH2HW96kvLBqRQj9VN2b3PgwNasspv+3gbsALcsdTb+HcFZ9 15 + QCdhTtt0DlRZKdA3SYZ2jDQZPpuoGZ1RI/lOpkqcFj86SP2DXClc1IAGktaX8Dsh 16 + CmAb8gBX1Ib2UkYKLfKDJr+PLnFGdUEY1/UIS/BjzbQ4BPRyoWlJsqCpffSUiPYj 17 + +EQNpC1jQ+bAl+QkdOUCKUzDFR3XuqSH0ct42Rx7S7b8BdeovyKkCFS5m5hKrg/R 18 + CaElP90kCU6qIQdaMIiuAHSBNdnIecqIT8bj+P32 19 + -----END CERTIFICATE-----
+13
test/serve/fixtures/express-server.ts
··· 1 + import { createServer } from 'node:http'; 2 + import express from 'express'; 3 + import { mo } from 'moroutine'; 4 + import { listen, type ListenArgs } from 'moroutine/serve'; 5 + 6 + export const runExpressServer = mo(import.meta, async (...args: ListenArgs): Promise<void> => { 7 + const app = express(); 8 + app.get('/greet/:name', (req, res) => { 9 + res.json({ hello: req.params.name }); 10 + }); 11 + const srv = createServer(app); 12 + await listen(srv, ...args); 13 + });
+28
test/serve/fixtures/key.pem
··· 1 + -----BEGIN PRIVATE KEY----- 2 + MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCnfhBO4s5hQnWl 3 + B+v+5rw2u238VdmjEmJzMI/eSTjFWt7FEruf0YwBEQRsdLZOkOcUgJ6U+VH7uAn5 4 + /FrHeZhhvT4kH1gXVsF4Nmh4OLSniQCJTTanEYnJoVSYDCYjMdsTi8Z7Mty3PyeK 5 + VTH6/eZ1Y0muQMi3EfS5quz8EBtPG5qfN0PVlvt+VtB9Zh2yOekPe/TyI3ScdiQw 6 + sb+/+2jMQulWQi09TsOJadtWyzBht1YUWvxq8PJztJWJQi5csQ9gCw2oIHrowURH 7 + unpc3sWAxbxTCK0AA+nTw1sga5La32oytXJRV4bf2+CYrfF3D2Yive8wjY9QTywl 8 + jUghIU+DAgMBAAECggEAPh9cRh4KZPmE7/pxth/3dCdtgleHOtwhRs8etVgcJdrv 9 + 4FFz5LGQh3670MwzKA3Hn4ubBe85UK8wiBdoxi6b7biGmVhE0Jc9d+xaMCj8HF2M 10 + pOBOIoY3QGFFooVzCb++nU/NBLnXMmJKtJynxouwlB6I4AgoSfpvdgPnFRCWSG7/ 11 + fSq4HrYx0JkA3ZStMStNycI6fyhOe+cXQ5qohs4QKIs8GsDsn0fMzu4AN8VyNkA1 12 + JzKphGfIIM5BuSQlG8S6x4VU+Bof/Ypp6cghEVWdbV/fagQk4UbdenRs67PSsDqf 13 + h2kGU0+tuB9OAlGkfMBzR1Nhp8p3GDSjtgjVAiEDyQKBgQDQW25ybg5ibxLqNV2H 14 + /yaHj6OJXuBit5fxBReitC5YALV4oVtbNXJejBhCsxkHZTbfFpBpJHLWtnViwZsD 15 + KBa5rxSA/O6FMLjstnA9Vudrc/wCLXzf1Uqiib5wweREmyDz+wOWEkPMMt2jwU6C 16 + M65GaALaDeq+tt+WvQ+CjFqGOwKBgQDNyop+U1tTtIKNnSaIvJCZKgheVTIAci05 17 + E5nb/JpRoJg+h0G/VwoNyib/xhdBHMmlQhzSEJflGuAJ4uJZZK6SKWx/67eATdz9 18 + Gmr9BboyTv9w8Rj0OtsTzoJlqFfFmBCnaTF967KECLKpHIcdbXGbFFKQ3ZFRTPED 19 + oaQqLj6fWQKBgCI9hE8FyxNeEYuvrWk2AwzwC/39O369kRior5OrbJpFs4zZZ3v7 20 + sonbF/mOGSTf+z56JM1CkiS641uRWXoRUnq9TO9NoZz9vsYXzSYna+x6qQVqmETo 21 + dlVzsXoyQuMLY0T1EUfORJoAGfBZSh+XgqJLYXyYIOWqg9/vvebETfQVAoGAFn4L 22 + sxDzS2vQVSAXBRzvBP6WGZFgPLI+BR4CVAwA5ekSmsr3wa0GuUkeCBijlAdMdtaK 23 + Bd+wamcMk6gLq0+Y0PrcFAM0dD6OZ8+KYAtPNY5y1upTGuaP6VNJ50iGB1++ej3C 24 + Hgwz38B/noIytmGu6A9JkvHUnzk0onv5UG7rg0kCgYAWkyMd/dOjcPKCKcrgoI3t 25 + z9AGAaiN2RdMw9Yo1qsYY2xdGvLfruICPQS2sgDLDaofAMZGAZ0iz59HfUtG7SGz 26 + 9wIwRg7trhzjw1JePPnLexgC6KvK4AP/uLgVP/1rmxhF1lCDWJpJpwsejSEJy9EF 27 + 1YhJ3vd+lh5aGplA1M+PPA== 28 + -----END PRIVATE KEY-----
+21
test/serve/fixtures/tls-server.ts
··· 1 + import { createServer } from 'node:https'; 2 + import { readFileSync } from 'node:fs'; 3 + import { fileURLToPath } from 'node:url'; 4 + import { mo } from 'moroutine'; 5 + import { listen, type ListenArgs } from 'moroutine/serve'; 6 + 7 + const here = (name: string) => fileURLToPath(new URL(`./${name}`, import.meta.url)); 8 + 9 + export const runTlsServer = mo(import.meta, async (...args: ListenArgs): Promise<void> => { 10 + const srv = createServer( 11 + { 12 + key: readFileSync(here('key.pem')), 13 + cert: readFileSync(here('cert.pem')), 14 + }, 15 + (req, res) => { 16 + res.writeHead(200, { 'Content-Type': 'text/plain' }); 17 + res.end('tls-ok'); 18 + }, 19 + ); 20 + await listen(srv, ...args); 21 + });
+50
test/serve/tls.test.ts
··· 1 + import { describe, it } from 'node:test'; 2 + import assert from 'node:assert/strict'; 3 + import { once } from 'node:events'; 4 + import { createServer as createNetServer } from 'node:net'; 5 + import { request } from 'node:https'; 6 + import { readFileSync } from 'node:fs'; 7 + import { workers, assign } from 'moroutine'; 8 + import { serverThreads } from 'moroutine/serve'; 9 + import { runTlsServer } from './fixtures/tls-server.ts'; 10 + 11 + function httpsGet(port: number): Promise<string> { 12 + return new Promise((resolve, reject) => { 13 + const req = request( 14 + { 15 + host: '127.0.0.1', 16 + port, 17 + path: '/', 18 + ca: readFileSync('test/serve/fixtures/cert.pem'), 19 + }, 20 + (res) => { 21 + const chunks: Buffer[] = []; 22 + res.on('data', (c) => chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c))); 23 + res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); 24 + }, 25 + ); 26 + req.on('error', reject); 27 + req.end(); 28 + }); 29 + } 30 + 31 + describe('TLS on worker', () => { 32 + it('worker terminates TLS; main stays raw TCP', { timeout: 15_000 }, async () => { 33 + const server = createNetServer(); 34 + server.listen(0); 35 + await once(server, 'listening'); 36 + const port = (server.address() as any).port; 37 + 38 + { 39 + using run = workers(1); 40 + using threads = serverThreads(run.workers, server); 41 + const serving = run(threads.map(([w, args]) => assign(w, runTlsServer(...args)))); 42 + 43 + const body = await httpsGet(port); 44 + assert.equal(body, 'tls-ok'); 45 + 46 + server.close(); 47 + await serving; 48 + } 49 + }); 50 + });