From 6eeaa1cd4021c5e46e573ca4740662bbe3fc19f5 Mon Sep 17 00:00:00 2001 From: Anton Gunnarsson Date: Tue, 1 Jul 2025 08:49:33 +0000 Subject: [PATCH] Merged in feat/sw-2872-dependency-inject-app-context-in-trpc-package (pull request #2478) feat(SW-2872) Dependency inject app context in trpc package * Move appRouter to trpc package * WIP Move serverClient to trpc package Doesn't handle errors yet * Don't use global * Use trpc everywhere Approved-by: Linus Flood --- apps/partner-sas/app/page.tsx | 15 ++++- apps/partner-sas/global.d.ts | 4 ++ apps/partner-sas/lib/trpc.ts | 34 +++++++++++ apps/partner-sas/next.config.ts | 23 +++++++- apps/partner-sas/package.json | 1 + .../app/api/web/trpc/[trpc]/route.ts | 3 +- apps/scandic-web/i18n/serverContext.ts | 1 - apps/scandic-web/lib/trpc/client.ts | 3 +- apps/scandic-web/lib/trpc/server.ts | 34 +++-------- apps/scandic-web/package.json | 1 - apps/scandic-web/server/index.ts | 21 ------- .../scandic-web/server/routers/utils/index.ts | 15 ----- packages/booking-flow/global.d.ts | 4 ++ packages/booking-flow/lib/index.tsx | 23 ++++++-- packages/booking-flow/lib/trpc.ts | 3 + packages/trpc/lib/routers/appRouter.ts | 21 +++++++ packages/trpc/lib/serverClient.ts | 59 +++++++++++++++++++ packages/trpc/package.json | 2 + yarn.lock | 27 +-------- 19 files changed, 194 insertions(+), 100 deletions(-) create mode 100644 apps/partner-sas/global.d.ts create mode 100644 apps/partner-sas/lib/trpc.ts delete mode 100644 apps/scandic-web/server/index.ts delete mode 100644 apps/scandic-web/server/routers/utils/index.ts create mode 100644 packages/booking-flow/global.d.ts create mode 100644 packages/booking-flow/lib/trpc.ts create mode 100644 packages/trpc/lib/routers/appRouter.ts create mode 100644 packages/trpc/lib/serverClient.ts diff --git a/apps/partner-sas/app/page.tsx b/apps/partner-sas/app/page.tsx index fc51ec18a..bd023bea4 100644 --- a/apps/partner-sas/app/page.tsx +++ b/apps/partner-sas/app/page.tsx @@ -1,15 +1,26 @@ import { Temp } from "@scandic-hotels/booking-flow/test-entry" +import { Lang } from "@scandic-hotels/common/constants/language" import { Typography } from "@scandic-hotels/design-system/Typography" +import { serverClient } from "@/lib/trpc" + import styles from "./page.module.css" -export default function Home() { +export default async function Home() { + const caller = await serverClient() + const destinations = await caller.autocomplete.destinations({ + lang: Lang.en, + includeTypes: ["hotels"], + query: "Göteborg", + }) + const hotel = destinations.hits.hotels[0].name + return (
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} -

hello world

+

hello world with data: {hotel}

