From 9ac58901528b0717e860a47bcdfc0daf7156d54c Mon Sep 17 00:00:00 2001 From: unnoq Date: Mon, 26 May 2025 10:45:24 +0700 Subject: [PATCH 1/7] init --- packages/hey-api/.gitignore | 26 ++++++ packages/hey-api/README.md | 78 ++++++++++++++++++ packages/hey-api/package.json | 46 +++++++++++ packages/hey-api/src/index.ts | 0 packages/hey-api/tsconfig.json | 15 ++++ pnpm-lock.yaml | 141 +++++++++++++++++++++++++++++++++ 6 files changed, 306 insertions(+) create mode 100644 packages/hey-api/.gitignore create mode 100644 packages/hey-api/README.md create mode 100644 packages/hey-api/package.json create mode 100644 packages/hey-api/src/index.ts create mode 100644 packages/hey-api/tsconfig.json diff --git a/packages/hey-api/.gitignore b/packages/hey-api/.gitignore new file mode 100644 index 000000000..f3620b55e --- /dev/null +++ b/packages/hey-api/.gitignore @@ -0,0 +1,26 @@ +# Hidden folders and files +.* +!.gitignore +!.*.example + +# Common generated folders +logs/ +node_modules/ +out/ +dist/ +dist-ssr/ +build/ +coverage/ +temp/ + +# Common generated files +*.log +*.log.* +*.tsbuildinfo +*.vitest-temp.json +vite.config.ts.timestamp-* +vitest.config.ts.timestamp-* + +# Common manual ignore files +*.local +*.pem \ No newline at end of file diff --git a/packages/hey-api/README.md b/packages/hey-api/README.md new file mode 100644 index 000000000..4d2448bb5 --- /dev/null +++ b/packages/hey-api/README.md @@ -0,0 +1,78 @@ +
+ oRPC logo +
+ +

+ +
+ + codecov + + + weekly downloads + + + MIT License + + + Discord + +
+ +

Typesafe APIs Made Simple 🪄

+ +**oRPC is a powerful combination of RPC and OpenAPI**, makes it easy to build APIs that are end-to-end type-safe and adhere to OpenAPI standards + +--- + +## Highlights + +- **🔗 End-to-End Type Safety**: Ensure type-safe inputs, outputs, and errors from client to server. +- **📘 First-Class OpenAPI**: Built-in support that fully adheres to the OpenAPI standard. +- **📝 Contract-First Development**: Optionally define your API contract before implementation. +- **⚙️ Framework Integrations**: Seamlessly integrate with TanStack Query (React, Vue, Solid, Svelte), Pinia Colada, and more. +- **🚀 Server Actions**: Fully compatible with React Server Actions on Next.js, TanStack Start, and other platforms. +- **🔠 Standard Schema Support**: Works out of the box with Zod, Valibot, ArkType, and other schema validators. +- **🗃️ Native Types**: Supports native types like Date, File, Blob, BigInt, URL, and more. +- **⏱️ Lazy Router**: Enhance cold start times with our lazy routing feature. +- **📡 SSE & Streaming**: Enjoy full type-safe support for SSE and streaming. +- **🌍 Multi-Runtime Support**: Fast and lightweight on Cloudflare, Deno, Bun, Node.js, and beyond. +- **🔌 Extendability**: Easily extend functionality with plugins, middleware, and interceptors. +- **🛡️ Reliability**: Well-tested, TypeScript-based, production-ready, and MIT licensed. + +## Documentation + +You can find the full documentation [here](https://orpc.unnoq.com). + +## Packages + +- [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Build your API contract. +- [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract. +- [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety. +- [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with NestJS. +- [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions. +- [@orpc/react-query](https://www.npmjs.com/package/@orpc/react-query): Integration with [React Query](https://tanstack.com/query/latest/docs/framework/react/overview). +- [@orpc/vue-query](https://www.npmjs.com/package/@orpc/vue-query): Integration with [Vue Query](https://tanstack.com/query/latest/docs/framework/vue/overview). +- [@orpc/solid-query](https://www.npmjs.com/package/@orpc/solid-query): Integration with [Solid Query](https://tanstack.com/query/latest/docs/framework/solid/overview). +- [@orpc/svelte-query](https://www.npmjs.com/package/@orpc/svelte-query): Integration with [Svelte Query](https://tanstack.com/query/latest/docs/framework/svelte/overview). +- [@orpc/vue-colada](https://www.npmjs.com/package/@orpc/vue-colada): Integration with [Pinia Colada](https://pinia-colada.esm.dev/). +- [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Generate OpenAPI specs and handle OpenAPI requests. +- [@orpc/zod](https://www.npmjs.com/package/@orpc/zod): More schemas that [Zod](https://zod.dev/) doesn't support yet. +- [@orpc/valibot](https://www.npmjs.com/package/@orpc/valibot): OpenAPI spec generation from [Valibot](https://valibot.dev/). +- [@orpc/arktype](https://www.npmjs.com/package/@orpc/arktype): OpenAPI spec generation from [ArkType](https://arktype.io/). + +## `@orpc/hey-api` + +Integration with [Hey API](https://heyapi.dev/). + +## Sponsors + +

+ + + +

