-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathconfig.ts
More file actions
95 lines (79 loc) · 3.16 KB
/
config.ts
File metadata and controls
95 lines (79 loc) · 3.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import dotenv from 'dotenv';
import { z } from 'zod';
dotenv.config();
function isAccelerateUrl(url: string) {
return url.startsWith('prisma://') || url.startsWith('prisma+postgres://');
}
function parseTrustProxy(value?: string) {
if (!value) {
return false;
}
const trimmed = value.trim();
if (trimmed === 'true') {
return true;
}
if (trimmed === 'false') {
return false;
}
if (/^\d+$/.test(trimmed)) {
return Number(trimmed);
}
if (trimmed.includes(',')) {
return trimmed
.split(',')
.map((entry) => entry.trim())
.filter(Boolean);
}
return trimmed;
}
const envSchema = z.object({
DATABASE_URL: z.string().min(1, 'DATABASE_URL cannot be empty').optional(),
DIRECT_DATABASE_URL: z.string().min(1, 'DIRECT_DATABASE_URL cannot be empty').optional(),
DIRECT_URL: z.string().min(1, 'DIRECT_URL cannot be empty').optional(),
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PAGE_SIZE: z.coerce.number().int().positive().max(100).default(10),
PORT: z.coerce.number().int().positive().default(8080),
REQUEST_BODY_LIMIT: z.string().trim().min(1).default('16kb'),
TRUST_PROXY: z.string().trim().min(1).optional(),
RATE_LIMIT_WINDOW_MS: z.coerce.number().int().positive().default(60_000),
RATE_LIMIT_MAX_REQUESTS: z.coerce.number().int().positive().default(120),
RATE_LIMIT_BURST_WINDOW_MS: z.coerce.number().int().positive().default(10_000),
RATE_LIMIT_BURST_MAX_REQUESTS: z.coerce.number().int().positive().default(30),
SEARCH_RATE_LIMIT_WINDOW_MS: z.coerce.number().int().positive().default(60_000),
SEARCH_RATE_LIMIT_MAX_REQUESTS: z.coerce.number().int().positive().default(30),
SEARCH_RATE_LIMIT_BURST_WINDOW_MS: z.coerce.number().int().positive().default(10_000),
SEARCH_RATE_LIMIT_BURST_MAX_REQUESTS: z.coerce.number().int().positive().default(10),
});
const env = envSchema.parse(process.env);
const databaseUrl = env.DATABASE_URL ?? env.DIRECT_DATABASE_URL ?? env.DIRECT_URL;
if (!databaseUrl) {
throw new Error('DATABASE_URL is required. DIRECT_DATABASE_URL or legacy DIRECT_URL may be used as a fallback.');
}
const usesAccelerate = isAccelerateUrl(databaseUrl);
const directDatabaseUrl = env.DIRECT_DATABASE_URL ?? env.DIRECT_URL ?? (usesAccelerate ? undefined : databaseUrl);
if (env.NODE_ENV !== 'production' && !directDatabaseUrl) {
throw new Error('Non-production requires a direct PostgreSQL URL via DIRECT_DATABASE_URL, DIRECT_URL, or DATABASE_URL.');
}
const config = {
databaseUrl,
directDatabaseUrl,
nodeEnv: env.NODE_ENV,
pageSize: env.PAGE_SIZE,
port: env.PORT,
requestBodyLimit: env.REQUEST_BODY_LIMIT,
trustProxy: parseTrustProxy(env.TRUST_PROXY),
rateLimit: {
burstMaxRequests: env.RATE_LIMIT_BURST_MAX_REQUESTS,
burstWindowMs: env.RATE_LIMIT_BURST_WINDOW_MS,
maxRequests: env.RATE_LIMIT_MAX_REQUESTS,
windowMs: env.RATE_LIMIT_WINDOW_MS,
},
searchRateLimit: {
burstMaxRequests: env.SEARCH_RATE_LIMIT_BURST_MAX_REQUESTS,
burstWindowMs: env.SEARCH_RATE_LIMIT_BURST_WINDOW_MS,
maxRequests: env.SEARCH_RATE_LIMIT_MAX_REQUESTS,
windowMs: env.SEARCH_RATE_LIMIT_WINDOW_MS,
},
usesAccelerate,
};
export default config;