{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} diff --git a/apps/partner-sas/global.d.ts b/apps/partner-sas/global.d.ts new file mode 100644 index 000000000..9b2abb356 --- /dev/null +++ b/apps/partner-sas/global.d.ts @@ -0,0 +1,4 @@ +import "@scandic-hotels/common/global.d.ts" +import "@scandic-hotels/trpc/types.d.ts" +import "@scandic-hotels/trpc/auth.d.ts" +import "@scandic-hotels/trpc/jwt.d.ts" diff --git a/apps/partner-sas/lib/trpc.ts b/apps/partner-sas/lib/trpc.ts new file mode 100644 index 000000000..4ba549d84 --- /dev/null +++ b/apps/partner-sas/lib/trpc.ts @@ -0,0 +1,34 @@ +import { headers } from "next/headers" + +import { createContext } from "@scandic-hotels/trpc/context" +import { + appServerClient, + configureServerClient, +} from "@scandic-hotels/trpc/serverClient" + +import type { Lang } from "@scandic-hotels/common/constants/language" + +export async function createAppContext() { + const headersList = await headers() + + const ctx = createContext({ + lang: headersList.get("x-lang") as Lang, + pathname: headersList.get("x-pathname")!, + uid: headersList.get("x-uid"), + url: headersList.get("x-url")!, + contentType: headersList.get("x-contenttype")!, + auth: async () => { + return null + }, + }) + + return ctx +} + +configureServerClient(createAppContext) + +export async function serverClient() { + const ctx = await createAppContext() + + return appServerClient(ctx) +} diff --git a/apps/partner-sas/next.config.ts b/apps/partner-sas/next.config.ts index 8b84ac803..c0ee08e9d 100644 --- a/apps/partner-sas/next.config.ts +++ b/apps/partner-sas/next.config.ts @@ -1,3 +1,5 @@ +import * as Sentry from "@sentry/nextjs" + import type { NextConfig } from "next" const nextConfig: NextConfig = { @@ -27,4 +29,23 @@ const nextConfig: NextConfig = { }, } -export default nextConfig +export default Sentry.withSentryConfig(nextConfig, { + org: "scandic-hotels", + project: "partner-sas", + + authToken: process.env.SENTRY_AUTH_TOKEN, + + // Only print logs for uploading source maps in CI + silent: !process.env.CI, + + // Upload a larger set of source maps for prettier stack traces (increases build time) + widenClientFileUpload: true, + + // Automatically annotate React components to show their full name in breadcrumbs and session replay + reactComponentAnnotation: { + enabled: true, + }, + + hideSourceMaps: true, + disableLogger: true, +}) diff --git a/apps/partner-sas/package.json b/apps/partner-sas/package.json index 61cc0a254..016fcb6b1 100644 --- a/apps/partner-sas/package.json +++ b/apps/partner-sas/package.json @@ -18,6 +18,7 @@ "@netlify/plugin-nextjs": "^5.11.2", "@scandic-hotels/booking-flow": "workspace:*", "@scandic-hotels/design-system": "workspace:*", + "@sentry/nextjs": "^8.41.0", "next": "15.3.4", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/apps/scandic-web/app/api/web/trpc/[trpc]/route.ts b/apps/scandic-web/app/api/web/trpc/[trpc]/route.ts index b1eac0bd1..f4c96f5d8 100644 --- a/apps/scandic-web/app/api/web/trpc/[trpc]/route.ts +++ b/apps/scandic-web/app/api/web/trpc/[trpc]/route.ts @@ -1,7 +1,8 @@ import { fetchRequestHandler } from "@trpc/server/adapters/fetch" +import { appRouter } from "@scandic-hotels/trpc/routers/appRouter" + import { createAppContext } from "@/lib/trpc/server" -import { appRouter } from "@/server" async function handler(req: Request) { return fetchRequestHandler({ diff --git a/apps/scandic-web/i18n/serverContext.ts b/apps/scandic-web/i18n/serverContext.ts index 1b569e6bb..9e191b7a9 100644 --- a/apps/scandic-web/i18n/serverContext.ts +++ b/apps/scandic-web/i18n/serverContext.ts @@ -4,7 +4,6 @@ import { headers } from "next/headers" import { cache } from "react" import { Lang } from "@scandic-hotels/common/constants/language" - import { languageSchema } from "@scandic-hotels/common/utils/languages" const getRef = cache(() => ({ current: undefined as Lang | undefined })) diff --git a/apps/scandic-web/lib/trpc/client.ts b/apps/scandic-web/lib/trpc/client.ts index c36594c58..c26280ca5 100644 --- a/apps/scandic-web/lib/trpc/client.ts +++ b/apps/scandic-web/lib/trpc/client.ts @@ -1,9 +1,8 @@ import { createTRPCReact } from "@trpc/react-query" +import type { AppRouter } from "@scandic-hotels/trpc/routers/appRouter" import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server" -import type { AppRouter } from "@/server" - export const trpc = createTRPCReact() export type RouterInput = inferRouterInputs diff --git a/apps/scandic-web/lib/trpc/server.ts b/apps/scandic-web/lib/trpc/server.ts index af66713bd..defc93404 100644 --- a/apps/scandic-web/lib/trpc/server.ts +++ b/apps/scandic-web/lib/trpc/server.ts @@ -1,22 +1,21 @@ -import * as Sentry from "@sentry/nextjs" import { TRPCError } from "@trpc/server" import { cookies, headers } from "next/headers" import { redirect } from "next/navigation" import { Lang } from "@scandic-hotels/common/constants/language" -import { createCallerFactory } from "@scandic-hotels/trpc" import { createContext } from "@scandic-hotels/trpc/context" +import { + appServerClient, + configureServerClient, +} from "@scandic-hotels/trpc/serverClient" import { login } from "@/constants/routes/handleAuth" import { webviews } from "@/constants/routes/webviews" -import { appRouter } from "@/server" import { auth } from "@/auth" import type { Session } from "next-auth" -const createCaller = createCallerFactory(appRouter) - export async function createAppContext() { const headersList = await headers() const cookie = await cookies() @@ -49,17 +48,13 @@ export async function createAppContext() { return ctx } +configureServerClient(createAppContext) + export async function serverClient() { const ctx = await createAppContext() - return createCaller(ctx, { - onError: ({ ctx, error, input, path, type }) => { - console.error(`[serverClient] error for ${type}: ${path}`, error) - - if (input) { - console.error(`[serverClient] received input:`, input) - } - + return appServerClient(ctx, { + onError: ({ ctx, error }) => { if (error instanceof TRPCError) { if (error.code === "UNAUTHORIZED") { let lang = Lang.en @@ -90,19 +85,6 @@ export async function serverClient() { redirect(redirectUrl) } } - - Sentry.captureException(error, { - extra: { - input, - path, - type, - url: ctx?.url, - lang: ctx?.lang, - pathname: ctx?.pathname, - contentType: ctx?.contentType, - uid: ctx?.uid, - }, - }) }, }) } diff --git a/apps/scandic-web/package.json b/apps/scandic-web/package.json index ee642e92f..04609c786 100644 --- a/apps/scandic-web/package.json +++ b/apps/scandic-web/package.json @@ -66,7 +66,6 @@ "@vercel/otel": "^1.12.0", "@vis.gl/react-google-maps": "^1.5.2", "class-variance-authority": "^0.7.1", - "clean-deep": "^3.4.0", "contentstack": "^3.25.3", "date-fns": "^4.1.0", "dayjs": "^1.11.13", diff --git a/apps/scandic-web/server/index.ts b/apps/scandic-web/server/index.ts deleted file mode 100644 index d3553b3e8..000000000 --- a/apps/scandic-web/server/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** Routers */ -import { router } from "@scandic-hotels/trpc" -import { autocompleteRouter } from "@scandic-hotels/trpc/routers/autocomplete" -import { bookingRouter } from "@scandic-hotels/trpc/routers/booking" -import { contentstackRouter } from "@scandic-hotels/trpc/routers/contentstack" -import { hotelsRouter } from "@scandic-hotels/trpc/routers/hotels" -import { navigationRouter } from "@scandic-hotels/trpc/routers/navigation" -import { partnerRouter } from "@scandic-hotels/trpc/routers/partners" -import { userRouter } from "@scandic-hotels/trpc/routers/user" - -export const appRouter = router({ - booking: bookingRouter, - contentstack: contentstackRouter, - hotel: hotelsRouter, - user: userRouter, - partner: partnerRouter, - navigation: navigationRouter, - autocomplete: autocompleteRouter, -}) - -export type AppRouter = typeof appRouter diff --git a/apps/scandic-web/server/routers/utils/index.ts b/apps/scandic-web/server/routers/utils/index.ts deleted file mode 100644 index 01019aa41..000000000 --- a/apps/scandic-web/server/routers/utils/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import cleaner from "clean-deep" - -/** - * Function to remove empty objects from a fetched content type. - * Used since Contentstack returns empty objects for all non - * queried in modular blocks. - */ -export function removeEmptyObjects(obj: T) { - return cleaner(obj, { - emptyArrays: false, - emptyStrings: false, - nullValues: false, - undefinedValues: false, - }) -} diff --git a/packages/booking-flow/global.d.ts b/packages/booking-flow/global.d.ts new file mode 100644 index 000000000..9b2abb356 --- /dev/null +++ b/packages/booking-flow/global.d.ts @@ -0,0 +1,4 @@ +import "@scandic-hotels/common/global.d.ts" +import "@scandic-hotels/trpc/types.d.ts" +import "@scandic-hotels/trpc/auth.d.ts" +import "@scandic-hotels/trpc/jwt.d.ts" diff --git a/packages/booking-flow/lib/index.tsx b/packages/booking-flow/lib/index.tsx index 81e53ff6c..a98b34865 100644 --- a/packages/booking-flow/lib/index.tsx +++ b/packages/booking-flow/lib/index.tsx @@ -1,13 +1,28 @@ +import { Lang } from "@scandic-hotels/common/constants/language" import { Typography } from "@scandic-hotels/design-system/Typography" import { env } from "../env/server" +import { serverClient } from "./trpc" const tempEnv = env.FOO -export function Temp() { +export async function Temp() { + const caller = await serverClient() + const destinations = await caller.autocomplete.destinations({ + lang: Lang.en, + includeTypes: ["hotels"], + query: "Stockholm", + }) + const hotel = destinations.hits.hotels[0].name + return ( - -

Tjena {tempEnv}

-
+
+ +

Tjena {tempEnv}

+
+ +

Something fetched by tRPC: {hotel}

+
+
) } diff --git a/packages/booking-flow/lib/trpc.ts b/packages/booking-flow/lib/trpc.ts new file mode 100644 index 000000000..ad7cb355a --- /dev/null +++ b/packages/booking-flow/lib/trpc.ts @@ -0,0 +1,3 @@ +import { packageServerClient } from "@scandic-hotels/trpc/serverClient" + +export const serverClient = packageServerClient diff --git a/packages/trpc/lib/routers/appRouter.ts b/packages/trpc/lib/routers/appRouter.ts new file mode 100644 index 000000000..1873e5576 --- /dev/null +++ b/packages/trpc/lib/routers/appRouter.ts @@ -0,0 +1,21 @@ +/** Routers */ +import { autocompleteRouter } from "../routers/autocomplete" +import { bookingRouter } from "../routers/booking" +import { contentstackRouter } from "../routers/contentstack" +import { hotelsRouter } from "../routers/hotels" +import { navigationRouter } from "../routers/navigation" +import { partnerRouter } from "../routers/partners" +import { userRouter } from "../routers/user" +import { router } from ".." + +export const appRouter = router({ + booking: bookingRouter, + contentstack: contentstackRouter, + hotel: hotelsRouter, + user: userRouter, + partner: partnerRouter, + navigation: navigationRouter, + autocomplete: autocompleteRouter, +}) + +export type AppRouter = typeof appRouter diff --git a/packages/trpc/lib/serverClient.ts b/packages/trpc/lib/serverClient.ts new file mode 100644 index 000000000..aad3b2d20 --- /dev/null +++ b/packages/trpc/lib/serverClient.ts @@ -0,0 +1,59 @@ +import * as Sentry from "@sentry/nextjs" + +import { appRouter } from "./routers/appRouter" +import { createCallerFactory } from "." + +import type { Context } from "./context" + +const createCaller = createCallerFactory(appRouter) + +export type CreateContextFn = () => Promise +let createTrpcContext: CreateContextFn | null = null + +export function configureServerClient(createContext: () => Promise) { + createTrpcContext = createContext +} + +type OnError = Required>[1]["onError"] +type ServerClientOptions = { + onError?: OnError +} +export function appServerClient( + context: Context, + options: ServerClientOptions = {} +) { + return createCaller(context, { + onError: (args) => { + const { ctx, error, input, path, type } = args + console.error(`[serverClient] error for ${type}: ${path}`, error) + if (input) { + console.error(`[serverClient] received input:`, input) + } + + options.onError?.(args) + + Sentry.captureException(error, { + extra: { + input, + path, + type, + url: ctx?.url, + lang: ctx?.lang, + pathname: ctx?.pathname, + contentType: ctx?.contentType, + uid: ctx?.uid, + }, + }) + }, + }) +} + +export async function packageServerClient() { + if (!createTrpcContext) { + throw new Error( + "createTrpcContext is not defined. Did you forget to call configureServerClient?" + ) + } + + return appServerClient(await createTrpcContext()) +} diff --git a/packages/trpc/package.json b/packages/trpc/package.json index d803e2fc3..732aa4173 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -17,6 +17,7 @@ "./errors": "./lib/errors.ts", "./procedures": "./lib/procedures.ts", "./transformer": "./lib/transformer.ts", + "./serverClient": "./lib/serverClient.ts", "./utils/generateTag": "./lib/utils/generateTag.ts", "./graphql/request": "./lib/graphql/request.ts", "./graphql/batchRequest": "./lib/graphql/batchRequest.ts", @@ -28,6 +29,7 @@ "./routers/partners/*": "./lib/routers/partners/*.ts", "./routers/autocomplete/*": "./lib/routers/autocomplete/*.ts", "./routers/navigation/*": "./lib/routers/navigation/*.ts", + "./routers/appRouter": "./lib/routers/appRouter.ts", "./enums/*": "./lib/enums/*.ts", "./types/*": "./lib/types/*.ts", "./constants/*": "./lib/constants/*.ts", diff --git a/yarn.lock b/yarn.lock index f395ec36d..43093ee98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6898,6 +6898,7 @@ __metadata: "@scandic-hotels/common": "workspace:*" "@scandic-hotels/design-system": "workspace:*" "@scandic-hotels/typescript-config": "workspace:*" + "@sentry/nextjs": "npm:^8.41.0" "@types/node": "npm:^20" "@types/react": "npm:19.1.0" "@types/react-dom": "npm:19.1.0" @@ -7015,7 +7016,6 @@ __metadata: adm-zip: "npm:^0.5.16" babel-plugin-formatjs: "npm:^10.5.39" class-variance-authority: "npm:^0.7.1" - clean-deep: "npm:^3.4.0" contentstack: "npm:^3.25.3" cypress: "npm:^14.3.3" date-fns: "npm:^4.1.0" @@ -11017,17 +11017,6 @@ __metadata: languageName: node linkType: hard -"clean-deep@npm:^3.4.0": - version: 3.4.0 - resolution: "clean-deep@npm:3.4.0" - dependencies: - lodash.isempty: "npm:^4.4.0" - lodash.isplainobject: "npm:^4.0.6" - lodash.transform: "npm:^4.6.0" - checksum: 10c0/cefb0fba6739724e8b49ef1bd4d4f05ca3072458e982ca82cadb9bee27db05dc02c17be8771e5b752a81696bef3ca86d883b2a39c2a2e8f91fa30057b8253ae0 - languageName: node - linkType: hard - "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -16114,13 +16103,6 @@ __metadata: languageName: node linkType: hard -"lodash.isempty@npm:^4.4.0": - version: 4.4.0 - resolution: "lodash.isempty@npm:4.4.0" - checksum: 10c0/6c7eaa0802398736809b9e8aed8b8ac1abca9be71788fd719ba9d7f5b4c23e8dc63b7f049df4131713dda30a2fdedc2f655268e9deb8cd5a985dfc934afca194 - languageName: node - linkType: hard - "lodash.isinteger@npm:^4.0.4": version: 4.0.4 resolution: "lodash.isinteger@npm:4.0.4" @@ -16170,13 +16152,6 @@ __metadata: languageName: node linkType: hard -"lodash.transform@npm:^4.6.0": - version: 4.6.0 - resolution: "lodash.transform@npm:4.6.0" - checksum: 10c0/ad7f376b00dccff09f8597f19a171f2e074756178ae74887346876e10f6f0d83009460cc0793183cc5ee4e24a72ff86e68031c45b5aa2731c2f681a4dc93fe77 - languageName: node - linkType: hard - "lodash@npm:^4.17.21, lodash@npm:~4.17.15": version: 4.17.21 resolution: "lodash@npm:4.17.21"