diff --git a/package.json b/package.json index f3fd99f4..486ea27b 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "dev:prepare": "unbuild --stub", "build": "unbuild", "test": "vitest dev", - "test:integration": "jest", + "test:integration": "cross-env NODE_OPTIONS=--experimental-vm-modules jest", "test:types": "tsc --noEmit --skipLibCheck", "lint": "pnpm lint:oxlint && pnpm lint:oxfmt", "lint:oxlint": "oxlint --type-aware", @@ -52,11 +52,12 @@ "@typescript-eslint/parser": "8.56.1", "@vitest/coverage-v8": "4.0.18", "commit-and-tag-version": "12.6.1", + "cross-env": "^10.1.0", "crossws": "0.3.5", "graphql": "16.13.0", "graphql-subscriptions": "3.0.0", "graphql-ws": "6.0.7", - "h3": "1.15.5", + "h3": "2.0.1-rc.14", "jest": "30.2.0", "listhen": "1.9.0", "oxfmt": "0.35.0", @@ -72,7 +73,7 @@ "crossws": "^0.3.0", "graphql": "^16.0.0", "graphql-ws": "^5.0.0 || ^6.0.0", - "h3": "^1.11.0" + "h3": "^1.11.0 || ^2.0.0" }, "peerDependenciesMeta": { "graphql-ws": { @@ -82,5 +83,10 @@ "engines": { "node": ">=20.0.0" }, - "packageManager": "pnpm@10.30.3" + "packageManager": "pnpm@10.30.3", + "pnpm": { + "overrides": { + "h3": "2.0.1-rc.14" + } + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c32dff7..aa843172 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + h3: 2.0.1-rc.14 + importers: .: @@ -33,6 +36,9 @@ importers: commit-and-tag-version: specifier: 12.6.1 version: 12.6.1 + cross-env: + specifier: ^10.1.0 + version: 10.1.0 crossws: specifier: 0.3.5 version: 0.3.5 @@ -46,8 +52,8 @@ importers: specifier: 6.0.7 version: 6.0.7(crossws@0.3.5)(graphql@16.13.0) h3: - specifier: 1.15.5 - version: 1.15.5 + specifier: 2.0.1-rc.14 + version: 2.0.1-rc.14(crossws@0.3.5) jest: specifier: 30.2.0 version: 30.2.0(@types/node@25.3.3) @@ -365,6 +371,9 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@epic-web/invariant@1.0.0': + resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -2136,9 +2145,6 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-es@1.2.2: - resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} - cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -2157,6 +2163,11 @@ packages: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} + cross-env@10.1.0: + resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} + engines: {node: '>=20'} + hasBin: true + cross-inspect@1.0.1: resolution: {integrity: sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==} engines: {node: '>=16.0.0'} @@ -2270,9 +2281,6 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - destr@2.0.5: - resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -2706,8 +2714,15 @@ packages: resolution: {integrity: sha512-uSisMYERbaB9bkA9M4/4dnqyktaEkf1kMHNKq/7DHyxVeWqHQ2mBmVqm5u6/FVHwF3iCNalKcg82Zfl+tffWoA==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - h3@1.15.5: - resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==} + h3@2.0.1-rc.14: + resolution: {integrity: sha512-163qbGmTr/9rqQRNuqMqtgXnOUAkE4KTdauiC9y0E5iG1I65kte9NyfWvZw5RTDMt6eY+DtyoNzrQ9wA2BfvGQ==} + engines: {node: '>=20.11.1'} + hasBin: true + peerDependencies: + crossws: ^0.4.1 + peerDependenciesMeta: + crossws: + optional: true handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} @@ -2812,9 +2827,6 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - iron-webcrypto@1.2.1: - resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} - is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -3373,9 +3385,6 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-mock-http@1.0.4: - resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} - node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -3801,9 +3810,6 @@ packages: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} - radix3@1.1.2: - resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} - range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -3893,6 +3899,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rou3@0.7.12: + resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -4016,6 +4025,11 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + srvx@0.11.8: + resolution: {integrity: sha512-2n9t0YnAXPJjinytvxccNgs7rOA5gmE7Wowt/8Dy2dx2fDC6sBhfBpbrCvjYKALlVukPS/Uq3QwkolKNa7P/2Q==} + engines: {node: '>=20.16.0'} + hasBin: true + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -4858,6 +4872,8 @@ snapshots: tslib: 2.8.1 optional: true + '@epic-web/invariant@1.0.0': {} + '@esbuild/aix-ppc64@0.25.12': optional: true @@ -6379,8 +6395,6 @@ snapshots: convert-source-map@2.0.0: {} - cookie-es@1.2.2: {} - cookie-signature@1.2.2: {} cookie@0.7.2: {} @@ -6394,6 +6408,11 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cross-env@10.1.0: + dependencies: + '@epic-web/invariant': 1.0.0 + cross-spawn: 7.0.6 + cross-inspect@1.0.1: dependencies: tslib: 2.8.1 @@ -6515,8 +6534,6 @@ snapshots: depd@2.0.0: {} - destr@2.0.5: {} - detect-indent@6.1.0: {} detect-libc@2.1.2: {} @@ -7037,17 +7054,12 @@ snapshots: graphql@16.13.0: {} - h3@1.15.5: + h3@2.0.1-rc.14(crossws@0.3.5): dependencies: - cookie-es: 1.2.2 + rou3: 0.7.12 + srvx: 0.11.8 + optionalDependencies: crossws: 0.3.5 - defu: 6.1.4 - destr: 2.0.5 - iron-webcrypto: 1.2.1 - node-mock-http: 1.0.4 - radix3: 1.1.2 - ufo: 1.6.3 - uncrypto: 0.1.3 handlebars@4.7.8: dependencies: @@ -7137,8 +7149,6 @@ snapshots: ipaddr.js@1.9.1: {} - iron-webcrypto@1.2.1: {} - is-arrayish@0.2.1: {} is-callable@1.2.7: {} @@ -7615,7 +7625,7 @@ snapshots: crossws: 0.3.5 defu: 6.1.4 get-port-please: 3.2.0 - h3: 1.15.5 + h3: 2.0.1-rc.14(crossws@0.3.5) http-shutdown: 1.2.2 jiti: 2.6.1 mlly: 1.8.0 @@ -7825,8 +7835,6 @@ snapshots: node-int64@0.4.0: {} - node-mock-http@1.0.4: {} - node-releases@2.0.27: {} normalize-package-data@2.5.0: @@ -8260,8 +8268,6 @@ snapshots: quick-lru@4.0.1: {} - radix3@1.1.2: {} - range-parser@1.2.1: {} raw-body@3.0.2: @@ -8379,6 +8385,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 + rou3@0.7.12: {} + router@2.2.0: dependencies: debug: 4.4.3 @@ -8522,6 +8530,8 @@ snapshots: sprintf-js@1.0.3: {} + srvx@0.11.8: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 diff --git a/src/index.ts b/src/index.ts index e289f5aa..312c54ac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,7 @@ import type { ApolloServer, BaseContext, ContextFunction, HTTPGraphQLRequest } from '@apollo/server' import { HeaderMap } from '@apollo/server' import { Hooks } from 'crossws' -import { - eventHandler, - EventHandler, - getHeaders, - H3Event, - HTTPMethod, - isMethod, - setHeaders, - readBody, - RequestHeaders, -} from 'h3' +import { defineHandler, EventHandler, H3Event, HTTPMethod, isMethod, RequestHeaders } from 'h3' import type { WithRequired } from '@apollo/utils.withrequired' export interface H3ContextFunctionArgument { @@ -42,8 +32,8 @@ export function startServerAndCreateH3Handler( const contextFunction: ContextFunction<[H3ContextFunctionArgument], TContext> = options?.context ?? defaultContext - return eventHandler({ - async handler(event) { + return defineHandler({ + handler: async (event) => { // Apollo-server doesn't handle OPTIONS calls, so we have to do this on our own // https://github.com/apollographql/apollo-server/blob/fa82c1d5299c4803f9ef8ae7fa2e367eadd8c0e6/packages/server/src/runHttpQuery.ts#L182-L192 if (isMethod(event, 'OPTIONS')) { @@ -62,13 +52,15 @@ export function startServerAndCreateH3Handler( throw new Error('Incremental delivery not implemented') } - setHeaders(event, Object.fromEntries(headers)) - event.res.statusCode = status || 200 + for (const [key, value] of headers) { + event.res.headers.set(key, value) + } + event.res.status = status || 200 return body.string } catch (error) { if (error instanceof SyntaxError) { // This is what the apollo test suite expects - event.res.statusCode = 400 + event.res.status = 400 return error.message } else { throw error @@ -82,7 +74,7 @@ export function startServerAndCreateH3Handler( async function toGraphqlRequest(event: H3Event): Promise { return { method: event.req.method || 'POST', - headers: normalizeHeaders(getHeaders(event)), + headers: normalizeHeaders(event.req.headers), search: normalizeQueryString(event.req.url), body: await normalizeBody(event), } @@ -110,6 +102,6 @@ function normalizeQueryString(url: string | undefined): string { async function normalizeBody(event: H3Event): Promise { const PayloadMethods: HTTPMethod[] = ['PATCH', 'POST', 'PUT', 'DELETE'] if (isMethod(event, PayloadMethods)) { - return await readBody(event) + return await event.req.json() } } diff --git a/test/integration.test.ts b/test/integration.test.ts index 5a13d4f3..549cebcc 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -2,7 +2,7 @@ import { CreateServerForIntegrationTestsOptions, defineIntegrationTestSuite, } from '@apollo/server-integration-testsuite' -import { createApp, toNodeListener } from 'h3' +import { H3, toNodeHandler } from 'h3' import { ApolloServer, ApolloServerOptions, BaseContext } from '@apollo/server' import { startServerAndCreateH3Handler } from '../src' import { createServer, Server } from 'node:http' @@ -20,7 +20,7 @@ defineIntegrationTestSuite( serverOptions: ApolloServerOptions, testOptions?: CreateServerForIntegrationTestsOptions, ) => { - const app = createApp() + const app = new H3() const apollo = new ApolloServer({ ...serverOptions, }) @@ -31,7 +31,7 @@ defineIntegrationTestSuite( }), ) - const httpServer = createServer(toNodeListener(app)) + const httpServer = createServer(toNodeHandler(app)) await new Promise((resolve) => { httpServer.listen({ port: 0 }, resolve) })