pull/715/head
Travis Fischer 2025-04-24 07:21:23 +07:00
rodzic 5fa7c5ae2d
commit 1e90451b0c
6 zmienionych plików z 123 dodań i 94 usunięć

Wyświetl plik

@ -46,7 +46,8 @@
"postgres": "^3.4.5",
"restore-cursor": "catalog:",
"type-fest": "catalog:",
"zod": "catalog:"
"zod": "catalog:",
"zod-validation-error": "^3.4.0"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.9",

Wyświetl plik

@ -2,6 +2,8 @@ import 'dotenv/config'
import { z } from 'zod'
import { parseZodSchema } from './utils'
export const envSchema = z.object({
NODE_ENV: z
.enum(['development', 'test', 'production'])
@ -10,8 +12,11 @@ export const envSchema = z.object({
JWT_SECRET: z.string(),
PORT: z.number().default(3000)
})
export type Env = z.infer<typeof envSchema>
// eslint-disable-next-line no-process-env
export const env = envSchema.parse(process.env)
export const env = parseZodSchema(envSchema, process.env, {
error: 'Invalid environment variables'
})
export const isProd = env.NODE_ENV === 'production'

Wyświetl plik

@ -1,17 +1,41 @@
import type { ContentfulStatusCode } from 'hono/utils/http-status'
import { fromError } from 'zod-validation-error'
export class HttpError extends Error {
export class BaseError extends Error {
constructor({ message, cause }: { message: string; cause?: unknown }) {
super(message, { cause })
// Ensure the name of this error is the same as the class name
this.name = this.constructor.name
// Set stack trace to caller
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor)
}
}
}
export class HttpError extends BaseError {
readonly statusCode: ContentfulStatusCode
constructor({
statusCode = 500,
message
message,
cause
}: {
statusCode?: ContentfulStatusCode
message: string
cause?: unknown
}) {
super(message)
super({ message, cause })
this.statusCode = statusCode
}
}
export class ZodValidationError extends BaseError {
constructor({ prefix, cause }: { prefix?: string; cause: unknown }) {
const error = fromError(cause, { prefix })
super({ message: error.message, cause })
}
}

Wyświetl plik

@ -37,4 +37,6 @@ export function initExitHooks({
wait: timeoutMs
}
)
// TODO: On Node.js, log unhandledRejection, uncaughtException, and warning events
}

Wyświetl plik