+ +## License + +Distributed under the MIT License. See [LICENSE](https://github.com/unnoq/orpc/blob/main/LICENSE) for more information. diff --git a/packages/hey-api/package.json b/packages/hey-api/package.json new file mode 100644 index 000000000..07e30b976 --- /dev/null +++ b/packages/hey-api/package.json @@ -0,0 +1,46 @@ +{ + "name": "@orpc/hey-api", + "type": "module", + "version": "1.3.0", + "license": "MIT", + "homepage": "https://orpc.unnoq.com", + "repository": { + "type": "git", + "url": "git+https://github.com/unnoq/orpc.git", + "directory": "packages/hey-api" + }, + "keywords": [ + "unnoq", + "orpc" + ], + "publishConfig": { + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs", + "default": "./dist/index.mjs" + } + } + }, + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "unbuild", + "build:watch": "pnpm run build --watch", + "type:check": "tsc -b" + }, + "peerDependencies": { + "@hey-api/client-fetch": "*" + }, + "dependencies": { + "@orpc/shared": "workspace:*" + }, + "devDependencies": { + "@hey-api/client-fetch": "^0.10.1", + "@hey-api/openapi-ts": "^0.67.6" + } +} diff --git a/packages/hey-api/src/index.ts b/packages/hey-api/src/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/hey-api/tsconfig.json b/packages/hey-api/tsconfig.json new file mode 100644 index 000000000..8ff116dba --- /dev/null +++ b/packages/hey-api/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.lib.json", + "references": [ + { "path": "../shared" } + ], + "include": ["src"], + "exclude": [ + "**/*.test.*", + "**/*.test-d.ts", + "**/*.bench.*", + "**/__tests__/**", + "**/__mocks__/**", + "**/__snapshots__/**" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ff4c3c81..ceaf02a66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -218,6 +218,19 @@ importers: specifier: ^3.25.11 version: 3.25.23 + packages/hey-api: + dependencies: + '@orpc/shared': + specifier: workspace:* + version: link:../shared + devDependencies: + '@hey-api/client-fetch': + specifier: ^0.10.1 + version: 0.10.1(@hey-api/openapi-ts@0.67.6(magicast@0.3.5)(typescript@5.8.3)) + '@hey-api/openapi-ts': + specifier: ^0.67.6 + version: 0.67.6(magicast@0.3.5)(typescript@5.8.3) + packages/nest: dependencies: '@orpc/client': @@ -2627,6 +2640,22 @@ packages: '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + '@hey-api/client-fetch@0.10.1': + resolution: {integrity: sha512-C1XZEnzvOIdXppvMcnO8/V/RpcORxA4rh+5qjuMcItkV++hv7aBz7tSLd0z+bSLFUwttec077WT/nPS+oO4BiA==} + peerDependencies: + '@hey-api/openapi-ts': < 2 + + '@hey-api/json-schema-ref-parser@1.0.6': + resolution: {integrity: sha512-yktiFZoWPtEW8QKS65eqKwA5MTKp88CyiL8q72WynrBs/73SAaxlSWlA2zW/DZlywZ5hX1OYzrCC0wFdvO9c2w==} + engines: {node: '>= 16'} + + '@hey-api/openapi-ts@0.67.6': + resolution: {integrity: sha512-ywZggKKYieVjM6O6T60/Bl+QBRvhcKAov8dAIQor7reyKpFbEn3Ws+9WKoXR8QUuXN8AR8nMFjOuYPer5db/dg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=22.10.0} + hasBin: true + peerDependencies: + typescript: ^5.5.3 + '@hono/node-server@1.14.2': resolution: {integrity: sha512-GHjpOeHYbr9d1vkID2sNUYkl5IxumyhDrUJB7wBp7jvqYwPFt+oNKsAPBRcdSbV7kIrXhouLE199ks1QcK4r7A==} engines: {node: '>=18.14.1'} @@ -3052,6 +3081,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@kwsites/file-exists@1.1.1': resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} @@ -5874,6 +5906,14 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + c12@2.0.1: + resolution: {integrity: sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + c12@3.0.4: resolution: {integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==} peerDependencies: @@ -6141,6 +6181,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@13.0.0: + resolution: {integrity: sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ==} + engines: {node: '>=18'} + commander@13.1.0: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} @@ -7633,6 +7677,10 @@ packages: get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + giget@1.2.5: + resolution: {integrity: sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug==} + hasBin: true + giget@2.0.0: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true @@ -7768,6 +7816,11 @@ packages: h3@1.15.3: resolution: {integrity: sha512-z6GknHqyX0h9aQaTx22VZDf6QyZn+0Nh+Ym8O/u0SGSkyF5cuTJYKlc8MkzW3Nzf9LE1ivcpmYC3FUGpywhuUQ==} + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -9314,6 +9367,11 @@ packages: nwsapi@2.2.20: resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} + nypm@0.5.4: + resolution: {integrity: sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + nypm@0.6.0: resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==} engines: {node: ^14.16.0 || >=16.10.0} @@ -11485,6 +11543,11 @@ packages: ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + uhyphen@0.2.0: resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==} @@ -12349,6 +12412,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -14007,6 +14073,27 @@ snapshots: '@gar/promisify@1.1.3': {} + '@hey-api/client-fetch@0.10.1(@hey-api/openapi-ts@0.67.6(magicast@0.3.5)(typescript@5.8.3))': + dependencies: + '@hey-api/openapi-ts': 0.67.6(magicast@0.3.5)(typescript@5.8.3) + + '@hey-api/json-schema-ref-parser@1.0.6': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.0 + lodash: 4.17.21 + + '@hey-api/openapi-ts@0.67.6(magicast@0.3.5)(typescript@5.8.3)': + dependencies: + '@hey-api/json-schema-ref-parser': 1.0.6 + c12: 2.0.1(magicast@0.3.5) + commander: 13.0.0 + handlebars: 4.7.8 + typescript: 5.8.3 + transitivePeerDependencies: + - magicast + '@hono/node-server@1.14.2(hono@4.7.6)': dependencies: hono: 4.7.6 @@ -14379,6 +14466,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jsdevtools/ono@7.1.3': {} + '@kwsites/file-exists@1.1.1': dependencies: debug: 4.4.1 @@ -18361,6 +18450,23 @@ snapshots: bytes@3.1.2: {} + c12@2.0.1(magicast@0.3.5): + dependencies: + chokidar: 4.0.3 + confbox: 0.1.8 + defu: 6.1.4 + dotenv: 16.5.0 + giget: 1.2.5 + jiti: 2.4.2 + mlly: 1.7.4 + ohash: 1.1.6 + pathe: 1.1.2 + perfect-debounce: 1.0.0 + pkg-types: 1.3.1 + rc9: 2.1.2 + optionalDependencies: + magicast: 0.3.5 + c12@3.0.4(magicast@0.3.5): dependencies: chokidar: 4.0.3 @@ -18658,6 +18764,8 @@ snapshots: commander@10.0.1: {} + commander@13.0.0: {} + commander@13.1.0: {} commander@2.20.3: {} @@ -20460,6 +20568,16 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + giget@1.2.5: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.6 + nypm: 0.5.4 + pathe: 2.0.3 + tar: 6.2.1 + giget@2.0.0: dependencies: citty: 0.1.6 @@ -20669,6 +20787,15 @@ snapshots: ufo: 1.6.1 uncrypto: 0.1.3 + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + has-flag@4.0.0: {} has-own-prop@2.0.0: {} @@ -22655,6 +22782,15 @@ snapshots: nwsapi@2.2.20: {} + nypm@0.5.4: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 1.3.1 + tinyexec: 0.3.2 + ufo: 1.6.1 + nypm@0.6.0: dependencies: citty: 0.1.6 @@ -25104,6 +25240,9 @@ snapshots: ufo@1.6.1: {} + uglify-js@3.19.3: + optional: true + uhyphen@0.2.0: {} uid@2.0.2: @@ -26282,6 +26421,8 @@ snapshots: word-wrap@1.2.5: {} + wordwrap@1.0.0: {} + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 From d807d6a5518fa26cf647716f4c212f49fade8bb7 Mon Sep 17 00:00:00 2001 From: unnoq Date: Mon, 26 May 2025 15:47:42 +0700 Subject: [PATCH 2/7] wip --- eslint.config.js | 1 + package.json | 1 + packages/hey-api/package.json | 3 +- packages/hey-api/src/index.ts | 1 + packages/hey-api/src/to-orpc-client.test-d.ts | 47 +++++ packages/hey-api/src/to-orpc-client.test.ts | 133 +++++++++++++ packages/hey-api/src/to-orpc-client.ts | 44 +++++ packages/hey-api/tests/client/client.gen.ts | 16 ++ packages/hey-api/tests/client/index.ts | 3 + packages/hey-api/tests/client/sdk.gen.ts | 44 +++++ packages/hey-api/tests/client/types.gen.ts | 67 +++++++ packages/hey-api/tests/spec.json | 133 +++++++++++++ pnpm-lock.yaml | 181 +++++++++++++++++- 13 files changed, 665 insertions(+), 9 deletions(-) create mode 100644 packages/hey-api/src/to-orpc-client.test-d.ts create mode 100644 packages/hey-api/src/to-orpc-client.test.ts create mode 100644 packages/hey-api/src/to-orpc-client.ts create mode 100644 packages/hey-api/tests/client/client.gen.ts create mode 100644 packages/hey-api/tests/client/index.ts create mode 100644 packages/hey-api/tests/client/sdk.gen.ts create mode 100644 packages/hey-api/tests/client/types.gen.ts create mode 100644 packages/hey-api/tests/spec.json diff --git a/eslint.config.js b/eslint.config.js index d61b665c3..32758f8e1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,6 +3,7 @@ import pluginBan from 'eslint-plugin-ban' export default antfu({ formatters: true, + ignores: ['packages/hey-api/tests/client/**'], }, { plugins: { ban: pluginBan }, rules: { diff --git a/package.json b/package.json index 0562c9676..5a565a8fb 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "eslint-plugin-format": "^1.0.0", "jsdom": "^26.0.0", "lint-staged": "^16.0.0", + "msw": "^2.8.4", "simple-git-hooks": "^2.11.1", "typescript": "^5.8.3", "unbuild": "^3.5.0", diff --git a/packages/hey-api/package.json b/packages/hey-api/package.json index 07e30b976..2839d27eb 100644 --- a/packages/hey-api/package.json +++ b/packages/hey-api/package.json @@ -31,7 +31,8 @@ "scripts": { "build": "unbuild", "build:watch": "pnpm run build --watch", - "type:check": "tsc -b" + "type:check": "tsc -b", + "prepare": "openapi-ts -i ./tests/spec.json -o ./tests/client -c @hey-api/client-fetch" }, "peerDependencies": { "@hey-api/client-fetch": "*" diff --git a/packages/hey-api/src/index.ts b/packages/hey-api/src/index.ts index e69de29bb..80fdd1cd6 100644 --- a/packages/hey-api/src/index.ts +++ b/packages/hey-api/src/index.ts @@ -0,0 +1 @@ +export * from './to-orpc-client' diff --git a/packages/hey-api/src/to-orpc-client.test-d.ts b/packages/hey-api/src/to-orpc-client.test-d.ts new file mode 100644 index 000000000..4202df9a5 --- /dev/null +++ b/packages/hey-api/src/to-orpc-client.test-d.ts @@ -0,0 +1,47 @@ +import type { NestedClient } from '../../client/src/types' +import type { Planet } from '../tests/client/types.gen' +import * as sdk from '../tests/client/sdk.gen' +import { experimental_toORPCClient } from './to-orpc-client' + +describe('toORPCClient', () => { + const client = experimental_toORPCClient({ + ...sdk, + somethingElse: 123, + }) + + const c = sdk.listPlanets() + + it('satisfies nested client', () => { + const _b: NestedClient> = client + }) + + it('inputs', async () => { + client.listPlanets() + + client.listPlanets({ + query: { + limit: 10, + offset: 0, + }, + }) + + client.listPlanets({ + query: { + // @ts-expect-error - invalid type + limit: 'invalid', + }, + }) + + client.getPlanet({ path: { planetId: 'earth' } }) + // @ts-expect-error - path is required + client.getPlanet() + // @ts-expect-error - invalid type + client.getPlanet({ path: { planetId: 123 } }) + }) + + it('outputs', async () => { + expectTypeOf(await client.listPlanets()).toEqualTypeOf<{ body: Planet[], request: Request, response: Response }>() + expectTypeOf(await client.getPlanet({ path: { planetId: 'earth' } })).toEqualTypeOf<{ body: Planet, request: Request, response: Response }>() + expectTypeOf(await client.createPlanet({ body: { name: 'Earth' } })).toEqualTypeOf<{ body: Planet, request: Request, response: Response }>() + }) +}) diff --git a/packages/hey-api/src/to-orpc-client.test.ts b/packages/hey-api/src/to-orpc-client.test.ts new file mode 100644 index 000000000..51397f4f2 --- /dev/null +++ b/packages/hey-api/src/to-orpc-client.test.ts @@ -0,0 +1,133 @@ +import { http, HttpResponse } from 'msw' +import { setupServer } from 'msw/node' +import { client } from '../tests/client/client.gen' +import * as sdk from '../tests/client/sdk.gen' +import { experimental_toORPCClient } from './to-orpc-client' + +client.setConfig({ + baseUrl: 'https://example.com', +}) + +const server = setupServer( + http.get('https://example.com/planets', (req) => { + if (req.request.url.includes('throwOnError=1')) { + return HttpResponse.json(null, { status: 500 }) + } + + return HttpResponse.json([{ id: 'earth', name: 'Earth' }], { + headers: { + 'X-Rate-Limit': '10', + 'Last-Event-ID': req.request.headers.get('Last-Event-ID') ?? 'EMPTY', + }, + }) + }), + http.post('https://example.com/planets', async (req) => { + const body = await req.request.json() as any + return HttpResponse.json({ id: body.name, name: body.name }) + }), + http.get('https://example.com/planets/:planetId', (req) => { + return HttpResponse.json({ id: req.params.planetId, name: req.params.planetId }) + }), +) + +beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) + +afterEach(() => server.resetHandlers()) + +afterAll(() => server.close()) + +describe('toORPCClient', () => { + const client = experimental_toORPCClient({ + ...sdk, + somethingElse: 123, + }) + + it('should ignore non-function properties', () => { + expect(client.somethingElse).toBeUndefined() + }) + + it('works', async () => { + const result = await client.listPlanets() + expect(result).toEqual({ + body: [{ id: 'earth', name: 'Earth' }], + request: expect.any(Request), + response: expect.any(Response), + }) + }) + + it('with lastEventId', async () => { + const result = await client.listPlanets({ + headers: { + 'x-something': 'value', + 'last-event-id': '123', + }, + }, { lastEventId: '456' }) + + expect(result.request.headers.get('x-something')).toBe('value') + expect(result.request.headers.get('last-event-id')).toBe('456') + expect(result.response.headers.get('last-event-id')).toBe('456') + }) + + it('with query', async () => { + const result = await client.listPlanets({ + query: { + limit: 10, + offset: 0, + }, + }) + + expect(result.request.url).toBe('https://example.com/planets?limit=10&offset=0') + expect(result.body).toEqual([{ id: 'earth', name: 'Earth' }]) + }) + + it('with params', async () => { + const result = await client.getPlanet({ + path: { planetId: 'earth' }, + }) + + expect(result.request.url).toBe('https://example.com/planets/earth') + expect(result.body).toEqual({ id: 'earth', name: 'earth' }) + }) + + it('with body', async () => { + const result = await client.createPlanet({ + body: { name: 'Bob' }, + }) + + expect(result.body).toEqual({ id: 'Bob', name: 'Bob' }) + }) + + describe('abort signal', () => { + it('case 1', async () => { + const controller1 = new AbortController() + const controller2 = new AbortController() + + const result = await client.createPlanet({ + body: { name: 'Bob' }, + signal: controller1.signal, + }, { signal: controller2.signal }) + + expect(result.request.signal.aborted).toEqual(false) + controller1.abort() + expect(result.request.signal.aborted).toEqual(true) + }) + + it('case 2', async () => { + const controller1 = new AbortController() + const controller2 = new AbortController() + + const result = await client.createPlanet({ + body: { name: 'Bob' }, + signal: controller1.signal, + }, { signal: controller2.signal }) + + expect(result.request.signal.aborted).toEqual(false) + controller2.abort() + expect(result.request.signal.aborted).toEqual(true) + }) + }) + + it('throws on error', async () => { + await expect(client.listPlanets({ query: { throwOnError: 1 } as any })).rejects.toThrowError() + }) +}) diff --git a/packages/hey-api/src/to-orpc-client.ts b/packages/hey-api/src/to-orpc-client.ts new file mode 100644 index 000000000..557bd8be1 --- /dev/null +++ b/packages/hey-api/src/to-orpc-client.ts @@ -0,0 +1,44 @@ +import type { RequestResult } from '@hey-api/client-fetch' +import type { Client, ThrowableError } from '@orpc/client' + +export type experimental_ToORPCClientResult> = { + [K in keyof T]: T[K] extends (options: infer UInput extends Record | undefined) => RequestResult + ? Client, UInput, { body: Exclude, request: Request, response: Response }, ThrowableError> + : never +} + +export function experimental_toORPCClient>(sdk: T): experimental_ToORPCClientResult { + const client = {} as Record, undefined | Record, any, any>> + + for (const key in sdk) { + const fn = sdk[key] + + if (!fn || typeof fn !== 'function') { + continue + } + + client[key] = async (input, options) => { + const controller = new AbortController() + input?.signal?.addEventListener('abort', () => controller.abort()) + options?.signal?.addEventListener('abort', () => controller.abort()) + + const result = await fn({ + ...input, + signal: controller.signal, + headers: { + ...input?.headers, + ...typeof options?.lastEventId === 'string' ? { 'last-event-id': options.lastEventId } : {}, + }, + throwOnError: true, + }) + + return { + body: result.data, + request: result.request, + response: result.response, + } + } + } + + return client as experimental_ToORPCClientResult +} diff --git a/packages/hey-api/tests/client/client.gen.ts b/packages/hey-api/tests/client/client.gen.ts new file mode 100644 index 000000000..6759c1f28 --- /dev/null +++ b/packages/hey-api/tests/client/client.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from '@hey-api/client-fetch'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/packages/hey-api/tests/client/index.ts b/packages/hey-api/tests/client/index.ts new file mode 100644 index 000000000..e64537d21 --- /dev/null +++ b/packages/hey-api/tests/client/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/packages/hey-api/tests/client/sdk.gen.ts b/packages/hey-api/tests/client/sdk.gen.ts new file mode 100644 index 000000000..5fd0499e4 --- /dev/null +++ b/packages/hey-api/tests/client/sdk.gen.ts @@ -0,0 +1,44 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { ListPlanetsData, ListPlanetsResponse, CreatePlanetData, CreatePlanetResponse, GetPlanetData, GetPlanetResponse } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +export const listPlanets = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + url: '/planets', + ...options + }); +}; + +export const createPlanet = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/planets', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +export const getPlanet = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + url: '/planets/{planetId}', + ...options + }); +}; \ No newline at end of file diff --git a/packages/hey-api/tests/client/types.gen.ts b/packages/hey-api/tests/client/types.gen.ts new file mode 100644 index 000000000..9f6b41ee4 --- /dev/null +++ b/packages/hey-api/tests/client/types.gen.ts @@ -0,0 +1,67 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type Planet = { + id: string; + name: string; +}; + +export type NewPlanet = { + name: string; +}; + +export type ListPlanetsData = { + body?: never; + path?: never; + query?: { + limit?: number; + offset?: number; + }; + url: '/planets'; +}; + +export type ListPlanetsResponses = { + /** + * A list of planets + */ + 200: Array; +}; + +export type ListPlanetsResponse = ListPlanetsResponses[keyof ListPlanetsResponses]; + +export type CreatePlanetData = { + body: NewPlanet; + path?: never; + query?: never; + url: '/planets'; +}; + +export type CreatePlanetResponses = { + /** + * A created planet + */ + 201: Planet; +}; + +export type CreatePlanetResponse = CreatePlanetResponses[keyof CreatePlanetResponses]; + +export type GetPlanetData = { + body?: never; + path: { + planetId: string; + }; + query?: never; + url: '/planets/{planetId}'; +}; + +export type GetPlanetResponses = { + /** + * A planet + */ + 200: Planet; +}; + +export type GetPlanetResponse = GetPlanetResponses[keyof GetPlanetResponses]; + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; \ No newline at end of file diff --git a/packages/hey-api/tests/spec.json b/packages/hey-api/tests/spec.json new file mode 100644 index 000000000..9fa3a9852 --- /dev/null +++ b/packages/hey-api/tests/spec.json @@ -0,0 +1,133 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "Hey API Test", + "version": "1.0.0" + }, + "paths": { + "/planets": { + "get": { + "operationId": "listPlanets", + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "A list of planets", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Planet" + } + } + } + }, + "headers": { + "X-Rate-Limit": { + "schema": { + "type": "integer" + }, + "required": true + } + } + } + } + }, + "post": { + "operationId": "createPlanet", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewPlanet" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "A created planet", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Planet" + } + } + } + } + } + } + }, + "/planets/{planetId}": { + "get": { + "operationId": "getPlanet", + "parameters": [ + { + "name": "planetId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "A planet", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Planet" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Planet": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": ["id", "name"] + }, + "NewPlanet": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + } + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ceaf02a66..09dc89b9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: lint-staged: specifier: ^16.0.0 version: 16.0.0 + msw: + specifier: ^2.8.4 + version: 2.8.4(@types/node@22.15.21)(typescript@5.8.3) simple-git-hooks: specifier: ^2.11.1 version: 2.13.0 @@ -70,7 +73,7 @@ importers: version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0)) vitest: specifier: ^3.0.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) + version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.4(@types/node@22.15.21)(typescript@5.8.3))(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) apps/content: devDependencies: @@ -1589,6 +1592,15 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@bundled-es-modules/cookie@2.0.1': + resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + '@capsizecss/unpack@2.4.0': resolution: {integrity: sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q==} @@ -3111,6 +3123,10 @@ packages: engines: {node: '>=18'} hasBin: true + '@mswjs/interceptors@0.37.6': + resolution: {integrity: sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==} + engines: {node: '>=18'} + '@napi-rs/nice-android-arm-eabi@1.0.1': resolution: {integrity: sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==} engines: {node: '>= 10'} @@ -3453,6 +3469,15 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} @@ -4779,12 +4804,18 @@ packages: '@types/serve-static@1.15.7': resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} + '@types/statuses@2.0.5': + resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + '@types/superagent@8.1.9': resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} '@types/supertest@6.0.3': resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} @@ -7796,6 +7827,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql@16.11.0: + resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + gray-matter@4.0.3: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} @@ -7881,6 +7916,9 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} @@ -8172,6 +8210,9 @@ packages: is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-npm@6.0.0: resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -9109,6 +9150,16 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.8.4: + resolution: {integrity: sha512-GLU8gx0o7RBG/3x/eTnnLd5S5ZInxXRRRMN8GJwaPZ4jpJTxzQfWGvwr90e8L5dkKJnz+gT4gQYCprLy/c4kVw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} @@ -9491,6 +9542,9 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + oxc-parser@0.71.0: resolution: {integrity: sha512-RXmu7qi+67RJ8E5UhKZJdliTI+AqD3gncsJecjujcYvjsCZV9KNIfu42fQAnAfLaYZuzOMRdUYh7LzV3F1C0Gw==} engines: {node: '>=14.0.0'} @@ -10206,6 +10260,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + publish-browser-extension@3.0.0: resolution: {integrity: sha512-gwjH8mIepNqID2VqKIxzT6lmtvkcc5tcWYzrGSUdkeUFFFSHhGp9xx01EZ7j8wPq50dDe0XU5VNbHMAqr6wWAA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -10233,6 +10290,9 @@ packages: quansync@0.2.10: resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -11022,6 +11082,9 @@ packages: streamx@2.22.0: resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -11385,6 +11448,10 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + tough-cookie@5.1.2: resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} @@ -11676,6 +11743,10 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -11801,6 +11872,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + urlpattern-polyfill@10.1.0: resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} @@ -13279,6 +13353,19 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@bundled-es-modules/cookie@2.0.1': + dependencies: + cookie: 0.7.2 + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.1 + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + '@capsizecss/unpack@2.4.0(encoding@0.1.13)': dependencies: blob-to-buffer: 1.2.9 @@ -14519,6 +14606,15 @@ snapshots: - encoding - supports-color + '@mswjs/interceptors@0.37.6': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + '@napi-rs/nice-android-arm-eabi@1.0.1': optional: true @@ -15102,6 +15198,15 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + '@oslojs/encoding@1.1.0': {} '@oxc-parser/binding-darwin-arm64@0.71.0': @@ -16627,7 +16732,7 @@ snapshots: svelte: 5.33.1 optionalDependencies: vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) - vitest: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) + vitest: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.4(@types/node@22.15.21)(typescript@5.8.3))(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': dependencies: @@ -16850,6 +16955,8 @@ snapshots: '@types/node': 22.15.21 '@types/send': 0.17.4 + '@types/statuses@2.0.5': {} + '@types/superagent@8.1.9': dependencies: '@types/cookiejar': 2.1.5 @@ -16862,6 +16969,8 @@ snapshots: '@types/methods': 1.1.4 '@types/superagent': 8.1.9 + '@types/tough-cookie@4.0.5': {} + '@types/triple-beam@1.3.5': {} '@types/unist@2.0.11': {} @@ -17250,7 +17359,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) + vitest: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.4(@types/node@22.15.21)(typescript@5.8.3))(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -17260,7 +17369,7 @@ snapshots: eslint: 9.27.0(jiti@2.4.2) optionalDependencies: typescript: 5.8.3 - vitest: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) + vitest: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.4(@types/node@22.15.21)(typescript@5.8.3))(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -17271,12 +17380,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.4(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0))': + '@vitest/mocker@3.1.4(msw@2.8.4(@types/node@22.15.21)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.1.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: + msw: 2.8.4(@types/node@22.15.21)(typescript@5.8.3) vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) '@vitest/pretty-format@3.1.4': @@ -17307,7 +17417,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.13 tinyrainbow: 2.0.0 - vitest: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) + vitest: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.4(@types/node@22.15.21)(typescript@5.8.3))(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0) '@vitest/utils@3.1.4': dependencies: @@ -20737,6 +20847,8 @@ snapshots: graphemer@1.4.0: {} + graphql@16.11.0: {} + gray-matter@4.0.3: dependencies: js-yaml: 3.14.1 @@ -20906,6 +21018,8 @@ snapshots: he@1.2.0: {} + headers-polyfill@4.0.3: {} + highlight.js@10.7.3: {} hono@4.7.6: {} @@ -21172,6 +21286,8 @@ snapshots: is-module@1.0.0: {} + is-node-process@1.2.0: {} + is-npm@6.0.0: {} is-number@7.0.0: {} @@ -22251,6 +22367,31 @@ snapshots: ms@2.1.3: {} + msw@2.8.4(@types/node@22.15.21)(typescript@5.8.3): + dependencies: + '@bundled-es-modules/cookie': 2.0.1 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.1.9(@types/node@22.15.21) + '@mswjs/interceptors': 0.37.6 + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.5 + graphql: 16.11.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + strict-event-emitter: 0.5.1 + type-fest: 4.41.0 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - '@types/node' + muggle-string@0.4.1: {} multer@1.4.5-lts.2: @@ -22950,6 +23091,8 @@ snapshots: os-tmpdir@1.0.2: {} + outvariant@1.4.3: {} + oxc-parser@0.71.0: dependencies: '@oxc-project/types': 0.71.0 @@ -23625,6 +23768,10 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + psl@1.15.0: + dependencies: + punycode: 2.3.1 + publish-browser-extension@3.0.0: dependencies: cac: 6.7.14 @@ -23664,6 +23811,8 @@ snapshots: quansync@0.2.10: {} + querystringify@2.2.0: {} + queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} @@ -24701,6 +24850,8 @@ snapshots: optionalDependencies: bare-events: 2.5.4 + strict-event-emitter@0.5.1: {} + string-argv@0.3.2: {} string-width@4.2.3: @@ -25090,6 +25241,13 @@ snapshots: totalist@3.0.1: {} + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + tough-cookie@5.1.2: dependencies: tldts: 6.1.86 @@ -25452,6 +25610,8 @@ snapshots: universalify@0.1.2: {} + universalify@0.2.0: {} + universalify@2.0.1: {} unixify@1.0.0: @@ -25584,6 +25744,11 @@ snapshots: dependencies: punycode: 2.3.1 + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + urlpattern-polyfill@10.1.0: {} urlpattern-polyfill@8.0.2: {} @@ -26021,10 +26186,10 @@ snapshots: - typescript - universal-cookie - vitest@3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0): + vitest@3.1.4(@types/debug@4.1.12)(@types/node@22.15.21)(@vitest/ui@3.1.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.4(@types/node@22.15.21)(typescript@5.8.3))(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0): dependencies: '@vitest/expect': 3.1.4 - '@vitest/mocker': 3.1.4(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0)) + '@vitest/mocker': 3.1.4(msw@2.8.4(@types/node@22.15.21)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0)) '@vitest/pretty-format': 3.1.4 '@vitest/runner': 3.1.4 '@vitest/snapshot': 3.1.4 From 247bfb2af73e9b703bbebd0ae29d90abf559bd89 Mon Sep 17 00:00:00 2001 From: unnoq Date: Mon, 26 May 2025 15:47:56 +0700 Subject: [PATCH 3/7] wip --- packages/hey-api/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hey-api/package.json b/packages/hey-api/package.json index 2839d27eb..260032686 100644 --- a/packages/hey-api/package.json +++ b/packages/hey-api/package.json @@ -1,7 +1,7 @@ { "name": "@orpc/hey-api", "type": "module", - "version": "1.3.0", + "version": "0.0.0", "license": "MIT", "homepage": "https://orpc.unnoq.com", "repository": { From 815e753498222a9bcd081b01ed5e7c0a2d66569e Mon Sep 17 00:00:00 2001 From: unnoq Date: Mon, 26 May 2025 15:57:41 +0700 Subject: [PATCH 4/7] wip --- packages/hey-api/src/to-orpc-client.test.ts | 26 +++++++++++++++++++++ packages/hey-api/src/to-orpc-client.ts | 10 ++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/hey-api/src/to-orpc-client.test.ts b/packages/hey-api/src/to-orpc-client.test.ts index 51397f4f2..472fd6ac9 100644 --- a/packages/hey-api/src/to-orpc-client.test.ts +++ b/packages/hey-api/src/to-orpc-client.test.ts @@ -125,6 +125,32 @@ describe('toORPCClient', () => { controller2.abort() expect(result.request.signal.aborted).toEqual(true) }) + + it('case 3', async () => { + const controller1 = new AbortController() + const controller2 = new AbortController() + controller1.abort() + + await expect( + client.createPlanet({ + body: { name: 'Bob' }, + signal: controller1.signal, + }, { signal: controller2.signal }), + ).rejects.toThrowError('This operation was aborted') + }) + + it('case 4', async () => { + const controller1 = new AbortController() + const controller2 = new AbortController() + controller2.abort() + + await expect( + client.createPlanet({ + body: { name: 'Bob' }, + signal: controller1.signal, + }, { signal: controller2.signal }), + ).rejects.toThrowError('This operation was aborted') + }) }) it('throws on error', async () => { diff --git a/packages/hey-api/src/to-orpc-client.ts b/packages/hey-api/src/to-orpc-client.ts index 557bd8be1..09b7a0c7b 100644 --- a/packages/hey-api/src/to-orpc-client.ts +++ b/packages/hey-api/src/to-orpc-client.ts @@ -19,8 +19,14 @@ export function experimental_toORPCClient>(sdk: T) client[key] = async (input, options) => { const controller = new AbortController() - input?.signal?.addEventListener('abort', () => controller.abort()) - options?.signal?.addEventListener('abort', () => controller.abort()) + + if (input?.signal?.aborted || options?.signal?.aborted) { + controller.abort() + } + else { + input?.signal?.addEventListener('abort', () => controller.abort()) + options?.signal?.addEventListener('abort', () => controller.abort()) + } const result = await fn({ ...input, From 7ab22c8509f9fe3c498a6d31187a72bca21064b7 Mon Sep 17 00:00:00 2001 From: unnoq Date: Mon, 26 May 2025 16:22:15 +0700 Subject: [PATCH 5/7] docs --- apps/content/.vitepress/config.ts | 1 + apps/content/docs/hey-api.md | 71 +++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 apps/content/docs/hey-api.md diff --git a/apps/content/.vitepress/config.ts b/apps/content/.vitepress/config.ts index 5e498aaee..c28224286 100644 --- a/apps/content/.vitepress/config.ts +++ b/apps/content/.vitepress/config.ts @@ -173,6 +173,7 @@ export default defineConfig({ collapsed: true, items: [ { text: 'Pinia Colada', link: '/docs/pinia-colada' }, + { text: 'Hey API', link: '/docs/hey-api' }, { text: 'NestJS', link: '/docs/openapi/nest/implement-contract' }, { text: 'Playgrounds', link: '/docs/playgrounds' }, { text: 'Comparison', link: '/docs/comparison' }, diff --git a/apps/content/docs/hey-api.md b/apps/content/docs/hey-api.md new file mode 100644 index 000000000..a3c450a71 --- /dev/null +++ b/apps/content/docs/hey-api.md @@ -0,0 +1,71 @@ +--- +title: Hey API Integration +description: Easily convert a Hey API generated client into an oRPC client to take full advantage of the oRPC ecosystem. +--- + +# Hey API Integration + +Easily convert a [Hey API](https://heyapi.dev/) generated client into an oRPC client to take full advantage of the oRPC ecosystem. + +## Installation + +::: code-group + +```sh [npm] +npm install @orpc/hey-api@latest +``` + +```sh [yarn] +yarn add @orpc/hey-api@latest +``` + +```sh [pnpm] +pnpm add @orpc/hey-api@latest +``` + +```sh [bun] +bun add @orpc/hey-api@latest +``` + +```sh [deno] +deno install npm:@orpc/hey-api@latest +``` + +::: + +## Generating an Hey API Client + +To generate a Hey API client, run the following command: + +```sh +npx @hey-api/openapi-ts \ + -i https://get.heyapi.dev/hey-api/backend \ + -o src/client \ + -c @hey-api/client-fetch +``` + +This command uses the OpenAPI spec at `https://get.heyapi.dev/hey-api/backend` and outputs the generated client into the `src/client` directory. +And make sure you have `@hey-api/client-fetch` installed. + +::: info +For more information on Hey API, please refer to the [official documentation](https://heyapi.dev/). +::: + +## Converting to an oRPC Client + +Once the client is generated, convert it to an oRPC client using the `toORPCClient` function: + +```ts +import { experimental_toORPCClient } from '@orpc/hey-api' +import * as sdk from 'src/client/sdk.gen' + +export const client = experimental_toORPCClient(sdk) + +const { body } = await client.listPlanets() +``` + +This `client` now behaves like any standard oRPC [server-side client](/docs/client/server-side) or [client-side client](/docs/client/client-side), allowing you to use it with any oRPC-compatible library. + +## Error Handling + +Internally, oRPC passes the `throwOnError` option to the Hey API client. If the original Hey API client throws an error, oRPC will forward it as is without modification ensuring consistent error handling. From 1b60c91d7bca333cfd5df3c7624ce946410e6b8c Mon Sep 17 00:00:00 2001 From: unnoq Date: Mon, 26 May 2025 16:25:33 +0700 Subject: [PATCH 6/7] wip --- packages/hey-api/package.json | 1 + packages/hey-api/tsconfig.json | 1 + pnpm-lock.yaml | 3 +++ 3 files changed, 5 insertions(+) diff --git a/packages/hey-api/package.json b/packages/hey-api/package.json index 260032686..77bb19a7a 100644 --- a/packages/hey-api/package.json +++ b/packages/hey-api/package.json @@ -38,6 +38,7 @@ "@hey-api/client-fetch": "*" }, "dependencies": { + "@orpc/client": "workspace:*", "@orpc/shared": "workspace:*" }, "devDependencies": { diff --git a/packages/hey-api/tsconfig.json b/packages/hey-api/tsconfig.json index 8ff116dba..076e6f9ef 100644 --- a/packages/hey-api/tsconfig.json +++ b/packages/hey-api/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.lib.json", "references": [ + { "path": "../client" }, { "path": "../shared" } ], "include": ["src"], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09dc89b9e..bae685a20 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -223,6 +223,9 @@ importers: packages/hey-api: dependencies: + '@orpc/client': + specifier: workspace:* + version: link:../client '@orpc/shared': specifier: workspace:* version: link:../shared From 582ea3cc9aa2d30caf56ecf3dd2dc3375260c0f9 Mon Sep 17 00:00:00 2001 From: unnoq Date: Mon, 26 May 2025 16:37:45 +0700 Subject: [PATCH 7/7] wip --- packages/hey-api/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/hey-api/package.json b/packages/hey-api/package.json index 77bb19a7a..70e34e6b0 100644 --- a/packages/hey-api/package.json +++ b/packages/hey-api/package.json @@ -11,7 +11,8 @@ }, "keywords": [ "unnoq", - "orpc" + "orpc", + "Hey API" ], "publishConfig": { "exports": {