From 698136af9c171b39f2d017af59ccf80cf0774a8a Mon Sep 17 00:00:00 2001 From: Nicola Carpanese Date: Wed, 29 Jul 2026 10:53:14 +0200 Subject: [PATCH] chore: release rpc-node-toolkit 0.1.4 --- .github/workflows/ci.yml | 20 +- README.md | 87 ++ docs/COMPATIBILITY.md | 69 ++ package-lock.json | 809 +++++++++++++++++- package.json | 19 +- src/index.d.ts | 400 +++++---- src/safe.d.ts | 42 + src/safe.js | 16 + test/package/fixtures/runtime-cjs/index.cjs | 24 + .../package/fixtures/runtime-cjs/package.json | 3 + test/package/fixtures/runtime-esm/index.mjs | 36 + .../package/fixtures/runtime-esm/package.json | 3 + test/package/fixtures/ts-cjs/index.cts | 43 + test/package/fixtures/ts-cjs/package.json | 3 + test/package/fixtures/ts-cjs/tsconfig.json | 14 + test/package/fixtures/ts-esm/index.ts | 83 ++ test/package/fixtures/ts-esm/package.json | 3 + test/package/fixtures/ts-esm/tsconfig.json | 14 + test/package/runner.js | 383 +++++++++ 19 files changed, 1879 insertions(+), 192 deletions(-) create mode 100644 docs/COMPATIBILITY.md create mode 100644 src/safe.d.ts create mode 100644 test/package/fixtures/runtime-cjs/index.cjs create mode 100644 test/package/fixtures/runtime-cjs/package.json create mode 100644 test/package/fixtures/runtime-esm/index.mjs create mode 100644 test/package/fixtures/runtime-esm/package.json create mode 100644 test/package/fixtures/ts-cjs/index.cts create mode 100644 test/package/fixtures/ts-cjs/package.json create mode 100644 test/package/fixtures/ts-cjs/tsconfig.json create mode 100644 test/package/fixtures/ts-esm/index.ts create mode 100644 test/package/fixtures/ts-esm/package.json create mode 100644 test/package/fixtures/ts-esm/tsconfig.json create mode 100644 test/package/runner.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31b5f75..74917ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,5 +28,21 @@ jobs: - name: Run tests run: npm test - - name: Check package contents - run: npm pack --dry-run + package: + runs-on: ubuntu-latest + needs: test + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Test packed package + run: npm run package-test diff --git a/README.md b/README.md index 4fa88ea..9b3156f 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,87 @@ This package is the framework-agnostic Node.js core for the RPC Toolkit ecosyste npm install rpc-node-toolkit ``` +Requirements: + +- Node.js 18+ + +## Compatibility + +`rpc-node-toolkit` is tested with Node.js 18, 20, and 22. Its CommonJS +runtime supports both CommonJS and Node.js ESM consumers. See +[Compatibility](docs/COMPATIBILITY.md) for the runtime, module, and packaged +consumer matrices. + +## TypeScript + +The package supports TypeScript ESM/NodeNext and CommonJS consumers. Both +forms are tested with `strict: true`, `skipLibCheck: false`, and +`esModuleInterop: false` against the tarball produced by `npm pack`. + +Install the declarations used by these examples: + +```bash +npm install --save-dev typescript @types/node +``` + +ESM/NodeNext (`package.json` contains `"type": "module"`): + +```typescript +import RpcEndpoint, { + RpcEndpoint as NamedRpcEndpoint, + RpcClient, + type RpcEndpointOptions, +} from 'rpc-node-toolkit'; +import { + RpcSafeClient, + RpcSafeEndpoint, +} from 'rpc-node-toolkit/safe'; + +const options: RpcEndpointOptions = { safeEnabled: false }; +const rpc = new RpcEndpoint({}, options); +const namedRpc = new NamedRpcEndpoint({}, options); +const client = new RpcClient('http://localhost:3000/api'); +const safeRpc = new RpcSafeEndpoint({}); +const safeClient = new RpcSafeClient('http://localhost:3000/api'); +``` + +Use these compiler options for the ESM example: + +```json +{ + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": false, + "esModuleInterop": false, + "types": ["node"], + "ignoreDeprecations": "6.0" + } +} +``` + +`ignoreDeprecations` only acknowledges TypeScript 6's deprecation notice for +the explicitly tested `esModuleInterop: false` setting. + +CommonJS TypeScript (`.cts` with NodeNext or Node16 resolution): + +```typescript +import RpcEndpoint = require('rpc-node-toolkit'); +import Safe = require('rpc-node-toolkit/safe'); + +const options: RpcEndpoint.RpcEndpointOptions = { safeEnabled: false }; +const rpc = new RpcEndpoint({}, options); +const namedRpc = new RpcEndpoint.RpcEndpoint({}, options); +const client = new RpcEndpoint.RpcClient('http://localhost:3000/api'); +const safeRpc = new Safe.RpcSafeEndpoint({}); +const safeClient = new Safe.RpcSafeClient('http://localhost:3000/api'); +``` + +The root CommonJS import remains the constructable `RpcEndpoint` export while +also exposing its named API. The `/safe` subpath exposes the safe classes and +the root utilities it re-exports at runtime. + ## Current Scope - Framework-independent `RpcEndpoint` @@ -131,10 +212,16 @@ npm run example:safe ```bash npm install npm test +npm run typecheck +npm run package-test ``` The package test suite covers the core endpoint, HTTP handler, schema validation, batch requests, notifications, and Safe Mode behavior. The ecosystem compatibility matrix also covers `rpc-node-toolkit` as an HTTP Safe Mode server. +`npm run package-test` validates TypeScript and Node.js consumers against the +tarball produced by `npm pack`, including the package export map and the files +that would be published. + ## Related Projects - [rpc-express-toolkit](https://github.com/n-car/rpc-express-toolkit) diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md new file mode 100644 index 0000000..60aebec --- /dev/null +++ b/docs/COMPATIBILITY.md @@ -0,0 +1,69 @@ +# Compatibility + +`rpc-node-toolkit` is a framework-agnostic Node.js library for JSON-RPC 2.0 +servers and clients. + +## Runtime Matrix + +| Runtime | Status | +| --- | --- | +| Node.js 18.x | Supported and CI tested | +| Node.js 20.x | Supported and CI tested | +| Node.js 22.x | Supported and CI tested | + +The package requires Node.js 18 or newer. The GitHub Actions matrix runs the +runtime test suite across Node.js 18, 20, and 22. + +## Module And TypeScript Compatibility + +The published package remains CommonJS at runtime and supports both CommonJS +and Node.js ESM consumers. Its TypeScript declarations model the constructable +CommonJS root export and the named properties exposed by the root and `/safe` +entrypoints. + +| Consumer | Module setup | Verified imports | Validation | +| --- | --- | --- | --- | +| TypeScript ESM | `"type": "module"` with `module` and `moduleResolution` set to `NodeNext` | Root default and named imports, public root types, and named `/safe` imports | TypeScript 6, `strict: true`, `skipLibCheck: false`, `esModuleInterop: false` | +| TypeScript CommonJS | `.cts` with NodeNext resolution | `import = require()` for the root and `/safe`, including namespace properties | TypeScript 6, `strict: true`, `skipLibCheck: false`, `esModuleInterop: false` | +| Node.js ESM | `.mjs` | Root default and named imports plus named `/safe` imports | Runtime smoke test | +| Node.js CommonJS | `.cjs` | `require()` for the root and `/safe`, including root default identity | Runtime smoke test | + +The TypeScript ESM consumer covers `RpcEndpoint` as both the default and a +named import, `RpcClient`, `RpcEndpointOptions`, `RpcSafeEndpoint`, and +`RpcSafeClient`. The CommonJS consumer covers the constructable root export, +`RpcEndpoint.RpcEndpoint`, `RpcEndpoint.RpcClient`, +`Safe.RpcSafeEndpoint`, and `Safe.RpcSafeClient`. + +TypeScript applications should install `@types/node` because the public server +API references types from `node:http`. + +The TypeScript 6 fixtures set `ignoreDeprecations: "6.0"` solely to +acknowledge the compiler's deprecation notice for the deliberately explicit +`esModuleInterop: false` test setting. + +### Packaged Consumer Validation + +`npm run package-test` builds a tarball with `npm pack`, installs that tarball +into isolated consumer fixtures, and runs the full module and TypeScript matrix +above. This verifies the published `exports` map, included files, declaration +resolution, module extensions, and runtime format instead of importing the +repository sources directly. + +The same validation checks the packed artifact with `publint --strict` and +`@arethetypeswrong/cli` for both the root and `/safe` entrypoints. + +Package validation runs in a dedicated Node.js 20 CI job and as part of +`prepublishOnly`, so declaration or packaging regressions block publication. + +## Compatibility Coverage + +The runtime tests cover: + +- core endpoint calls; +- plain `node:http` handling; +- method schema validation; +- batch requests and notifications; +- Safe Mode serialization and HTTP behavior. + +The isolated packaged consumers separately cover module resolution, runtime +exports, and TypeScript declaration compatibility. diff --git a/package-lock.json b/package-lock.json index 14d3608..2244861 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,157 @@ { "name": "rpc-node-toolkit", - "version": "0.1.3", + "version": "0.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rpc-node-toolkit", - "version": "0.1.3", + "version": "0.1.4", "license": "MIT", "dependencies": { "ajv": "^8.20.0", "ajv-formats": "^2.1.1", "rpc-toolkit-js-client": "^1.1.2" }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.18.5", + "@types/node": "^18.19.0", + "publint": "^0.3.22", + "typescript": "~6.0.3" + }, "engines": { "node": ">=18.0.0" } }, + "node_modules/@andrewbranch/untar.js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz", + "integrity": "sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==", + "dev": true + }, + "node_modules/@arethetypeswrong/cli": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/cli/-/cli-0.18.5.tgz", + "integrity": "sha512-gM+8vRsQOD/Uc7EnBedUhkG5OCsDWE4uoak5QvomGpMpaky0Eh41p04nIMgrWb8EOmqZUJGc6zz9hsP6E56R7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@arethetypeswrong/core": "0.18.5", + "chalk": "^4.1.2", + "cli-table3": "^0.6.3", + "commander": "^10.0.1", + "marked": "^9.1.2", + "marked-terminal": "^7.1.0", + "semver": "^7.5.4" + }, + "bin": { + "attw": "dist/index.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/core": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/core/-/core-0.18.5.tgz", + "integrity": "sha512-9ytjzGwxjm9Uz7I9avfbt5vlQt6uk9uRRESzJjqrznl6WKvI6dwYTo+vJ3U02Wrq/mR3iql/PzhvHhKdJIAjDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@andrewbranch/untar.js": "^1.0.3", + "@loaderkit/resolve": "^1.0.2", + "cjs-module-lexer": "^1.2.3", + "fflate": "^0.8.3", + "lru-cache": "^11.0.1", + "semver": "^7.5.4", + "typescript": "5.6.1-rc", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/typescript": { + "version": "5.6.1-rc", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.1-rc.tgz", + "integrity": "sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@braidai/lang": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@braidai/lang/-/lang-1.1.2.tgz", + "integrity": "sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@loaderkit/resolve": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@loaderkit/resolve/-/resolve-1.0.6.tgz", + "integrity": "sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@braidai/lang": "^1.0.0" + } + }, + "node_modules/@publint/pack": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.6.tgz", + "integrity": "sha512-3uVNyGcVplhPZSLVyeIpL7+cIRn1YCSNHLG/rUIlBQMVH8YuN9++YF+5+UDIIO9RW98dujiUoTltO7RDB5bFJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyexec": "^1.2.4" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -50,6 +185,209 @@ } } }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -57,9 +395,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "funding": [ { "type": "github", @@ -72,12 +410,235 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/marked": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", + "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/publint": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.22.tgz", + "integrity": "sha512-6Z/scsr5CA7APdwyF35EY88CqgDj1textWuY788DVTJYPCWVv/Wn9G6KmLnrVRnStgYcahqN4wCDLZGSbQJ69w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@publint/pack": "^0.1.6", + "package-manager-detector": "^1.7.0", + "picocolors": "^1.1.1", + "sade": "^1.8.1" + }, + "bin": { + "publint": "src/cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -95,6 +656,244 @@ "engines": { "node": ">=18.0.0" } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } } } } diff --git a/package.json b/package.json index 857e86a..636ba2f 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "rpc-node-toolkit", - "version": "0.1.3", + "version": "0.1.4", "description": "Framework-agnostic Node.js JSON-RPC 2.0 toolkit with HTTP handler support, schema validation, and RPC Toolkit Safe Mode.", "main": "src/index.js", "types": "src/index.d.ts", + "type": "commonjs", "exports": { ".": { "types": "./src/index.d.ts", @@ -11,7 +12,7 @@ "import": "./src/index.js" }, "./safe": { - "types": "./src/index.d.ts", + "types": "./src/safe.d.ts", "require": "./src/safe.js", "import": "./src/safe.js" } @@ -19,16 +20,20 @@ "files": [ "src/", "examples/", + "docs/", "README.md", "LICENSE" ], "scripts": { - "test": "node --test", + "test": "node --test test/core.test.js", "example:http": "node examples/http-server.js", "example:batch": "node examples/batch-and-notification.js", "example:schema": "node examples/schema-validation.js", "example:safe": "node examples/safe-mode-roundtrip.js", - "prepack": "npm test" + "typecheck": "node test/package/runner.js --types-only", + "package-test": "node test/package/runner.js", + "prepack": "npm test", + "prepublishOnly": "npm run package-test" }, "repository": { "type": "git", @@ -60,6 +65,12 @@ "engines": { "node": ">=18.0.0" }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.18.5", + "@types/node": "^18.19.0", + "publint": "^0.3.22", + "typescript": "~6.0.3" + }, "dependencies": { "ajv": "^8.20.0", "ajv-formats": "^2.1.1", diff --git a/src/index.d.ts b/src/index.d.ts index 6a9f22d..5c4085e 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,131 +1,62 @@ import type { IncomingMessage, ServerResponse } from 'node:http'; +import { + RpcClient as SharedRpcClient, + RpcError as SharedRpcError, + RpcHttpError as SharedRpcHttpError, + RpcSafeClient as SharedRpcSafeClient, +} from 'rpc-toolkit-js-client'; +import type { + RpcBatchRequest as SharedRpcBatchRequest, + RpcClientOptions as SharedRpcClientOptions, +} from 'rpc-toolkit-js-client'; + +/** + * Framework-independent JSON-RPC 2.0 endpoint. + */ +declare class RpcEndpoint { + constructor(context?: C, options?: RpcEndpoint.RpcEndpointOptions); -export type JsonRpcId = string | number | null; - -export interface JsonRpcRequest { - jsonrpc: '2.0'; - method: string; - params?: unknown[] | Record; - id?: JsonRpcId; -} - -export interface JsonRpcSuccess { - jsonrpc: '2.0'; - id: JsonRpcId; - result: unknown; -} - -export interface JsonRpcFailure { - jsonrpc: '2.0'; - id: JsonRpcId; - error: { - code: number; - message: string; - data?: unknown; - }; -} - -export type JsonRpcResponse = JsonRpcSuccess | JsonRpcFailure; - -export interface RpcEndpointOptions { - safeEnabled?: boolean; - strictMode?: boolean; - enableIntrospection?: boolean; - introspectionPrefix?: string; - maxSerializationDepth?: number; - maxDeserializationDepth?: number; - validation?: SchemaValidatorOptions; -} - -export interface RpcRequestContext { - headers?: Record; - request?: IncomingMessage; - ip?: string; - [key: string]: unknown; -} - -export interface RpcHandlerContext { - request: JsonRpcRequest; - rpc: RpcEndpoint; - method: string; - params: unknown; - context: C; - id: JsonRpcId | undefined; - requestContext: RpcRequestContext; - isNotification: boolean; - result?: unknown; - error?: unknown; -} - -export type RpcHandler = ( - request: JsonRpcRequest, - context: C, - params: unknown, - requestContext: RpcRequestContext -) => unknown | Promise; - -export interface RpcMethodConfig { - handler: RpcHandler; - description?: string; - exposeSchema?: boolean; - schema?: unknown; -} - -export interface RpcPayloadResult { - status: number; - headers: Record; - body?: JsonRpcResponse | JsonRpcResponse[]; -} - -export class MiddlewareManager { - use( - hook: RpcMiddlewareHook, - middleware: (context: RpcHandlerContext) => unknown | Promise - ): void; - execute( - hook: RpcMiddlewareHook, - context: RpcHandlerContext - ): Promise>; - getMiddlewares(hook: RpcMiddlewareHook): Function[]; -} - -export type RpcMiddlewareHook = - | 'beforeCall' - | 'beforeValidation' - | 'afterValidation' - | 'afterCall' - | 'onError'; - -export class RpcEndpoint { - constructor(context?: C, options?: RpcEndpointOptions); readonly context: C; - readonly methods: Record>; - readonly middleware: MiddlewareManager; - readonly validator: SchemaValidator; + readonly methods: Record>; + readonly middleware: RpcEndpoint.MiddlewareManager; + readonly validator: RpcEndpoint.SchemaValidator; readonly options: Required< Pick< - RpcEndpointOptions, - 'safeEnabled' | 'strictMode' | 'enableIntrospection' | 'introspectionPrefix' + RpcEndpoint.RpcEndpointOptions, + | 'safeEnabled' + | 'strictMode' + | 'enableIntrospection' + | 'introspectionPrefix' > > & - RpcEndpointOptions; + RpcEndpoint.RpcEndpointOptions; - addMethod(name: string, handler: RpcHandler): void; - addMethod(name: string, config: RpcMethodConfig): void; + addMethod(name: string, handler: RpcEndpoint.RpcHandler): void; + addMethod(name: string, config: RpcEndpoint.RpcMethodConfig): void; removeMethod(name: string): void; - getMethod(name: string): RpcMethodConfig | undefined; + getMethod(name: string): RpcEndpoint.RpcMethodConfig | undefined; listMethods(): string[]; use( - hook: RpcMiddlewareHook, - middleware: (context: RpcHandlerContext) => unknown | Promise + hook: RpcEndpoint.RpcMiddlewareHook, + middleware: ( + context: RpcEndpoint.RpcHandlerContext + ) => unknown | Promise ): void; handlePayload( - input: string | Buffer | JsonRpcRequest | JsonRpcRequest[], - requestContext?: RpcRequestContext - ): Promise; + input: + | string + | Buffer + | RpcEndpoint.JsonRpcRequest + | RpcEndpoint.JsonRpcRequest[], + requestContext?: RpcEndpoint.RpcRequestContext + ): Promise; handleRequest( - input: string | Buffer | JsonRpcRequest | JsonRpcRequest[], - requestContext?: RpcRequestContext + input: + | string + | Buffer + | RpcEndpoint.JsonRpcRequest + | RpcEndpoint.JsonRpcRequest[], + requestContext?: RpcEndpoint.RpcRequestContext ): Promise; serializeBigIntsAndDates(value: unknown): unknown; deserializeBigIntsAndDates( @@ -134,77 +65,184 @@ export class RpcEndpoint { ): unknown; } -export class RpcSafeEndpoint extends RpcEndpoint { - constructor(context?: C, options?: RpcEndpointOptions); -} - -export interface HttpHandlerOptions { - path?: string; - healthPath?: string; - maxBodyBytes?: number; -} - -export function createHttpHandler( - endpoint: RpcEndpoint, - options?: HttpHandlerOptions -): (req: IncomingMessage, res: ServerResponse) => Promise; - -export function serializeValue(value: unknown, options?: RpcEndpointOptions): unknown; -export function deserializeValue( - value: unknown, - options?: RpcEndpointOptions -): unknown; - -export interface SchemaValidatorOptions { - removeAdditional?: boolean; - useDefaults?: boolean; - coerceTypes?: boolean; - ajvOptions?: Record; -} - -export interface SchemaValidationResult { - valid: boolean; - errors: unknown[] | null; - data: unknown; -} - -export class SchemaValidator { - constructor(options?: SchemaValidatorOptions); - readonly ajv: unknown; - validate(params: unknown, schema: object): SchemaValidationResult; - addKeyword(name: string, definition: object): void; -} +/** + * Public properties and types attached to the CommonJS export. + */ +declare namespace RpcEndpoint { + // module.exports.RpcEndpoint === module.exports + export { RpcEndpoint }; -export class SchemaBuilder { - constructor(); - property(name: string, definition: object, required?: boolean): this; - properties(properties: Record): this; - required(fields: string[]): this; - additionalProperties(allowed: boolean): this; - build(): object; -} - -export const commonSchemas: { - pagination: object; - userId: object; - email: object; - bigintString: object; - dateString: object; -}; + // Classes re-exported from rpc-toolkit-js-client. + export { + SharedRpcClient as RpcClient, + SharedRpcError as RpcError, + SharedRpcHttpError as RpcHttpError, + SharedRpcSafeClient as RpcSafeClient, + }; -export class RpcClient { - constructor(endpoint: string, defaultHeaders?: object, options?: object); -} + export type JsonRpcId = string | number | null; + + export interface JsonRpcRequest { + jsonrpc: '2.0'; + method: string; + params?: unknown[] | Record; + id?: JsonRpcId; + } + + export interface JsonRpcSuccess { + jsonrpc: '2.0'; + id: JsonRpcId; + result: unknown; + } + + export interface JsonRpcFailure { + jsonrpc: '2.0'; + id: JsonRpcId; + error: { + code: number; + message: string; + data?: unknown; + }; + } + + export type JsonRpcResponse = JsonRpcSuccess | JsonRpcFailure; + + export interface RpcEndpointOptions { + safeEnabled?: boolean; + strictMode?: boolean; + enableIntrospection?: boolean; + introspectionPrefix?: string; + maxSerializationDepth?: number; + maxDeserializationDepth?: number; + validation?: SchemaValidatorOptions; + } + + export type RpcClientOptions = SharedRpcClientOptions; + export type RpcBatchRequest = SharedRpcBatchRequest; + + export interface RpcRequestContext { + headers?: Record; + request?: IncomingMessage; + ip?: string; + [key: string]: unknown; + } + + export interface RpcHandlerContext { + request: JsonRpcRequest; + rpc: RpcEndpoint; + method: string; + params: unknown; + context: C; + id: JsonRpcId | undefined; + requestContext: RpcRequestContext; + isNotification: boolean; + result?: unknown; + error?: unknown; + } + + export type RpcHandler = ( + request: JsonRpcRequest, + context: C, + params: unknown, + requestContext: RpcRequestContext + ) => unknown | Promise; + + export interface RpcMethodConfig { + handler: RpcHandler; + description?: string; + exposeSchema?: boolean; + schema?: unknown; + } + + export interface RpcPayloadResult { + status: number; + headers: Record; + body?: JsonRpcResponse | JsonRpcResponse[]; + } + + export type RpcMiddlewareHook = + | 'beforeCall' + | 'beforeValidation' + | 'afterValidation' + | 'afterCall' + | 'onError'; + + export class MiddlewareManager { + use( + hook: RpcMiddlewareHook, + middleware: ( + context: RpcHandlerContext + ) => unknown | Promise + ): void; + execute( + hook: RpcMiddlewareHook, + context: RpcHandlerContext + ): Promise>; + getMiddlewares(hook: RpcMiddlewareHook): Function[]; + } + + export class RpcSafeEndpoint extends RpcEndpoint { + constructor(context?: C, options?: RpcEndpointOptions); + } + + export interface HttpHandlerOptions { + path?: string; + healthPath?: string; + maxBodyBytes?: number; + } + + export function createHttpHandler( + endpoint: RpcEndpoint, + options?: HttpHandlerOptions + ): (req: IncomingMessage, res: ServerResponse) => Promise; + + export function serializeValue( + value: unknown, + options?: RpcEndpointOptions + ): unknown; -export class RpcSafeClient extends RpcClient {} + export function deserializeValue( + value: unknown, + options?: RpcEndpointOptions + ): unknown; -export class RpcError extends Error { - code?: number; - data?: unknown; -} + export interface SchemaValidatorOptions { + removeAdditional?: boolean; + useDefaults?: boolean; + coerceTypes?: boolean; + ajvOptions?: Record; + } + + export interface SchemaValidationResult { + valid: boolean; + errors: unknown[] | null; + data: unknown; + } + + export class SchemaValidator { + constructor(options?: SchemaValidatorOptions); + readonly ajv: unknown; + validate(params: unknown, schema: object): SchemaValidationResult; + addKeyword(name: string, definition: object): void; + } + + export class SchemaBuilder { + constructor(); + property(name: string, definition: object, required?: boolean): this; + properties(properties: Record): this; + required(fields: string[]): this; + additionalProperties(allowed: boolean): this; + build(): object; + } + + export const commonSchemas: { + pagination: object; + userId: object; + email: object; + bigintString: object; + dateString: object; + }; -export class RpcHttpError extends Error { - status?: number; } -export default RpcEndpoint; +export = RpcEndpoint; diff --git a/src/safe.d.ts b/src/safe.d.ts new file mode 100644 index 0000000..92d61b9 --- /dev/null +++ b/src/safe.d.ts @@ -0,0 +1,42 @@ +import Main = require('./index'); + +/** + * Public properties attached to the CommonJS safe entrypoint. + */ +declare namespace Safe { + // Runtime values re-exported by src/safe.js. + export import RpcEndpoint = Main; + export import RpcSafeEndpoint = Main.RpcSafeEndpoint; + export import createHttpHandler = Main.createHttpHandler; + export import MiddlewareManager = Main.MiddlewareManager; + export import SchemaBuilder = Main.SchemaBuilder; + export import SchemaValidator = Main.SchemaValidator; + export import commonSchemas = Main.commonSchemas; + export import serializeValue = Main.serializeValue; + export import deserializeValue = Main.deserializeValue; + export import RpcClient = Main.RpcClient; + export import RpcSafeClient = Main.RpcSafeClient; + export import RpcError = Main.RpcError; + export import RpcHttpError = Main.RpcHttpError; + + // Public root types re-exported by the safe entrypoint. + export import JsonRpcId = Main.JsonRpcId; + export import JsonRpcRequest = Main.JsonRpcRequest; + export import JsonRpcSuccess = Main.JsonRpcSuccess; + export import JsonRpcFailure = Main.JsonRpcFailure; + export import JsonRpcResponse = Main.JsonRpcResponse; + export import RpcEndpointOptions = Main.RpcEndpointOptions; + export import RpcClientOptions = Main.RpcClientOptions; + export import RpcBatchRequest = Main.RpcBatchRequest; + export import RpcRequestContext = Main.RpcRequestContext; + export import RpcHandlerContext = Main.RpcHandlerContext; + export import RpcHandler = Main.RpcHandler; + export import RpcMethodConfig = Main.RpcMethodConfig; + export import RpcPayloadResult = Main.RpcPayloadResult; + export import RpcMiddlewareHook = Main.RpcMiddlewareHook; + export import HttpHandlerOptions = Main.HttpHandlerOptions; + export import SchemaValidatorOptions = Main.SchemaValidatorOptions; + export import SchemaValidationResult = Main.SchemaValidationResult; +} + +export = Safe; diff --git a/src/safe.js b/src/safe.js index 85bf563..6e9a955 100644 --- a/src/safe.js +++ b/src/safe.js @@ -3,3 +3,19 @@ const Main = require('./index'); module.exports = { ...Main, }; + +// Make spread-based re-exports visible to Node's CommonJS named-export +// detection without changing the existing object values or property order. +module.exports.RpcEndpoint = Main.RpcEndpoint; +module.exports.RpcSafeEndpoint = Main.RpcSafeEndpoint; +module.exports.createHttpHandler = Main.createHttpHandler; +module.exports.MiddlewareManager = Main.MiddlewareManager; +module.exports.SchemaBuilder = Main.SchemaBuilder; +module.exports.SchemaValidator = Main.SchemaValidator; +module.exports.commonSchemas = Main.commonSchemas; +module.exports.serializeValue = Main.serializeValue; +module.exports.deserializeValue = Main.deserializeValue; +module.exports.RpcClient = Main.RpcClient; +module.exports.RpcSafeClient = Main.RpcSafeClient; +module.exports.RpcError = Main.RpcError; +module.exports.RpcHttpError = Main.RpcHttpError; diff --git a/test/package/fixtures/runtime-cjs/index.cjs b/test/package/fixtures/runtime-cjs/index.cjs new file mode 100644 index 0000000..c19ef51 --- /dev/null +++ b/test/package/fixtures/runtime-cjs/index.cjs @@ -0,0 +1,24 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const Main = require('rpc-node-toolkit'); +const Safe = require('rpc-node-toolkit/safe'); + +assert.equal(Main, Main.RpcEndpoint); +assert.equal(Safe.RpcEndpoint, Main.RpcEndpoint); +assert.equal(Safe.RpcSafeEndpoint, Main.RpcSafeEndpoint); +assert.equal(Safe.RpcClient, Main.RpcClient); +assert.equal(Safe.RpcSafeClient, Main.RpcSafeClient); +assert.equal(Safe.createHttpHandler, Main.createHttpHandler); +assert.equal(Safe.SchemaValidator, Main.SchemaValidator); + +const endpoint = new Main({ consumer: 'commonjs' }); +const safeEndpoint = new Safe.RpcSafeEndpoint({ consumer: 'commonjs' }); +const client = new Main.RpcClient('http://127.0.0.1/rpc'); +const safeClient = new Safe.RpcSafeClient('http://127.0.0.1/safe'); + +assert.ok(endpoint instanceof Main); +assert.ok(safeEndpoint instanceof Safe.RpcSafeEndpoint); +assert.ok(client instanceof Main.RpcClient); +assert.ok(safeClient instanceof Safe.RpcSafeClient); +assert.equal(typeof Main.createHttpHandler(endpoint), 'function'); diff --git a/test/package/fixtures/runtime-cjs/package.json b/test/package/fixtures/runtime-cjs/package.json new file mode 100644 index 0000000..5bbefff --- /dev/null +++ b/test/package/fixtures/runtime-cjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/test/package/fixtures/runtime-esm/index.mjs b/test/package/fixtures/runtime-esm/index.mjs new file mode 100644 index 0000000..565c65e --- /dev/null +++ b/test/package/fixtures/runtime-esm/index.mjs @@ -0,0 +1,36 @@ +import assert from 'node:assert/strict'; +import RpcEndpoint, { + RpcClient, + RpcEndpoint as NamedRpcEndpoint, + RpcSafeClient, + RpcSafeEndpoint, + SchemaValidator, + createHttpHandler, +} from 'rpc-node-toolkit'; +import { + RpcClient as SafeRootClient, + RpcEndpoint as SafeRootEndpoint, + RpcSafeClient as SafePresetClient, + RpcSafeEndpoint as SafePresetEndpoint, + SchemaValidator as SafeSchemaValidator, + createHttpHandler as createSafeHttpHandler, +} from 'rpc-node-toolkit/safe'; + +assert.equal(RpcEndpoint, NamedRpcEndpoint); +assert.equal(SafeRootEndpoint, RpcEndpoint); +assert.equal(SafeRootClient, RpcClient); +assert.equal(SafePresetEndpoint, RpcSafeEndpoint); +assert.equal(SafePresetClient, RpcSafeClient); +assert.equal(SafeSchemaValidator, SchemaValidator); +assert.equal(createSafeHttpHandler, createHttpHandler); + +const endpoint = new RpcEndpoint({ consumer: 'esm' }); +const safeEndpoint = new SafePresetEndpoint({ consumer: 'esm' }); +const client = new RpcClient('http://127.0.0.1/rpc'); +const safeClient = new SafePresetClient('http://127.0.0.1/safe'); + +assert.ok(endpoint instanceof RpcEndpoint); +assert.ok(safeEndpoint instanceof SafePresetEndpoint); +assert.ok(client instanceof RpcClient); +assert.ok(safeClient instanceof SafePresetClient); +assert.equal(typeof createHttpHandler(endpoint), 'function'); diff --git a/test/package/fixtures/runtime-esm/package.json b/test/package/fixtures/runtime-esm/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/test/package/fixtures/runtime-esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/package/fixtures/ts-cjs/index.cts b/test/package/fixtures/ts-cjs/index.cts new file mode 100644 index 0000000..0f1e4e0 --- /dev/null +++ b/test/package/fixtures/ts-cjs/index.cts @@ -0,0 +1,43 @@ +import RpcEndpoint = require('rpc-node-toolkit'); +import Safe = require('rpc-node-toolkit/safe'); + +const context = { consumer: 'commonjs' }; +const options: RpcEndpoint.RpcEndpointOptions = { + safeEnabled: false, + strictMode: true, +}; +const clientOptions: RpcEndpoint.RpcClientOptions = { safeEnabled: false }; +const batch: Safe.RpcBatchRequest[] = [{ method: 'ping', id: 1 }]; + +const endpoint = new RpcEndpoint(context, options); +const namedEndpoint = new RpcEndpoint.RpcEndpoint(context, options); +const safeRootEndpoint = new Safe.RpcEndpoint(context, options); +const safeEndpoint = new Safe.RpcSafeEndpoint(context, options); +const client = new RpcEndpoint.RpcClient( + 'http://127.0.0.1/rpc', + { authorization: 'Bearer package-test' }, + clientOptions +); +const safeRootClient = new Safe.RpcClient('http://127.0.0.1/safe-root'); +const safeClient = new Safe.RpcSafeClient('http://127.0.0.1/safe'); +const handler = RpcEndpoint.createHttpHandler(endpoint); + +const typedDefaultEndpoint: RpcEndpoint = endpoint; +const typedNamedEndpoint: RpcEndpoint.RpcEndpoint = + namedEndpoint; +const typedSafeOptions: Safe.RpcEndpointOptions = options; +const typedCall: typeof client.call = client.call.bind(client); + +void [ + typedDefaultEndpoint, + typedNamedEndpoint, + typedSafeOptions, + typedCall, + batch, + safeRootEndpoint, + safeEndpoint, + client, + safeRootClient, + safeClient, + handler, +]; diff --git a/test/package/fixtures/ts-cjs/package.json b/test/package/fixtures/ts-cjs/package.json new file mode 100644 index 0000000..5bbefff --- /dev/null +++ b/test/package/fixtures/ts-cjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/test/package/fixtures/ts-cjs/tsconfig.json b/test/package/fixtures/ts-cjs/tsconfig.json new file mode 100644 index 0000000..4ceb6e7 --- /dev/null +++ b/test/package/fixtures/ts-cjs/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": false, + "esModuleInterop": false, + "noEmit": true, + "ignoreDeprecations": "6.0", + "types": ["node"] + }, + "include": ["index.cts"] +} diff --git a/test/package/fixtures/ts-esm/index.ts b/test/package/fixtures/ts-esm/index.ts new file mode 100644 index 0000000..f2f0c49 --- /dev/null +++ b/test/package/fixtures/ts-esm/index.ts @@ -0,0 +1,83 @@ +import RpcEndpoint, { + MiddlewareManager, + RpcClient, + RpcEndpoint as NamedRpcEndpoint, + RpcSafeClient, + RpcSafeEndpoint, + SchemaBuilder, + SchemaValidator, + commonSchemas, + createHttpHandler, + deserializeValue, + serializeValue, + type JsonRpcRequest, + type RpcBatchRequest, + type RpcClientOptions, + type RpcEndpointOptions, + type RpcHandlerContext, + type SchemaValidationResult, +} from 'rpc-node-toolkit'; +import { + RpcClient as SafeRootClient, + RpcEndpoint as SafeRootEndpoint, + RpcSafeClient as SafePresetClient, + RpcSafeEndpoint as SafePresetEndpoint, +} from 'rpc-node-toolkit/safe'; + +const context = { consumer: 'esm' }; +const options: RpcEndpointOptions = { + safeEnabled: false, + strictMode: true, +}; +const request: JsonRpcRequest = { + jsonrpc: '2.0', + method: 'ping', + id: 1, +}; +const clientOptions: RpcClientOptions = { safeEnabled: false }; +const batch: RpcBatchRequest[] = [{ method: 'ping', id: 1 }]; + +const endpoint = new RpcEndpoint(context, options); +const namedEndpoint = new NamedRpcEndpoint(context, options); +const safeRootEndpoint = new SafeRootEndpoint(context, options); +const safeEndpoint = new SafePresetEndpoint(context, options); +const client = new RpcClient( + 'http://127.0.0.1/rpc', + { authorization: 'Bearer package-test' }, + clientOptions +); +const safeRootClient = new SafeRootClient('http://127.0.0.1/safe-root'); +const safeClient = new SafePresetClient('http://127.0.0.1/safe'); +const middleware = new MiddlewareManager(); +const validator = new SchemaValidator(); +const schema = new SchemaBuilder() + .property('message', { type: 'string' }, true) + .build(); +const validation: SchemaValidationResult = validator.validate({}, schema); +const handler = createHttpHandler(endpoint); +const serialized = serializeValue(request, options); +const deserialized = deserializeValue(serialized, options); + +declare const handlerContext: RpcHandlerContext; +const typedDefaultEndpoint: RpcEndpoint = endpoint; +const typedNamedEndpoint: NamedRpcEndpoint = namedEndpoint; +const typedSafeClient: RpcSafeClient = safeClient; +const typedCall: typeof client.call = client.call.bind(client); + +void [ + typedDefaultEndpoint, + typedNamedEndpoint, + typedSafeClient, + typedCall, + batch, + safeRootEndpoint, + safeEndpoint, + client, + safeRootClient, + middleware, + validation, + handler, + deserialized, + handlerContext, + commonSchemas, +]; diff --git a/test/package/fixtures/ts-esm/package.json b/test/package/fixtures/ts-esm/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/test/package/fixtures/ts-esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/package/fixtures/ts-esm/tsconfig.json b/test/package/fixtures/ts-esm/tsconfig.json new file mode 100644 index 0000000..0a488fa --- /dev/null +++ b/test/package/fixtures/ts-esm/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": false, + "esModuleInterop": false, + "noEmit": true, + "ignoreDeprecations": "6.0", + "types": ["node"] + }, + "include": ["index.ts"] +} diff --git a/test/package/runner.js b/test/package/runner.js new file mode 100644 index 0000000..2ce1c7f --- /dev/null +++ b/test/package/runner.js @@ -0,0 +1,383 @@ +'use strict'; + +const { spawnSync } = require('node:child_process'); +const { + cpSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} = require('node:fs'); +const { tmpdir } = require('node:os'); +const { basename, dirname, join, parse, resolve, sep } = require('node:path'); + +const projectRoot = resolve(__dirname, '..', '..'); +const fixturesRoot = join(__dirname, 'fixtures'); +const typesOnly = process.argv.includes('--types-only'); +const unsupportedArguments = process.argv + .slice(2) + .filter((argument) => argument !== '--types-only'); + +if (unsupportedArguments.length > 0) { + throw new Error(`Unsupported argument: ${unsupportedArguments.join(', ')}`); +} + +function readJson(filePath) { + return JSON.parse(readFileSync(filePath, 'utf8')); +} + +function run(command, arguments_, options = {}) { + const result = spawnSync(command, arguments_, { + cwd: options.cwd || projectRoot, + encoding: 'utf8', + env: { + ...process.env, + npm_config_audit: 'false', + npm_config_fund: 'false', + npm_config_update_notifier: 'false', + }, + maxBuffer: 50 * 1024 * 1024, + shell: false, + windowsHide: true, + }); + + if (result.error) { + throw result.error; + } + + if (result.status !== 0) { + const output = [result.stdout, result.stderr] + .filter(Boolean) + .join('\n') + .trim(); + const signal = result.signal ? ` (signal ${result.signal})` : ''; + throw new Error( + `${basename(command)} exited with status ${result.status}${signal}${ + output ? `\n${output}` : '' + }` + ); + } + + return result.stdout; +} + +function runStep(label, action) { + process.stdout.write(`[package-test] ${label} ... `); + try { + const result = action(); + process.stdout.write('ok\n'); + return result; + } catch (error) { + process.stdout.write('failed\n'); + throw error; + } +} + +function runNpm(arguments_, options = {}) { + if (process.env.npm_execpath && existsSync(process.env.npm_execpath)) { + return run( + process.execPath, + [process.env.npm_execpath, ...arguments_], + options + ); + } + + const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + return run(npmCommand, arguments_, options); +} + +function findPackageJson(packageName) { + try { + return require.resolve(`${packageName}/package.json`, { + paths: [projectRoot], + }); + } catch (packageJsonError) { + let entryPath; + try { + entryPath = require.resolve(packageName, { paths: [projectRoot] }); + } catch (entryError) { + throw new Error( + `Cannot resolve local dev dependency "${packageName}". Run npm ci first.`, + { cause: entryError } + ); + } + + let currentDirectory = dirname(entryPath); + const filesystemRoot = parse(currentDirectory).root; + + while (currentDirectory !== filesystemRoot) { + const candidate = join(currentDirectory, 'package.json'); + if (existsSync(candidate)) { + const manifest = readJson(candidate); + if (manifest.name === packageName) { + return candidate; + } + } + currentDirectory = dirname(currentDirectory); + } + + throw new Error( + `Cannot locate package.json for local dev dependency "${packageName}".`, + { cause: packageJsonError } + ); + } +} + +function getInstalledPackage(packageName) { + const packageJsonPath = findPackageJson(packageName); + return { + manifest: readJson(packageJsonPath), + root: dirname(packageJsonPath), + }; +} + +function getPackageBin(packageName, binName) { + const installedPackage = getInstalledPackage(packageName); + const { bin } = installedPackage.manifest; + const relativeBin = + typeof bin === 'string' + ? bin + : bin && (bin[binName] || bin[basename(packageName)]); + + if (!relativeBin) { + throw new Error( + `Dev dependency "${packageName}" does not declare the "${binName}" binary.` + ); + } + + const binPath = resolve(installedPackage.root, relativeBin); + if (!existsSync(binPath)) { + throw new Error(`Local binary does not exist: ${binPath}`); + } + return binPath; +} + +function runLocalBin(packageName, binName, arguments_, options = {}) { + return run( + process.execPath, + [getPackageBin(packageName, binName), ...arguments_], + options + ); +} + +function parsePackOutput(output) { + const trimmedOutput = output.trim(); + const candidates = [trimmedOutput]; + const arrayStart = trimmedOutput.indexOf('['); + + if (arrayStart > 0) { + candidates.push(trimmedOutput.slice(arrayStart)); + } + + const packResult = candidates + .map((candidate) => { + try { + const parsed = JSON.parse(candidate); + return Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : null; + } catch (error) { + return null; + } + }) + .find((candidate) => candidate !== null); + + if (packResult) { + return packResult; + } + + throw new Error(`Unable to parse npm pack --json output:\n${trimmedOutput}`); +} + +function assertPackedFiles(packResult) { + const packedPaths = new Set(packResult.files.map((file) => file.path)); + const requiredPaths = [ + 'package.json', + 'src/index.js', + 'src/index.d.ts', + 'src/safe.js', + 'src/safe.d.ts', + 'docs/COMPATIBILITY.md', + ]; + const missingPaths = requiredPaths.filter( + (filePath) => !packedPaths.has(filePath) + ); + + if (missingPaths.length > 0) { + throw new Error( + `Packed archive is missing required files: ${missingPaths.join(', ')}` + ); + } +} + +function removeTemporaryDirectory(directory) { + const resolvedDirectory = resolve(directory); + const temporaryRoot = `${resolve(tmpdir())}${sep}`; + + if (!resolvedDirectory.startsWith(temporaryRoot)) { + throw new Error( + `Refusing to remove non-temporary path: ${resolvedDirectory}` + ); + } + + rmSync(resolvedDirectory, { + force: true, + maxRetries: 3, + recursive: true, + retryDelay: 100, + }); +} + +function executePackageTests() { + const packageManifest = readJson(join(projectRoot, 'package.json')); + const typescriptPackage = getInstalledPackage('typescript').manifest; + + if (!typescriptPackage.version.startsWith('6.')) { + throw new Error( + `Package tests require TypeScript 6.x, found ${typescriptPackage.version}.` + ); + } + + const nodeTypesVersion = getInstalledPackage('@types/node').manifest.version; + const temporaryDirectory = mkdtempSync( + join(tmpdir(), 'rpc-node-toolkit-package-test-') + ); + + try { + const packResult = runStep('pack package', () => { + const output = runNpm( + [ + 'pack', + '--json', + '--dry-run=false', + '--pack-destination', + temporaryDirectory, + ], + { cwd: projectRoot } + ); + return parsePackOutput(output); + }); + + assertPackedFiles(packResult); + + const archivePath = join(temporaryDirectory, packResult.filename); + if (!existsSync(archivePath)) { + throw new Error(`npm pack did not create ${archivePath}`); + } + + const consumerRoot = join(temporaryDirectory, 'consumer'); + mkdirSync(consumerRoot, { recursive: true }); + writeFileSync( + join(consumerRoot, 'package.json'), + `${JSON.stringify( + { + name: 'rpc-node-toolkit-package-consumer', + version: '0.0.0', + private: true, + }, + null, + 2 + )}\n` + ); + cpSync(fixturesRoot, join(consumerRoot, 'fixtures'), { recursive: true }); + + runStep('install packed tarball', () => + runNpm( + [ + 'install', + '--dry-run=false', + '--ignore-scripts', + '--no-audit', + '--no-fund', + '--package-lock=false', + '--no-save', + '--prefer-offline', + archivePath, + `@types/node@${nodeTypesVersion}`, + ], + { cwd: consumerRoot } + ) + ); + + runStep('typecheck ESM consumer', () => + runLocalBin( + 'typescript', + 'tsc', + [ + '--project', + join(consumerRoot, 'fixtures', 'ts-esm', 'tsconfig.json'), + '--pretty', + 'false', + ], + { cwd: consumerRoot } + ) + ); + + runStep('typecheck CommonJS consumer', () => + runLocalBin( + 'typescript', + 'tsc', + [ + '--project', + join(consumerRoot, 'fixtures', 'ts-cjs', 'tsconfig.json'), + '--pretty', + 'false', + ], + { cwd: consumerRoot } + ) + ); + + if (!typesOnly) { + runStep('smoke test ESM runtime', () => + run(process.execPath, [ + join(consumerRoot, 'fixtures', 'runtime-esm', 'index.mjs'), + ]) + ); + + runStep('smoke test CommonJS runtime', () => + run(process.execPath, [ + join(consumerRoot, 'fixtures', 'runtime-cjs', 'index.cjs'), + ]) + ); + + runStep('publint packed tarball', () => + runLocalBin('publint', 'publint', ['run', archivePath, '--strict']) + ); + + runStep('ATTW packed entrypoints', () => + runLocalBin( + '@arethetypeswrong/cli', + 'attw', + [ + archivePath, + '--profile', + 'node16', + '--entrypoints', + '.', + './safe', + '--format', + 'table', + '--no-emoji', + '--no-color', + ], + { cwd: projectRoot } + ) + ); + } + + const mode = typesOnly ? 'types only' : 'full matrix'; + console.log( + `[package-test] passed ${packageManifest.name}@${packResult.version} ` + + `(${mode}, TypeScript ${typescriptPackage.version})` + ); + } finally { + removeTemporaryDirectory(temporaryDirectory); + } +} + +try { + executePackageTests(); +} catch (error) { + console.error(`[package-test] failed: ${error.message}`); + process.exitCode = 1; +}