@ -1,6 +1,9 @@
import { createHash, randomUUID } from 'node:crypto'
import { HttpError } from './errors'
import type { ContentfulStatusCode } from 'hono/utils/http-status'
import type { ZodSchema } from 'zod'
import { HttpError, ZodValidationError } from './errors'
export function sha256(input: string = randomUUID()) {
return createHash('sha256').update(input).digest('hex')
@ -9,12 +12,12 @@ export function sha256(input: string = randomUUID()) {
export function assert(expr: unknown, message?: string): asserts expr
export function assert(
expr: unknown,
statusCode?: number,
statusCode?: ContentfulStatusCode,
message?: string
): asserts expr
export function assert(
expr: unknown,
statusCodeOrMessage?: number | string,
statusCodeOrMessage?: ContentfulStatusCode | string,
message = 'Internal assertion failed'
): asserts expr {
if (expr) {
@ -27,3 +30,22 @@ export function assert(
throw new Error(statusCodeOrMessage ?? message)
}
}
export function parseZodSchema<T>(
schema: ZodSchema<T>,
input: unknown,
{
error
}: {
error?: string
} = {}
): T {
try {
return schema.parse(input)
} catch (err) {
throw new ZodValidationError({
prefix: error,
cause: err
})
}
}

Wyświetl plik

@ -6,15 +6,60 @@ settings:
catalogs:
default:
'@fisch0920/config':
specifier: ^1.0.4
version: 1.0.4
'@types/node':
specifier: ^22.14.1
version: 22.14.1
del-cli:
specifier: ^6.0.0
version: 6.0.0
dotenv:
specifier: ^16.5.0
version: 16.5.0
eslint:
specifier: ^9.25.1
version: 9.25.1
exit-hook:
specifier: ^4.0.0
version: 4.0.0
lint-staged:
specifier: ^15.5.1
version: 15.5.1
npm-run-all2:
specifier: ^7.0.2
version: 7.0.2
only-allow:
specifier: ^1.2.1
version: 1.2.1
prettier:
specifier: ^3.5.3
version: 3.5.3
restore-cursor:
specifier: ^5.1.0
version: 5.1.0
simple-git-hooks:
specifier: ^2.12.1
version: 2.12.1
tsup:
specifier: ^8.4.0
version: 8.4.0
tsx:
specifier: ^4.19.3
version: 4.19.3
turbo:
specifier: ^2.5.0
version: 2.5.0
type-fest:
specifier: ^4.40.0
version: 4.40.0
typescript:
specifier: ^5.8.3
version: 5.8.3
vitest:
specifier: ^3.1.2
version: 3.1.2
zod:
specifier: ^3.24.3
version: 3.24.3
@ -116,6 +161,9 @@ importers:
zod:
specifier: 'catalog:'
version: 3.24.3
zod-validation-error:
specifier: ^3.4.0
version: 3.4.0(zod@3.24.3)
devDependencies:
'@types/jsonwebtoken':
specifier: ^9.0.9
@ -771,10 +819,6 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/scope-manager@8.29.0':
resolution: {integrity: sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/scope-manager@8.31.0':
resolution: {integrity: sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -786,33 +830,16 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/types@8.29.0':
resolution: {integrity: sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/types@8.31.0':
resolution: {integrity: sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.29.0':
resolution: {integrity: sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/typescript-estree@8.31.0':
resolution: {integrity: sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/utils@8.29.0':
resolution: {integrity: sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/utils@8.31.0':
resolution: {integrity: sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -820,10 +847,6 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/visitor-keys@8.29.0':
resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/visitor-keys@8.31.0':
resolution: {integrity: sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -1532,14 +1555,6 @@ packages:
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
fdir@6.4.3:
resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
fdir@6.4.4:
resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==}
peerDependencies:
@ -2666,10 +2681,6 @@ packages:
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
tinyglobby@0.2.12:
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
engines: {node: '>=12.0.0'}
tinyglobby@0.2.13:
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
engines: {node: '>=12.0.0'}
@ -2979,6 +2990,12 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
zod-validation-error@3.4.0:
resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
zod: ^3.18.0
zod@3.24.3:
resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
@ -3508,11 +3525,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.29.0':
dependencies:
'@typescript-eslint/types': 8.29.0
'@typescript-eslint/visitor-keys': 8.29.0
'@typescript-eslint/scope-manager@8.31.0':
dependencies:
'@typescript-eslint/types': 8.31.0
@ -3529,24 +3541,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.29.0': {}
'@typescript-eslint/types@8.31.0': {}
'@typescript-eslint/typescript-estree@8.29.0(typescript@5.8.3)':
dependencies:
'@typescript-eslint/types': 8.29.0
'@typescript-eslint/visitor-keys': 8.29.0
debug: 4.4.0
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.7.1
ts-api-utils: 2.1.0(typescript@5.8.3)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/typescript-estree@8.31.0(typescript@5.8.3)':
dependencies:
'@typescript-eslint/types': 8.31.0
@ -3561,17 +3557,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.29.0(eslint@9.25.1)(typescript@5.8.3)':
dependencies:
'@eslint-community/eslint-utils': 4.5.1(eslint@9.25.1)
'@typescript-eslint/scope-manager': 8.29.0
'@typescript-eslint/types': 8.29.0
'@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.3)
eslint: 9.25.1
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.31.0(eslint@9.25.1)(typescript@5.8.3)':
dependencies:
'@eslint-community/eslint-utils': 4.5.1(eslint@9.25.1)
@ -3583,11 +3568,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/visitor-keys@8.29.0':
dependencies:
'@typescript-eslint/types': 8.29.0
eslint-visitor-keys: 4.2.0
'@typescript-eslint/visitor-keys@8.31.0':
dependencies:
'@typescript-eslint/types': 8.31.0
@ -4285,8 +4265,8 @@ snapshots:
eslint-plugin-testing-library@7.1.1(eslint@9.25.1)(typescript@5.8.3):
dependencies:
'@typescript-eslint/scope-manager': 8.29.0
'@typescript-eslint/utils': 8.29.0(eslint@9.25.1)(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.31.0
'@typescript-eslint/utils': 8.31.0(eslint@9.25.1)(typescript@5.8.3)
eslint: 9.25.1
transitivePeerDependencies:
- supports-color
@ -4420,10 +4400,6 @@ snapshots:
dependencies:
reusify: 1.1.0
fdir@6.4.3(picomatch@4.0.2):
optionalDependencies:
picomatch: 4.0.2
fdir@6.4.4(picomatch@4.0.2):
optionalDependencies:
picomatch: 4.0.2
@ -5588,11 +5564,6 @@ snapshots:
tinyexec@0.3.2: {}
tinyglobby@0.2.12:
dependencies:
fdir: 6.4.3(picomatch@4.0.2)
picomatch: 4.0.2
tinyglobby@0.2.13:
dependencies:
fdir: 6.4.4(picomatch@4.0.2)
@ -5645,7 +5616,7 @@ snapshots:
source-map: 0.8.0-beta.0
sucrase: 3.35.0
tinyexec: 0.3.2
tinyglobby: 0.2.12
tinyglobby: 0.2.13
tree-kill: 1.2.2
optionalDependencies:
postcss: 8.5.3
@ -5939,4 +5910,8 @@ snapshots:
yocto-queue@0.1.0: {}
zod-validation-error@3.4.0(zod@3.24.3):
dependencies:
zod: 3.24.3
zod@3.24.3: {}