feat: add file storage service and update configurations

refactor: migrate to tsup for builds and update tsconfigs
fix: update error handling and validation in payment service
chore: update package scripts and dependencies
docs: update README and env examples
This commit is contained in:
Yuu Ottosoka
2025-07-12 16:01:57 +03:00
parent c0c04bbdba
commit f9ecac94f2
67 changed files with 3308 additions and 695 deletions

View File

@@ -1,10 +1,17 @@
import { AppError, DatabaseError, ErrorCode, ValidationError } from './types';
import { ZodError } from "zod";
import {
AppError,
DatabaseError,
ErrorCode,
ValidationError,
ZodValidationError,
} from "./types";
export const createAppError = <T>(
code: T,
message: string,
details?: Record<string, unknown>,
cause?: Error
cause?: Error,
): AppError<T> => ({
code,
message,
@@ -15,7 +22,7 @@ export const createAppError = <T>(
export const createValidationError = (
field: string,
value: unknown,
message: string
message: string,
): ValidationError => ({
code: ErrorCode.VALIDATION_ERROR,
message,
@@ -25,11 +32,34 @@ export const createValidationError = (
export const databaseError = (
operation: string,
cause?: Error
cause?: Error,
): DatabaseError =>
createAppError(
ErrorCode.DATABASE_ERROR,
`Database error during ${operation}`,
undefined,
cause
cause,
);
export const createZodValidationError = (
zodError: ZodError,
context?: string,
): ZodValidationError => {
const issues = zodError.issues.map((issue) => ({
path: issue.path as ReadonlyArray<string | number>,
message: issue.message,
code: issue.code,
}));
const contextMessage = context ? ` in ${context}` : "";
const message = `Validation failed${contextMessage}: ${zodError.issues
.map((issue) => `${issue.path.join(".")} - ${issue.message}`)
.join(", ")}`;
return {
code: ErrorCode.ZOD_VALIDATION_ERROR,
message,
zodError,
issues,
};
};

View File

@@ -1,3 +1,3 @@
export * from './types';
export * from './constructors';
export * from './utils';
export * from "./types";
export * from "./constructors";
export * from "./utils";

View File

@@ -1,8 +1,11 @@
import { ZodError } from "zod";
export enum ErrorCode {
// System errors
DATABASE_ERROR = 'DATABASE_ERROR',
VALIDATION_ERROR = 'VALIDATION_ERROR',
UNKNOWN_ERROR = 'UNKNOWN_ERROR',
DATABASE_ERROR = "DATABASE_ERROR",
VALIDATION_ERROR = "VALIDATION_ERROR",
UNKNOWN_ERROR = "UNKNOWN_ERROR",
ZOD_VALIDATION_ERROR = "ZOD_VALIDATION_ERROR",
}
export type AppError<T> = {
@@ -19,3 +22,14 @@ export type ValidationError = AppError<ErrorCode.VALIDATION_ERROR> & {
};
export type DatabaseError = AppError<ErrorCode.DATABASE_ERROR>;
export interface ZodValidationError
extends AppError<ErrorCode.ZOD_VALIDATION_ERROR> {
readonly code: ErrorCode.ZOD_VALIDATION_ERROR;
readonly zodError: ZodError;
readonly issues: ReadonlyArray<{
readonly path: ReadonlyArray<string | number>;
readonly message: string;
readonly code: string;
}>;
}

View File

@@ -1,5 +1,5 @@
import { ResultAsync, err, ok } from 'neverthrow';
import { AppError } from '../types';
import { ResultAsync, err, ok } from "neverthrow";
import { AppError } from "../types";
/**
* Wraps a database operation in a Result type to handle errors gracefully
@@ -9,7 +9,7 @@ import { AppError } from '../types';
*/
export const wrapDbOperation = <T, E extends AppError<unknown>>(
operation: () => Promise<T>,
errorFactory: (error: unknown) => E
errorFactory: (error: unknown) => E,
): ResultAsync<T, E> => {
return ResultAsync.fromPromise(operation(), errorFactory);
};
@@ -17,9 +17,9 @@ export const wrapDbOperation = <T, E extends AppError<unknown>>(
export const wrapDbOperationWithNull = <T, E extends AppError<unknown>>(
operation: () => Promise<T | null>,
notFoundError: E,
dbErrorFactory: (error: unknown) => E
dbErrorFactory: (error: unknown) => E,
): ResultAsync<T, E> => {
return ResultAsync.fromPromise(operation(), (error) =>
dbErrorFactory(error)
dbErrorFactory(error),
).andThen((result) => (result === null ? err(notFoundError) : ok(result)));
};

View File

@@ -1,2 +1 @@
export * from './database';
export * from './validation';
export * from "./database";

View File

@@ -1,10 +0,0 @@
import { err, ok, Result } from 'neverthrow';
import { output, ZodError, ZodObject } from 'zod';
export const safeParse =
<T extends ZodObject>(schema: T) =>
<D>(data: D): Result<output<T>, ZodError<output<T>>> => {
const parseResult = schema.safeParse(data);
return parseResult.success ? ok(parseResult.data) : err(parseResult.error);
};