Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions apps/api/src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { database } from "@init/db/client"
import { Fault } from "@init/error/fault"
import { kv } from "@init/kv/client"
import { getLogger, LoggerCategory } from "@init/observability/logger"
import { honoLogger } from "@init/observability/logger/integrations"
Expand Down Expand Up @@ -52,19 +51,11 @@ app.use(async (c, next) => {
})

app.onError((error, c) => {
if (Fault.isFault(error)) {
c.var.logger.error(error.flatten(), {
cause: error.cause,
context: error.context,
debug: error.debug,
tag: error.tag,
})
}

if (error instanceof HTTPException) {
return error.getResponse()
}

c.var.logger.error(error.message)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lost detailed error information. Previously logged structured data including cause, context, debug info, and tags from Fault errors. Now only logs error.message which may not capture full error context for debugging.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/api/src/routes/index.ts
Line: 58:58

Comment:
Lost detailed error information. Previously logged structured data including cause, context, debug info, and tags from Fault errors. Now only logs `error.message` which may not capture full error context for debugging.

How can I resolve this? If you propose a fix, please make it concise.

captureException(error)

return c.text("Internal Server Error", 500)
Expand Down
7 changes: 0 additions & 7 deletions apps/api/src/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { extend } from "@init/error/fault"
import { TRPCError } from "@trpc/server"
import { getContext } from "hono/context-storage"
import { createFactory } from "hono/factory"
import { HTTPException } from "hono/http-exception"
import type { AppContext } from "#shared/types.ts"

/**
Expand All @@ -15,7 +12,3 @@ export const factory = createFactory<AppContext>()
* A utility function to get the context from the request.
*/
export const context = getContext<AppContext>

// Extended error classes with Fault functionality
export const HTTPFault = extend(HTTPException)
export const TRPCFault = extend(TRPCError)
2 changes: 1 addition & 1 deletion apps/app/src/shared/env.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createEnv } from "@init/env"
import { auth, db, node } from "@init/env/presets"
import { REACT_PUBLIC_ENV_PREFIX } from "@init/utils/constants"
import { isCI, isProduction } from "@init/utils/environment"
import { isCI } from "@init/utils/environment"
import * as z from "@init/utils/schema"

export default createEnv({
Expand Down
23 changes: 4 additions & 19 deletions apps/app/src/shared/server/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import crypto from "node:crypto"
import { Fault } from "@init/error/fault"
import { UnauthenticatedError, UnauthorizedError } from "@init/error"
import { createMiddleware } from "@tanstack/react-start"
import { authClient } from "#shared/auth.ts"
import { logger } from "#shared/logger.ts"
Expand All @@ -22,18 +22,11 @@ export const withLogger = createMiddleware()

export const requireSession = createMiddleware()
.middleware([withRequestId])
.server(async ({ next, context }) => {
.server(async ({ next }) => {
const { data: session } = await authClient.getSession()

if (!session) {
throw Fault.create("auth.unauthenticated")
.withDescription(
"User is not authenticated.",
"You are not authenticated. Please sign in to continue."
)
.withContext({
requestId: context.requestId,
})
throw new UnauthenticatedError()
}

return next({ context: { session } })
Expand All @@ -45,15 +38,7 @@ export const requireAdmin = createMiddleware()
const { user } = context.session

if (user.role !== "admin") {
throw Fault.create("auth.unauthorized")
.withDescription(
"User is not an admin.",
"You are not an admin. Please contact support if you believe this is an error."
)
.withContext({
requestId: context.requestId,
userId: context.session.user.id,
})
throw new UnauthorizedError({ userId: context.session.user.id })
}

return next()
Expand Down
10 changes: 0 additions & 10 deletions apps/app/src/shared/server/serialization.ts

This file was deleted.

2 changes: 0 additions & 2 deletions apps/app/src/start.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { createStart } from "@tanstack/react-start"
import { faultSerializer } from "#shared/server/serialization.ts"

export const startInstance = createStart(() => ({
functionMiddleware: [],
requestMiddleware: [],
serializationAdapters: [faultSerializer],
}))
12 changes: 9 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 4 additions & 6 deletions packages/backend/src/functions/shared/convex.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fault } from "@init/error/fault"
import { UnauthenticatedError, UnauthorizedError } from "@init/error"
import { getLogger, LoggerCategory } from "@init/observability/logger"
import {
customAction,
Expand Down Expand Up @@ -28,7 +28,7 @@ async function validateIdentity(ctx: QueryCtx | MutationCtx | ActionCtx) {
const identity = await ctx.auth.getUserIdentity()

if (!identity) {
throw Fault.create("auth.unauthenticated").withDescription("User is not authenticated")
throw new UnauthenticatedError()
}

return identity
Expand All @@ -38,13 +38,11 @@ async function validateAdmin(ctx: QueryCtx | MutationCtx | ActionCtx) {
const identity = await ctx.auth.getUserIdentity()

if (!identity) {
throw Fault.create("auth.unauthenticated").withDescription("User is not authenticated")
throw new UnauthenticatedError()
}

if (identity.role !== "admin") {
throw Fault.create("auth.unauthorized").withDescription("User is not authorized").withContext({
userId: identity.tokenIdentifier,
})
throw new UnauthorizedError({ userId: identity.tokenIdentifier })
}

return identity
Expand Down
28 changes: 12 additions & 16 deletions packages/email/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReactNode } from "react"
import { resend } from "@init/env/presets"
import { Fault } from "@init/error/fault"
import { SendEmailError, BatchSendEmailError } from "@init/error"
import { logger } from "@init/observability/logger"
import { type DurationInput, milliseconds } from "@init/utils/duration"
import { singleton } from "@init/utils/singleton"
Expand Down Expand Up @@ -51,14 +51,12 @@ export async function sendEmail(body: ReactNode, params: EmailSendParams) {
})

if (error) {
throw Fault.wrap(error)
.withTag("email.send_failed")
.withContext({
emails,
from,
subject,
text: await render(body, { plainText: true }),
})
throw new SendEmailError({
emails,
from,
subject,
text: await render(body, { plainText: true }),
})
}

return data
Expand Down Expand Up @@ -104,13 +102,11 @@ export async function batchEmails(payload: Array<EmailSendParams & { body: React
)

if (error) {
throw Fault.wrap(error)
.withTag("email.batch_send_failed")
.withContext({
emails: payload.flatMap(({ emails }) => emails),
from: env.EMAIL_FROM,
subject: payload.map(({ subject }) => subject).join(", "),
})
throw new BatchSendEmailError({
emails: payload.flatMap(({ emails }) => emails),
from: env.EMAIL_FROM,
subject: payload.map(({ subject }) => subject).join(", "),
})
}

return data.data ?? []
Expand Down
5 changes: 3 additions & 2 deletions packages/error/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
"license": "MIT",
"type": "module",
"exports": {
"./fault": "./src/fault.ts"
".": "./src/index.ts",
"./*": "./src/*.ts"
},
"scripts": {
"bump:deps": "bun update --interactive",
"clean": "git clean -xdf .cache .turbo dist node_modules",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"faultier": "1.1.0"
"better-result": "2.6.0"
},
"devDependencies": {
"@tooling/tsconfig": "workspace:*",
Expand Down
7 changes: 7 additions & 0 deletions packages/error/src/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { TaggedError } from "better-result"

export class UnauthenticatedError extends TaggedError("AuthenticationError")() {}

export class UnauthorizedError extends TaggedError("AuthorizationError")<{ userId: string }>() {}

export type AuthenticationError = UnauthenticatedError | UnauthorizedError
16 changes: 16 additions & 0 deletions packages/error/src/email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TaggedError } from "better-result"

export class SendEmailError extends TaggedError("SendEmailError")<{
emails: string[]
subject: string
from?: string
text: string
}>() {}

export class BatchSendEmailError extends TaggedError("BatchSendEmailError")<{
emails: string[]
subject: string
from?: string
}>() {}

export type EmailError = SendEmailError | BatchSendEmailError
36 changes: 0 additions & 36 deletions packages/error/src/fault.ts

This file was deleted.

9 changes: 9 additions & 0 deletions packages/error/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { AuthenticationError } from "./auth"
import type { EmailError } from "./email"
import type { UtilityError } from "./utils"

export type AppError = AuthenticationError | EmailError | UtilityError

export * from "./auth"
export * from "./email"
export * from "./utils"
33 changes: 33 additions & 0 deletions packages/error/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { TaggedError } from "better-result"

export class InvalidDurationParseInputError extends TaggedError("InvalidDurationParseInputError")<{
message: string
value: string
}>() {
constructor(props: { value: string }) {
super({ ...props, message: `Unable to parse duration from input: "${props.value}"` })
}
}

export class InvalidDurationFormatInputError extends TaggedError(
"InvalidDurationFormatInputError"
)<{ message: string }>() {
constructor() {
super({ message: "Invalid duration format provided" })
}
}

export class AssertUnreachableError extends TaggedError("AssertUnreachableError")<{
value: unknown
}>() {}

export class AssertConditionFailedError extends TaggedError("AssertConditionFailedError")<{
message: string
condition: string
}>() {}

export type DurationError = InvalidDurationParseInputError | InvalidDurationFormatInputError

export type AssertError = AssertUnreachableError | AssertConditionFailedError

export type UtilityError = DurationError | AssertError
Loading