saas-backend-kit 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +123 -344
  2. package/copy-dts.js +59 -0
  3. package/dist/auth/index.js +7 -2
  4. package/dist/auth/index.js.map +1 -1
  5. package/dist/auth/index.mjs +7 -2
  6. package/dist/auth/index.mjs.map +1 -1
  7. package/dist/config/index.js +6 -1
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/config/index.mjs +6 -1
  10. package/dist/config/index.mjs.map +1 -1
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.js +232 -41
  13. package/dist/index.js.map +1 -1
  14. package/dist/index.mjs +231 -42
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/logger/index.js +6 -1
  17. package/dist/logger/index.js.map +1 -1
  18. package/dist/logger/index.mjs +6 -1
  19. package/dist/logger/index.mjs.map +1 -1
  20. package/dist/notifications/index.js +6 -1
  21. package/dist/notifications/index.js.map +1 -1
  22. package/dist/notifications/index.mjs +6 -1
  23. package/dist/notifications/index.mjs.map +1 -1
  24. package/dist/queue/index.js +6 -1
  25. package/dist/queue/index.js.map +1 -1
  26. package/dist/queue/index.mjs +6 -1
  27. package/dist/queue/index.mjs.map +1 -1
  28. package/dist/rate-limit/index.js +7 -1
  29. package/dist/rate-limit/index.js.map +1 -1
  30. package/dist/rate-limit/index.mjs +7 -1
  31. package/dist/rate-limit/index.mjs.map +1 -1
  32. package/dist/response/index.js +51 -40
  33. package/dist/response/index.js.map +1 -1
  34. package/dist/response/index.mjs +51 -40
  35. package/dist/response/index.mjs.map +1 -1
  36. package/dist/upload/index.d.ts +57 -0
  37. package/dist/upload/index.js +344 -0
  38. package/dist/upload/index.js.map +1 -0
  39. package/dist/upload/index.mjs +334 -0
  40. package/dist/upload/index.mjs.map +1 -0
  41. package/jest-output.json +72 -0
  42. package/jest.config.js +19 -0
  43. package/package.json +20 -8
  44. package/saas-banner.svg +239 -0
  45. package/src/auth/jwt.ts +1 -1
  46. package/src/config/index.ts +5 -0
  47. package/src/index.ts +2 -0
  48. package/src/rate-limit/express.ts +1 -0
  49. package/src/response/index.ts +49 -40
  50. package/src/upload/index.ts +268 -0
  51. package/tests/auth.test.ts +134 -0
  52. package/tests/config.test.ts +36 -0
  53. package/tests/logger.test.ts +47 -0
  54. package/tests/notifications.test.ts +19 -0
  55. package/tests/rate-limit.test.ts +50 -0
  56. package/tests/upload.test.ts +33 -0
  57. package/tsconfig.test.json +14 -0
  58. package/tsup.config.ts +2 -1
@@ -0,0 +1,344 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var clientS3 = require('@aws-sdk/client-s3');
6
+ var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
7
+ var zod = require('zod');
8
+ var pino = require('pino');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var pino__default = /*#__PURE__*/_interopDefault(pino);
13
+
14
+ // src/upload/index.ts
15
+ var envSchema = zod.z.object({
16
+ NODE_ENV: zod.z.enum(["development", "production", "test"]).default("development"),
17
+ PORT: zod.z.string().default("3000"),
18
+ DATABASE_URL: zod.z.string().optional(),
19
+ REDIS_URL: zod.z.string().default("redis://localhost:6379"),
20
+ JWT_SECRET: zod.z.string().min(32).optional(),
21
+ JWT_EXPIRES_IN: zod.z.string().default("7d"),
22
+ JWT_REFRESH_SECRET: zod.z.string().min(32).optional(),
23
+ JWT_REFRESH_EXPIRES_IN: zod.z.string().default("30d"),
24
+ GOOGLE_CLIENT_ID: zod.z.string().optional(),
25
+ GOOGLE_CLIENT_SECRET: zod.z.string().optional(),
26
+ GOOGLE_REDIRECT_URI: zod.z.string().optional(),
27
+ SMTP_HOST: zod.z.string().optional(),
28
+ SMTP_PORT: zod.z.string().default("587"),
29
+ SMTP_USER: zod.z.string().optional(),
30
+ SMTP_PASS: zod.z.string().optional(),
31
+ SMTP_FROM: zod.z.string().optional(),
32
+ TWILIO_ACCOUNT_SID: zod.z.string().optional(),
33
+ TWILIO_AUTH_TOKEN: zod.z.string().optional(),
34
+ TWILIO_PHONE_NUMBER: zod.z.string().optional(),
35
+ SLACK_WEBHOOK_URL: zod.z.string().optional(),
36
+ RATE_LIMIT_WINDOW: zod.z.string().default("1m"),
37
+ RATE_LIMIT_LIMIT: zod.z.string().default("100"),
38
+ LOG_LEVEL: zod.z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).default("info"),
39
+ AWS_REGION: zod.z.string().default("us-east-1"),
40
+ AWS_ACCESS_KEY_ID: zod.z.string().optional(),
41
+ AWS_SECRET_ACCESS_KEY: zod.z.string().optional(),
42
+ AWS_S3_BUCKET: zod.z.string().optional(),
43
+ AWS_ENDPOINT: zod.z.string().optional()
44
+ });
45
+ var ConfigManager = class {
46
+ config = null;
47
+ schema;
48
+ validate;
49
+ constructor(options = {}) {
50
+ this.schema = options.schema || envSchema;
51
+ this.validate = options.validate ?? true;
52
+ }
53
+ load() {
54
+ if (this.config) return this.config;
55
+ const env = {};
56
+ for (const key of Object.keys(this.schema.shape)) {
57
+ env[key] = process.env[key];
58
+ }
59
+ if (this.validate) {
60
+ const result = this.schema.safeParse(env);
61
+ if (!result.success) {
62
+ const errors = result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
63
+ throw new Error(`Config validation failed: ${errors}`);
64
+ }
65
+ this.config = result.data;
66
+ } else {
67
+ this.config = env;
68
+ }
69
+ return this.config;
70
+ }
71
+ get(key) {
72
+ if (!this.config) this.load();
73
+ return this.config[key];
74
+ }
75
+ int(key) {
76
+ const value = this.get(key);
77
+ if (typeof value === "string") return parseInt(value, 10);
78
+ return Number(value);
79
+ }
80
+ bool(key) {
81
+ const value = this.get(key);
82
+ if (typeof value === "boolean") return value;
83
+ if (typeof value === "string") return value.toLowerCase() === "true";
84
+ return Boolean(value);
85
+ }
86
+ isProduction() {
87
+ return this.get("NODE_ENV") === "production";
88
+ }
89
+ isDevelopment() {
90
+ return this.get("NODE_ENV") === "development";
91
+ }
92
+ isTest() {
93
+ return this.get("NODE_ENV") === "test";
94
+ }
95
+ getAll() {
96
+ if (!this.config) this.load();
97
+ return this.config;
98
+ }
99
+ };
100
+ var globalConfig = new ConfigManager();
101
+ var config = {
102
+ load: () => globalConfig.load(),
103
+ get: (key) => globalConfig.get(key),
104
+ int: (key) => globalConfig.int(key),
105
+ bool: (key) => globalConfig.bool(key),
106
+ isProduction: () => globalConfig.isProduction(),
107
+ isDevelopment: () => globalConfig.isDevelopment(),
108
+ isTest: () => globalConfig.isTest(),
109
+ getAll: () => globalConfig.getAll(),
110
+ create: (options) => new ConfigManager(options)
111
+ };
112
+ var LoggerManager = class {
113
+ loggers = /* @__PURE__ */ new Map();
114
+ defaultLogger;
115
+ constructor() {
116
+ const level = config.get("LOG_LEVEL") || "info";
117
+ this.defaultLogger = pino__default.default({
118
+ level,
119
+ name: "saas-backend-kit",
120
+ formatters: {
121
+ bindings: (bindings) => ({
122
+ ...bindings,
123
+ service: "saas-backend-kit"
124
+ })
125
+ }
126
+ });
127
+ }
128
+ createLogger(options = {}) {
129
+ const name = options.name || "default";
130
+ if (this.loggers.has(name)) {
131
+ return this.loggers.get(name);
132
+ }
133
+ const level = options.level || config.get("LOG_LEVEL") || "info";
134
+ const logger2 = pino__default.default({
135
+ level,
136
+ name: options.name,
137
+ ...options
138
+ });
139
+ this.loggers.set(name, logger2);
140
+ return logger2;
141
+ }
142
+ getLogger(name) {
143
+ if (name) {
144
+ return this.loggers.get(name) || this.defaultLogger;
145
+ }
146
+ return this.defaultLogger;
147
+ }
148
+ child(bindings, options) {
149
+ const name = options?.name || "child";
150
+ const parent = options?.name ? this.getLogger(name) : this.defaultLogger;
151
+ return parent.child(bindings);
152
+ }
153
+ };
154
+ var loggerManager = new LoggerManager();
155
+ var logger = {
156
+ info: (message, ...args) => loggerManager.getLogger().info(message, ...args),
157
+ warn: (message, ...args) => loggerManager.getLogger().warn(message, ...args),
158
+ error: (message, ...args) => loggerManager.getLogger().error(message, ...args),
159
+ debug: (message, ...args) => loggerManager.getLogger().debug(message, ...args),
160
+ trace: (message, ...args) => loggerManager.getLogger().trace(message, ...args),
161
+ fatal: (message, ...args) => loggerManager.getLogger().fatal(message, ...args),
162
+ child: (bindings, options) => loggerManager.child(bindings, options),
163
+ create: (options) => loggerManager.createLogger(options),
164
+ get: (name) => loggerManager.getLogger(name)
165
+ };
166
+
167
+ // src/upload/index.ts
168
+ var S3Service = class {
169
+ client = null;
170
+ bucket;
171
+ initialized = false;
172
+ constructor() {
173
+ this.bucket = "";
174
+ }
175
+ initialize(config2) {
176
+ this.client = new clientS3.S3Client({
177
+ region: config2.region || "us-east-1",
178
+ credentials: config2.accessKeyId && config2.secretAccessKey ? {
179
+ accessKeyId: config2.accessKeyId,
180
+ secretAccessKey: config2.secretAccessKey
181
+ } : void 0,
182
+ endpoint: config2.endpoint,
183
+ forcePathStyle: config2.forcePathStyle || false
184
+ });
185
+ this.bucket = config2.bucket;
186
+ this.initialized = true;
187
+ logger.info("S3 service initialized", { bucket: this.bucket });
188
+ }
189
+ isInitialized() {
190
+ return this.initialized;
191
+ }
192
+ ensureInitialized() {
193
+ if (!this.initialized) {
194
+ const region = config.get("AWS_REGION") || "us-east-1";
195
+ const bucket = config.get("AWS_S3_BUCKET") || "";
196
+ this.initialize({
197
+ region,
198
+ accessKeyId: config.get("AWS_ACCESS_KEY_ID"),
199
+ secretAccessKey: config.get("AWS_SECRET_ACCESS_KEY"),
200
+ bucket,
201
+ endpoint: config.get("AWS_ENDPOINT")
202
+ });
203
+ }
204
+ }
205
+ async upload(file, options = {}) {
206
+ this.ensureInitialized();
207
+ const key = options.key || this.generateKey();
208
+ const contentType = options.contentType || this.guessContentType(key);
209
+ const command = new clientS3.PutObjectCommand({
210
+ Bucket: this.bucket,
211
+ Key: key,
212
+ Body: file,
213
+ ContentType: contentType,
214
+ Metadata: options.metadata
215
+ });
216
+ await this.client.send(command);
217
+ const url = await this.getSignedUrl(key, { expiresIn: options.expiresIn || 3600 });
218
+ logger.info("File uploaded to S3", { key, bucket: this.bucket, contentType });
219
+ return {
220
+ key,
221
+ url,
222
+ bucket: this.bucket,
223
+ contentType
224
+ };
225
+ }
226
+ async uploadImage(file, filename, options = {}) {
227
+ const key = options.key || `images/${Date.now()}-${filename}`;
228
+ return this.upload(file, {
229
+ ...options,
230
+ key,
231
+ contentType: options.contentType || this.getImageContentType(filename)
232
+ });
233
+ }
234
+ async uploadVideo(file, filename, options = {}) {
235
+ const key = options.key || `videos/${Date.now()}-${filename}`;
236
+ return this.upload(file, {
237
+ ...options,
238
+ key,
239
+ contentType: options.contentType || this.getVideoContentType(filename)
240
+ });
241
+ }
242
+ async delete(key) {
243
+ this.ensureInitialized();
244
+ const command = new clientS3.DeleteObjectCommand({
245
+ Bucket: this.bucket,
246
+ Key: key
247
+ });
248
+ await this.client.send(command);
249
+ logger.info("File deleted from S3", { key, bucket: this.bucket });
250
+ }
251
+ async getSignedUrl(key, options = {}) {
252
+ this.ensureInitialized();
253
+ const command = new clientS3.GetObjectCommand({
254
+ Bucket: this.bucket,
255
+ Key: key
256
+ });
257
+ return s3RequestPresigner.getSignedUrl(this.client, command, {
258
+ expiresIn: options.expiresIn || 3600
259
+ });
260
+ }
261
+ async getPublicUrl(key) {
262
+ return `https://${this.bucket}.s3.${config.get("AWS_REGION") || "us-east-1"}.amazonaws.com/${key}`;
263
+ }
264
+ async listFiles(prefix, maxKeys = 1e3) {
265
+ this.ensureInitialized();
266
+ const command = new clientS3.ListObjectsV2Command({
267
+ Bucket: this.bucket,
268
+ Prefix: prefix,
269
+ MaxKeys: maxKeys
270
+ });
271
+ const response = await this.client.send(command);
272
+ return (response.Contents || []).map((item) => ({
273
+ key: item.Key || "",
274
+ lastModified: item.LastModified,
275
+ size: item.Size
276
+ }));
277
+ }
278
+ generateKey() {
279
+ const timestamp = Date.now();
280
+ const random = Math.random().toString(36).substring(2, 15);
281
+ return `uploads/${timestamp}-${random}`;
282
+ }
283
+ guessContentType(key) {
284
+ const ext = key.split(".").pop()?.toLowerCase();
285
+ const contentTypes = {
286
+ jpg: "image/jpeg",
287
+ jpeg: "image/jpeg",
288
+ png: "image/png",
289
+ gif: "image/gif",
290
+ webp: "image/webp",
291
+ svg: "image/svg+xml",
292
+ mp4: "video/mp4",
293
+ webm: "video/webm",
294
+ mov: "video/quicktime",
295
+ avi: "video/x-msvideo",
296
+ pdf: "application/pdf",
297
+ json: "application/json",
298
+ txt: "text/plain"
299
+ };
300
+ return contentTypes[ext || ""] || "application/octet-stream";
301
+ }
302
+ getImageContentType(filename) {
303
+ const ext = filename.split(".").pop()?.toLowerCase();
304
+ const imageTypes = {
305
+ jpg: "image/jpeg",
306
+ jpeg: "image/jpeg",
307
+ png: "image/png",
308
+ gif: "image/gif",
309
+ webp: "image/webp",
310
+ svg: "image/svg+xml"
311
+ };
312
+ return imageTypes[ext || ""] || "image/jpeg";
313
+ }
314
+ getVideoContentType(filename) {
315
+ const ext = filename.split(".").pop()?.toLowerCase();
316
+ const videoTypes = {
317
+ mp4: "video/mp4",
318
+ webm: "video/webm",
319
+ mov: "video/quicktime",
320
+ avi: "video/x-msvideo",
321
+ mkv: "video/x-matroska",
322
+ ogv: "video/ogg"
323
+ };
324
+ return videoTypes[ext || ""] || "video/mp4";
325
+ }
326
+ };
327
+ var s3Service = new S3Service();
328
+ var upload = {
329
+ initialize: (config2) => s3Service.initialize(config2),
330
+ file: (file, options) => s3Service.upload(file, options),
331
+ image: (file, filename, options) => s3Service.uploadImage(file, filename, options),
332
+ video: (file, filename, options) => s3Service.uploadVideo(file, filename, options),
333
+ delete: (key) => s3Service.delete(key),
334
+ getSignedUrl: (key, options) => s3Service.getSignedUrl(key, options),
335
+ getPublicUrl: (key) => s3Service.getPublicUrl(key),
336
+ listFiles: (prefix, maxKeys) => s3Service.listFiles(prefix, maxKeys)
337
+ };
338
+ var upload_default = upload;
339
+
340
+ exports.default = upload_default;
341
+ exports.s3Service = s3Service;
342
+ exports.upload = upload;
343
+ //# sourceMappingURL=index.js.map
344
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/config/index.ts","../../src/logger/index.ts","../../src/upload/index.ts"],"names":["z","pino","logger","config","S3Client","PutObjectCommand","DeleteObjectCommand","GetObjectCommand","getSignedUrl","ListObjectsV2Command"],"mappings":";;;;;;;;;;;;;;AAEO,IAAM,SAAA,GAAYA,MAAE,MAAA,CAAO;AAAA,EAChC,QAAA,EAAUA,KAAA,CAAE,IAAA,CAAK,CAAC,aAAA,EAAe,cAAc,MAAM,CAAC,CAAA,CAAE,OAAA,CAAQ,aAAa,CAAA;AAAA,EAC7E,IAAA,EAAMA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,MAAM,CAAA;AAAA,EAC/B,YAAA,EAAcA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAClC,SAAA,EAAWA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,wBAAwB,CAAA;AAAA,EACtD,YAAYA,KAAA,CAAE,MAAA,GAAS,GAAA,CAAI,EAAE,EAAE,QAAA,EAAS;AAAA,EACxC,cAAA,EAAgBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,IAAI,CAAA;AAAA,EACvC,oBAAoBA,KAAA,CAAE,MAAA,GAAS,GAAA,CAAI,EAAE,EAAE,QAAA,EAAS;AAAA,EAChD,sBAAA,EAAwBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,KAAK,CAAA;AAAA,EAChD,gBAAA,EAAkBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACtC,oBAAA,EAAsBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC1C,mBAAA,EAAqBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACzC,SAAA,EAAWA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC/B,SAAA,EAAWA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,KAAK,CAAA;AAAA,EACnC,SAAA,EAAWA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC/B,SAAA,EAAWA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC/B,SAAA,EAAWA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC/B,kBAAA,EAAoBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACxC,iBAAA,EAAmBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACvC,mBAAA,EAAqBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACzC,iBAAA,EAAmBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACvC,iBAAA,EAAmBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,IAAI,CAAA;AAAA,EAC1C,gBAAA,EAAkBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,KAAK,CAAA;AAAA,EAC1C,SAAA,EAAWA,KAAA,CAAE,IAAA,CAAK,CAAC,OAAA,EAAS,OAAA,EAAS,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,OAAO,CAAC,CAAA,CAAE,QAAQ,MAAM,CAAA;AAAA,EACtF,UAAA,EAAYA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,WAAW,CAAA;AAAA,EAC1C,iBAAA,EAAmBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACvC,qBAAA,EAAuBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC3C,aAAA,EAAeA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACnC,YAAA,EAAcA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AAC3B,CAAC,CAAA;AAUD,IAAM,gBAAN,MAAoB;AAAA,EACV,MAAA,GAA2B,IAAA;AAAA,EAC3B,MAAA;AAAA,EACA,QAAA;AAAA,EAER,WAAA,CAAY,OAAA,GAAyB,EAAC,EAAG;AACvC,IAAA,IAAA,CAAK,MAAA,GAAS,QAAQ,MAAA,IAAU,SAAA;AAChC,IAAA,IAAA,CAAK,QAAA,GAAW,QAAQ,QAAA,IAAY,IAAA;AAAA,EACtC;AAAA,EAEA,IAAA,GAAkB;AAChB,IAAA,IAAI,IAAA,CAAK,MAAA,EAAQ,OAAO,IAAA,CAAK,MAAA;AAE7B,IAAA,MAAM,MAA0C,EAAC;AAEjD,IAAA,KAAA,MAAW,OAAO,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,EAAG;AAChD,MAAA,GAAA,CAAI,GAAG,CAAA,GAAI,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA;AAAA,IAC5B;AAEA,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,GAAG,CAAA;AACxC,MAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,QAAA,MAAM,SAAS,MAAA,CAAO,KAAA,CAAM,OAAO,GAAA,CAAI,CAAA,CAAA,KAAK,GAAG,CAAA,CAAE,IAAA,CAAK,IAAA,CAAK,GAAG,CAAC,CAAA,EAAA,EAAK,CAAA,CAAE,OAAO,CAAA,CAAE,CAAA,CAAE,KAAK,IAAI,CAAA;AAC1F,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,MAAM,CAAA,CAAE,CAAA;AAAA,MACvD;AACA,MAAA,IAAA,CAAK,SAAS,MAAA,CAAO,IAAA;AAAA,IACvB,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,MAAA,GAAS,GAAA;AAAA,IAChB;AAEA,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA,EAEA,IAA+B,GAAA,EAAsB;AACnD,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,EAAQ,IAAA,CAAK,IAAA,EAAK;AAC5B,IAAA,OAAO,IAAA,CAAK,OAAQ,GAAG,CAAA;AAAA,EACzB;AAAA,EAEA,IAAI,GAAA,EAA8B;AAChC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA;AAC1B,IAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,QAAA,CAAS,OAAO,EAAE,CAAA;AACxD,IAAA,OAAO,OAAO,KAAK,CAAA;AAAA,EACrB;AAAA,EAEA,KAAK,GAAA,EAA+B;AAClC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA;AAC1B,IAAA,IAAI,OAAO,KAAA,KAAU,SAAA,EAAW,OAAO,KAAA;AACvC,IAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,KAAA,CAAM,aAAY,KAAM,MAAA;AAC9D,IAAA,OAAO,QAAQ,KAAK,CAAA;AAAA,EACtB;AAAA,EAEA,YAAA,GAAwB;AACtB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,UAAU,CAAA,KAAM,YAAA;AAAA,EAClC;AAAA,EAEA,aAAA,GAAyB;AACvB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,UAAU,CAAA,KAAM,aAAA;AAAA,EAClC;AAAA,EAEA,MAAA,GAAkB;AAChB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,UAAU,CAAA,KAAM,MAAA;AAAA,EAClC;AAAA,EAEA,MAAA,GAAoB;AAClB,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,EAAQ,IAAA,CAAK,IAAA,EAAK;AAC5B,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AACF,CAAA;AAEA,IAAM,YAAA,GAAe,IAAI,aAAA,EAAc;AAEhC,IAAM,MAAA,GAAS;AAAA,EACpB,IAAA,EAAM,MAAM,YAAA,CAAa,IAAA,EAAK;AAAA,EAC9B,GAAA,EAAK,CAA4B,GAAA,KAAW,YAAA,CAAa,IAAI,GAAG,CAAA;AAAA,EAChE,GAAA,EAAK,CAAC,GAAA,KAAyB,YAAA,CAAa,IAAI,GAAG,CAAA;AAAA,EACnD,IAAA,EAAM,CAAC,GAAA,KAAyB,YAAA,CAAa,KAAK,GAAG,CAAA;AAAA,EACrD,YAAA,EAAc,MAAM,YAAA,CAAa,YAAA,EAAa;AAAA,EAC9C,aAAA,EAAe,MAAM,YAAA,CAAa,aAAA,EAAc;AAAA,EAChD,MAAA,EAAQ,MAAM,YAAA,CAAa,MAAA,EAAO;AAAA,EAClC,MAAA,EAAQ,MAAM,YAAA,CAAa,MAAA,EAAO;AAAA,EAClC,MAAA,EAAQ,CAAC,OAAA,KAA4B,IAAI,cAAc,OAAO;AAChE,CAAA;AC1GA,IAAM,gBAAN,MAAoB;AAAA,EACV,OAAA,uBAAmC,GAAA,EAAI;AAAA,EACvC,aAAA;AAAA,EAER,WAAA,GAAc;AACZ,IAAA,MAAM,KAAA,GAAS,MAAA,CAAO,GAAA,CAAI,WAAW,CAAA,IAAkB,MAAA;AACvD,IAAA,IAAA,CAAK,gBAAgBC,qBAAA,CAAK;AAAA,MACxB,KAAA;AAAA,MACA,IAAA,EAAM,kBAAA;AAAA,MACN,UAAA,EAAY;AAAA,QACV,QAAA,EAAU,CAAC,QAAA,MAAwB;AAAA,UACjC,GAAG,QAAA;AAAA,UACH,OAAA,EAAS;AAAA,SACX;AAAA;AACF,KACD,CAAA;AAAA,EACH;AAAA,EAEA,YAAA,CAAa,OAAA,GAAwB,EAAC,EAAW;AAC/C,IAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,IAAQ,SAAA;AAE7B,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA,EAAG;AAC1B,MAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA;AAAA,IAC9B;AAEA,IAAA,MAAM,QAAQ,OAAA,CAAQ,KAAA,IAAU,MAAA,CAAO,GAAA,CAAI,WAAW,CAAA,IAAkB,MAAA;AAExE,IAAA,MAAMC,UAASD,qBAAA,CAAK;AAAA,MAClB,KAAA;AAAA,MACA,MAAM,OAAA,CAAQ,IAAA;AAAA,MACd,GAAG;AAAA,KACJ,CAAA;AAED,IAAA,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAA,EAAMC,OAAM,CAAA;AAC7B,IAAA,OAAOA,OAAAA;AAAA,EACT;AAAA,EAEA,UAAU,IAAA,EAAuB;AAC/B,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,KAAK,IAAA,CAAK,aAAA;AAAA,IACxC;AACA,IAAA,OAAO,IAAA,CAAK,aAAA;AAAA,EACd;AAAA,EAEA,KAAA,CAAM,UAAoB,OAAA,EAAqC;AAC7D,IAAA,MAAM,IAAA,GAAO,SAAS,IAAA,IAAQ,OAAA;AAC9B,IAAA,MAAM,SAAS,OAAA,EAAS,IAAA,GAAO,KAAK,SAAA,CAAU,IAAI,IAAI,IAAA,CAAK,aAAA;AAC3D,IAAA,OAAO,MAAA,CAAO,MAAM,QAAQ,CAAA;AAAA,EAC9B;AACF,CAAA;AAEA,IAAM,aAAA,GAAgB,IAAI,aAAA,EAAc;AAEjC,IAAM,MAAA,GAAS;AAAA,EACpB,IAAA,EAAM,CAAC,OAAA,EAAA,GAAoB,IAAA,KAAoB,aAAA,CAAc,WAAU,CAAE,IAAA,CAAK,OAAA,EAAS,GAAG,IAAI,CAAA;AAAA,EAC9F,IAAA,EAAM,CAAC,OAAA,EAAA,GAAoB,IAAA,KAAoB,aAAA,CAAc,WAAU,CAAE,IAAA,CAAK,OAAA,EAAS,GAAG,IAAI,CAAA;AAAA,EAC9F,KAAA,EAAO,CAAC,OAAA,EAAA,GAAoB,IAAA,KAAoB,aAAA,CAAc,WAAU,CAAE,KAAA,CAAM,OAAA,EAAS,GAAG,IAAI,CAAA;AAAA,EAChG,KAAA,EAAO,CAAC,OAAA,EAAA,GAAoB,IAAA,KAAoB,aAAA,CAAc,WAAU,CAAE,KAAA,CAAM,OAAA,EAAS,GAAG,IAAI,CAAA;AAAA,EAChG,KAAA,EAAO,CAAC,OAAA,EAAA,GAAoB,IAAA,KAAoB,aAAA,CAAc,WAAU,CAAE,KAAA,CAAM,OAAA,EAAS,GAAG,IAAI,CAAA;AAAA,EAChG,KAAA,EAAO,CAAC,OAAA,EAAA,GAAoB,IAAA,KAAoB,aAAA,CAAc,WAAU,CAAE,KAAA,CAAM,OAAA,EAAS,GAAG,IAAI,CAAA;AAAA,EAChG,OAAO,CAAC,QAAA,EAAoB,YAAgC,aAAA,CAAc,KAAA,CAAM,UAAU,OAAO,CAAA;AAAA,EACjG,MAAA,EAAQ,CAAC,OAAA,KAA2B,aAAA,CAAc,aAAa,OAAO,CAAA;AAAA,EACtE,GAAA,EAAK,CAAC,IAAA,KAAkB,aAAA,CAAc,UAAU,IAAI;AACtD,CAAA;;;ACvCA,IAAM,YAAN,MAAgB;AAAA,EACN,MAAA,GAA0B,IAAA;AAAA,EAC1B,MAAA;AAAA,EACA,WAAA,GAAuB,KAAA;AAAA,EAE/B,WAAA,GAAc;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,EAAA;AAAA,EAChB;AAAA,EAEA,WAAWC,OAAAA,EAAwB;AACjC,IAAA,IAAA,CAAK,MAAA,GAAS,IAAIC,iBAAA,CAAS;AAAA,MACzB,MAAA,EAAQD,QAAO,MAAA,IAAU,WAAA;AAAA,MACzB,WAAA,EAAaA,OAAAA,CAAO,WAAA,IAAeA,OAAAA,CAAO,eAAA,GACtC;AAAA,QACE,aAAaA,OAAAA,CAAO,WAAA;AAAA,QACpB,iBAAiBA,OAAAA,CAAO;AAAA,OAC1B,GACA,MAAA;AAAA,MACJ,UAAUA,OAAAA,CAAO,QAAA;AAAA,MACjB,cAAA,EAAgBA,QAAO,cAAA,IAAkB;AAAA,KAC1C,CAAA;AAED,IAAA,IAAA,CAAK,SAASA,OAAAA,CAAO,MAAA;AACrB,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AACnB,IAAA,MAAA,CAAO,KAAK,wBAAA,EAA0B,EAAE,MAAA,EAAQ,IAAA,CAAK,QAAQ,CAAA;AAAA,EAC/D;AAAA,EAEA,aAAA,GAAyB;AACvB,IAAA,OAAO,IAAA,CAAK,WAAA;AAAA,EACd;AAAA,EAEQ,iBAAA,GAA0B;AAChC,IAAA,IAAI,CAAC,KAAK,WAAA,EAAa;AACrB,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,GAAA,CAAI,YAAY,CAAA,IAAK,WAAA;AAC3C,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,GAAA,CAAI,eAAe,CAAA,IAAK,EAAA;AAE9C,MAAA,IAAA,CAAK,UAAA,CAAW;AAAA,QACd,MAAA;AAAA,QACA,WAAA,EAAa,MAAA,CAAO,GAAA,CAAI,mBAAmB,CAAA;AAAA,QAC3C,eAAA,EAAiB,MAAA,CAAO,GAAA,CAAI,uBAAuB,CAAA;AAAA,QACnD,MAAA;AAAA,QACA,QAAA,EAAU,MAAA,CAAO,GAAA,CAAI,cAAc;AAAA,OACpC,CAAA;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,MAAA,CACJ,IAAA,EACA,OAAA,GAAyB,EAAC,EACH;AACvB,IAAA,IAAA,CAAK,iBAAA,EAAkB;AAEvB,IAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,IAAO,IAAA,CAAK,WAAA,EAAY;AAC5C,IAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,WAAA,IAAe,IAAA,CAAK,iBAAiB,GAAG,CAAA;AAEpE,IAAA,MAAM,OAAA,GAAU,IAAIE,yBAAA,CAAiB;AAAA,MACnC,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,GAAA,EAAK,GAAA;AAAA,MACL,IAAA,EAAM,IAAA;AAAA,MACN,WAAA,EAAa,WAAA;AAAA,MACb,UAAU,OAAA,CAAQ;AAAA,KACnB,CAAA;AAED,IAAA,MAAM,IAAA,CAAK,MAAA,CAAQ,IAAA,CAAK,OAAO,CAAA;AAE/B,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,YAAA,CAAa,GAAA,EAAK,EAAE,SAAA,EAAW,OAAA,CAAQ,SAAA,IAAa,IAAA,EAAM,CAAA;AAEjF,IAAA,MAAA,CAAO,IAAA,CAAK,uBAAuB,EAAE,GAAA,EAAK,QAAQ,IAAA,CAAK,MAAA,EAAQ,aAAa,CAAA;AAE5E,IAAA,OAAO;AAAA,MACL,GAAA;AAAA,MACA,GAAA;AAAA,MACA,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb;AAAA,KACF;AAAA,EACF;AAAA,EAEA,MAAM,WAAA,CACJ,IAAA,EACA,QAAA,EACA,OAAA,GAAyB,EAAC,EACH;AACvB,IAAA,MAAM,GAAA,GAAM,QAAQ,GAAA,IAAO,CAAA,OAAA,EAAU,KAAK,GAAA,EAAK,IAAI,QAAQ,CAAA,CAAA;AAE3D,IAAA,OAAO,IAAA,CAAK,OAAO,IAAA,EAAM;AAAA,MACvB,GAAG,OAAA;AAAA,MACH,GAAA;AAAA,MACA,WAAA,EAAa,OAAA,CAAQ,WAAA,IAAe,IAAA,CAAK,oBAAoB,QAAQ;AAAA,KACtE,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,WAAA,CACJ,IAAA,EACA,QAAA,EACA,OAAA,GAAyB,EAAC,EACH;AACvB,IAAA,MAAM,GAAA,GAAM,QAAQ,GAAA,IAAO,CAAA,OAAA,EAAU,KAAK,GAAA,EAAK,IAAI,QAAQ,CAAA,CAAA;AAE3D,IAAA,OAAO,IAAA,CAAK,OAAO,IAAA,EAAM;AAAA,MACvB,GAAG,OAAA;AAAA,MACH,GAAA;AAAA,MACA,WAAA,EAAa,OAAA,CAAQ,WAAA,IAAe,IAAA,CAAK,oBAAoB,QAAQ;AAAA,KACtE,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,GAAA,EAA4B;AACvC,IAAA,IAAA,CAAK,iBAAA,EAAkB;AAEvB,IAAA,MAAM,OAAA,GAAU,IAAIC,4BAAA,CAAoB;AAAA,MACtC,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,GAAA,EAAK;AAAA,KACN,CAAA;AAED,IAAA,MAAM,IAAA,CAAK,MAAA,CAAQ,IAAA,CAAK,OAAO,CAAA;AAC/B,IAAA,MAAA,CAAO,KAAK,sBAAA,EAAwB,EAAE,KAAK,MAAA,EAAQ,IAAA,CAAK,QAAQ,CAAA;AAAA,EAClE;AAAA,EAEA,MAAM,YAAA,CAAa,GAAA,EAAa,OAAA,GAA4B,EAAC,EAAoB;AAC/E,IAAA,IAAA,CAAK,iBAAA,EAAkB;AAEvB,IAAA,MAAM,OAAA,GAAU,IAAIC,yBAAA,CAAiB;AAAA,MACnC,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,GAAA,EAAK;AAAA,KACN,CAAA;AAED,IAAA,OAAOC,+BAAA,CAAa,IAAA,CAAK,MAAA,EAAS,OAAA,EAAS;AAAA,MACzC,SAAA,EAAW,QAAQ,SAAA,IAAa;AAAA,KACjC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,aAAa,GAAA,EAA8B;AAC/C,IAAA,OAAO,CAAA,QAAA,EAAW,IAAA,CAAK,MAAM,CAAA,IAAA,EAAO,MAAA,CAAO,IAAI,YAAY,CAAA,IAAK,WAAW,CAAA,eAAA,EAAkB,GAAG,CAAA,CAAA;AAAA,EAClG;AAAA,EAEA,MAAM,SAAA,CAAU,MAAA,EAAiB,OAAA,GAAkB,GAAA,EAA6B;AAC9E,IAAA,IAAA,CAAK,iBAAA,EAAkB;AAEvB,IAAA,MAAM,OAAA,GAAU,IAAIC,6BAAA,CAAqB;AAAA,MACvC,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,KACV,CAAA;AAED,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,MAAA,CAAQ,KAAK,OAAO,CAAA;AAEhD,IAAA,OAAA,CAAQ,SAAS,QAAA,IAAY,EAAC,EAAG,GAAA,CAAI,CAAC,IAAA,MAAU;AAAA,MAC9C,GAAA,EAAK,KAAK,GAAA,IAAO,EAAA;AAAA,MACjB,cAAc,IAAA,CAAK,YAAA;AAAA,MACnB,MAAM,IAAA,CAAK;AAAA,KACb,CAAE,CAAA;AAAA,EACJ;AAAA,EAEQ,WAAA,GAAsB;AAC5B,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,IAAA,MAAM,MAAA,GAAS,KAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,SAAA,CAAU,CAAA,EAAG,EAAE,CAAA;AACzD,IAAA,OAAO,CAAA,QAAA,EAAW,SAAS,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AAAA,EACvC;AAAA,EAEQ,iBAAiB,GAAA,EAAqB;AAC5C,IAAA,MAAM,MAAM,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,IAAO,WAAA,EAAY;AAE9C,IAAA,MAAM,YAAA,GAAuC;AAAA,MAC3C,GAAA,EAAK,YAAA;AAAA,MACL,IAAA,EAAM,YAAA;AAAA,MACN,GAAA,EAAK,WAAA;AAAA,MACL,GAAA,EAAK,WAAA;AAAA,MACL,IAAA,EAAM,YAAA;AAAA,MACN,GAAA,EAAK,eAAA;AAAA,MACL,GAAA,EAAK,WAAA;AAAA,MACL,IAAA,EAAM,YAAA;AAAA,MACN,GAAA,EAAK,iBAAA;AAAA,MACL,GAAA,EAAK,iBAAA;AAAA,MACL,GAAA,EAAK,iBAAA;AAAA,MACL,IAAA,EAAM,kBAAA;AAAA,MACN,GAAA,EAAK;AAAA,KACP;AAEA,IAAA,OAAO,YAAA,CAAa,GAAA,IAAO,EAAE,CAAA,IAAK,0BAAA;AAAA,EACpC;AAAA,EAEQ,oBAAoB,QAAA,EAA0B;AACpD,IAAA,MAAM,MAAM,QAAA,CAAS,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,IAAO,WAAA,EAAY;AACnD,IAAA,MAAM,UAAA,GAAqC;AAAA,MACzC,GAAA,EAAK,YAAA;AAAA,MACL,IAAA,EAAM,YAAA;AAAA,MACN,GAAA,EAAK,WAAA;AAAA,MACL,GAAA,EAAK,WAAA;AAAA,MACL,IAAA,EAAM,YAAA;AAAA,MACN,GAAA,EAAK;AAAA,KACP;AACA,IAAA,OAAO,UAAA,CAAW,GAAA,IAAO,EAAE,CAAA,IAAK,YAAA;AAAA,EAClC;AAAA,EAEQ,oBAAoB,QAAA,EAA0B;AACpD,IAAA,MAAM,MAAM,QAAA,CAAS,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,IAAO,WAAA,EAAY;AACnD,IAAA,MAAM,UAAA,GAAqC;AAAA,MACzC,GAAA,EAAK,WAAA;AAAA,MACL,IAAA,EAAM,YAAA;AAAA,MACN,GAAA,EAAK,iBAAA;AAAA,MACL,GAAA,EAAK,iBAAA;AAAA,MACL,GAAA,EAAK,kBAAA;AAAA,MACL,GAAA,EAAK;AAAA,KACP;AACA,IAAA,OAAO,UAAA,CAAW,GAAA,IAAO,EAAE,CAAA,IAAK,WAAA;AAAA,EAClC;AACF,CAAA;AAEO,IAAM,SAAA,GAAY,IAAI,SAAA;AAEtB,IAAM,MAAA,GAAS;AAAA,EACpB,UAAA,EAAY,CAACN,OAAAA,KAAqB,SAAA,CAAU,WAAWA,OAAM,CAAA;AAAA,EAE7D,MAAM,CAAC,IAAA,EAAoC,YACzC,SAAA,CAAU,MAAA,CAAO,MAAM,OAAO,CAAA;AAAA,EAEhC,KAAA,EAAO,CAAC,IAAA,EAAoC,QAAA,EAAkB,YAC5D,SAAA,CAAU,WAAA,CAAY,IAAA,EAAM,QAAA,EAAU,OAAO,CAAA;AAAA,EAE/C,KAAA,EAAO,CAAC,IAAA,EAAoC,QAAA,EAAkB,YAC5D,SAAA,CAAU,WAAA,CAAY,IAAA,EAAM,QAAA,EAAU,OAAO,CAAA;AAAA,EAE/C,MAAA,EAAQ,CAAC,GAAA,KAAgB,SAAA,CAAU,OAAO,GAAG,CAAA;AAAA,EAC7C,cAAc,CAAC,GAAA,EAAa,YAA+B,SAAA,CAAU,YAAA,CAAa,KAAK,OAAO,CAAA;AAAA,EAC9F,YAAA,EAAc,CAAC,GAAA,KAAgB,SAAA,CAAU,aAAa,GAAG,CAAA;AAAA,EACzD,WAAW,CAAC,MAAA,EAAiB,YAAqB,SAAA,CAAU,SAAA,CAAU,QAAQ,OAAO;AACvF;AAEA,IAAO,cAAA,GAAQ","file":"index.js","sourcesContent":["import { z } from 'zod';\n\nexport const envSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n PORT: z.string().default('3000'),\n DATABASE_URL: z.string().optional(),\n REDIS_URL: z.string().default('redis://localhost:6379'),\n JWT_SECRET: z.string().min(32).optional(),\n JWT_EXPIRES_IN: z.string().default('7d'),\n JWT_REFRESH_SECRET: z.string().min(32).optional(),\n JWT_REFRESH_EXPIRES_IN: z.string().default('30d'),\n GOOGLE_CLIENT_ID: z.string().optional(),\n GOOGLE_CLIENT_SECRET: z.string().optional(),\n GOOGLE_REDIRECT_URI: z.string().optional(),\n SMTP_HOST: z.string().optional(),\n SMTP_PORT: z.string().default('587'),\n SMTP_USER: z.string().optional(),\n SMTP_PASS: z.string().optional(),\n SMTP_FROM: z.string().optional(),\n TWILIO_ACCOUNT_SID: z.string().optional(),\n TWILIO_AUTH_TOKEN: z.string().optional(),\n TWILIO_PHONE_NUMBER: z.string().optional(),\n SLACK_WEBHOOK_URL: z.string().optional(),\n RATE_LIMIT_WINDOW: z.string().default('1m'),\n RATE_LIMIT_LIMIT: z.string().default('100'),\n LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),\n AWS_REGION: z.string().default('us-east-1'),\n AWS_ACCESS_KEY_ID: z.string().optional(),\n AWS_SECRET_ACCESS_KEY: z.string().optional(),\n AWS_S3_BUCKET: z.string().optional(),\n AWS_ENDPOINT: z.string().optional(),\n});\n\nexport type EnvConfig = z.infer<typeof envSchema>;\n\nexport interface ConfigOptions {\n schema?: z.ZodSchema;\n envPath?: string;\n validate?: boolean;\n}\n\nclass ConfigManager {\n private config: EnvConfig | null = null;\n private schema: z.ZodSchema;\n private validate: boolean;\n\n constructor(options: ConfigOptions = {}) {\n this.schema = options.schema || envSchema;\n this.validate = options.validate ?? true;\n }\n\n load(): EnvConfig {\n if (this.config) return this.config;\n\n const env: Record<string, string | undefined> = {};\n \n for (const key of Object.keys(this.schema.shape)) {\n env[key] = process.env[key];\n }\n\n if (this.validate) {\n const result = this.schema.safeParse(env);\n if (!result.success) {\n const errors = result.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');\n throw new Error(`Config validation failed: ${errors}`);\n }\n this.config = result.data;\n } else {\n this.config = env as EnvConfig;\n }\n\n return this.config;\n }\n\n get<K extends keyof EnvConfig>(key: K): EnvConfig[K] {\n if (!this.config) this.load();\n return this.config![key];\n }\n\n int(key: keyof EnvConfig): number {\n const value = this.get(key);\n if (typeof value === 'string') return parseInt(value, 10);\n return Number(value);\n }\n\n bool(key: keyof EnvConfig): boolean {\n const value = this.get(key);\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') return value.toLowerCase() === 'true';\n return Boolean(value);\n }\n\n isProduction(): boolean {\n return this.get('NODE_ENV') === 'production';\n }\n\n isDevelopment(): boolean {\n return this.get('NODE_ENV') === 'development';\n }\n\n isTest(): boolean {\n return this.get('NODE_ENV') === 'test';\n }\n\n getAll(): EnvConfig {\n if (!this.config) this.load();\n return this.config!;\n }\n}\n\nconst globalConfig = new ConfigManager();\n\nexport const config = {\n load: () => globalConfig.load(),\n get: <K extends keyof EnvConfig>(key: K) => globalConfig.get(key),\n int: (key: keyof EnvConfig) => globalConfig.int(key),\n bool: (key: keyof EnvConfig) => globalConfig.bool(key),\n isProduction: () => globalConfig.isProduction(),\n isDevelopment: () => globalConfig.isDevelopment(),\n isTest: () => globalConfig.isTest(),\n getAll: () => globalConfig.getAll(),\n create: (options?: ConfigOptions) => new ConfigManager(options),\n};\n\nexport default config;\n","import pino, { Logger, LoggerOptions, Bindings } from 'pino';\nimport { config } from '../config';\n\nexport type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';\n\nexport interface LoggerConfig extends Partial<LoggerOptions> {\n level?: LogLevel;\n name?: string;\n prettyPrint?: boolean;\n}\n\nexport interface RequestLoggerOptions {\n logLevel?: LogLevel;\n autoLogging?: boolean;\n}\n\nclass LoggerManager {\n private loggers: Map<string, Logger> = new Map();\n private defaultLogger: Logger;\n\n constructor() {\n const level = (config.get('LOG_LEVEL') as LogLevel) || 'info';\n this.defaultLogger = pino({\n level,\n name: 'saas-backend-kit',\n formatters: {\n bindings: (bindings: Bindings) => ({\n ...bindings,\n service: 'saas-backend-kit',\n }),\n },\n });\n }\n\n createLogger(options: LoggerConfig = {}): Logger {\n const name = options.name || 'default';\n \n if (this.loggers.has(name)) {\n return this.loggers.get(name)!;\n }\n\n const level = options.level || (config.get('LOG_LEVEL') as LogLevel) || 'info';\n \n const logger = pino({\n level,\n name: options.name,\n ...options,\n });\n\n this.loggers.set(name, logger);\n return logger;\n }\n\n getLogger(name?: string): Logger {\n if (name) {\n return this.loggers.get(name) || this.defaultLogger;\n }\n return this.defaultLogger;\n }\n\n child(bindings: Bindings, options?: { name?: string }): Logger {\n const name = options?.name || 'child';\n const parent = options?.name ? this.getLogger(name) : this.defaultLogger;\n return parent.child(bindings);\n }\n}\n\nconst loggerManager = new LoggerManager();\n\nexport const logger = {\n info: (message: string, ...args: unknown[]) => loggerManager.getLogger().info(message, ...args),\n warn: (message: string, ...args: unknown[]) => loggerManager.getLogger().warn(message, ...args),\n error: (message: string, ...args: unknown[]) => loggerManager.getLogger().error(message, ...args),\n debug: (message: string, ...args: unknown[]) => loggerManager.getLogger().debug(message, ...args),\n trace: (message: string, ...args: unknown[]) => loggerManager.getLogger().trace(message, ...args),\n fatal: (message: string, ...args: unknown[]) => loggerManager.getLogger().fatal(message, ...args),\n child: (bindings: Bindings, options?: { name?: string }) => loggerManager.child(bindings, options),\n create: (options?: LoggerConfig) => loggerManager.createLogger(options),\n get: (name?: string) => loggerManager.getLogger(name),\n};\n\nexport function createRequestLogger(options: RequestLoggerOptions = {}) {\n const logLevel = options.logLevel || 'info';\n const logger = loggerManager.getLogger('http');\n\n return function requestLogger(\n req: { method: string; url: string; headers: Record<string, string | string[] | undefined> },\n res: { statusCode: number; statusMessage?: string },\n elapsed: number\n ) {\n const log = logger.child({\n method: req.method,\n url: req.url,\n status: res.statusCode,\n responseTime: elapsed,\n ip: req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || 'unknown',\n userAgent: req.headers['user-agent'],\n });\n\n if (res.statusCode >= 500) {\n log.error(`Request completed`);\n } else if (res.statusCode >= 400) {\n log.warn(`Request completed`);\n } else {\n log.info(`Request completed`);\n }\n };\n}\n\nexport default logger;\n","import { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport { config } from '../config';\nimport { logger } from '../logger';\n\nexport interface S3Config {\n region?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n bucket: string;\n endpoint?: string;\n forcePathStyle?: boolean;\n}\n\nexport interface UploadOptions {\n key?: string;\n contentType?: string;\n expiresIn?: number;\n metadata?: Record<string, string>;\n}\n\nexport interface UploadResult {\n key: string;\n url: string;\n bucket: string;\n contentType?: string;\n size?: number;\n}\n\nexport interface SignedUrlOptions {\n expiresIn?: number;\n}\n\nexport interface FileObject {\n key: string;\n lastModified?: Date;\n size?: number;\n contentType?: string;\n}\n\nclass S3Service {\n private client: S3Client | null = null;\n private bucket: string;\n private initialized: boolean = false;\n\n constructor() {\n this.bucket = '';\n }\n\n initialize(config: S3Config): void {\n this.client = new S3Client({\n region: config.region || 'us-east-1',\n credentials: config.accessKeyId && config.secretAccessKey\n ? {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n }\n : undefined,\n endpoint: config.endpoint,\n forcePathStyle: config.forcePathStyle || false,\n });\n\n this.bucket = config.bucket;\n this.initialized = true;\n logger.info('S3 service initialized', { bucket: this.bucket });\n }\n\n isInitialized(): boolean {\n return this.initialized;\n }\n\n private ensureInitialized(): void {\n if (!this.initialized) {\n const region = config.get('AWS_REGION') || 'us-east-1';\n const bucket = config.get('AWS_S3_BUCKET') || '';\n \n this.initialize({\n region,\n accessKeyId: config.get('AWS_ACCESS_KEY_ID'),\n secretAccessKey: config.get('AWS_SECRET_ACCESS_KEY'),\n bucket,\n endpoint: config.get('AWS_ENDPOINT'),\n });\n }\n }\n\n async upload(\n file: Buffer | Uint8Array | string,\n options: UploadOptions = {}\n ): Promise<UploadResult> {\n this.ensureInitialized();\n\n const key = options.key || this.generateKey();\n const contentType = options.contentType || this.guessContentType(key);\n\n const command = new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: file,\n ContentType: contentType,\n Metadata: options.metadata,\n });\n\n await this.client!.send(command);\n\n const url = await this.getSignedUrl(key, { expiresIn: options.expiresIn || 3600 });\n\n logger.info('File uploaded to S3', { key, bucket: this.bucket, contentType });\n\n return {\n key,\n url,\n bucket: this.bucket,\n contentType,\n };\n }\n\n async uploadImage(\n file: Buffer | Uint8Array | string,\n filename: string,\n options: UploadOptions = {}\n ): Promise<UploadResult> {\n const key = options.key || `images/${Date.now()}-${filename}`;\n \n return this.upload(file, {\n ...options,\n key,\n contentType: options.contentType || this.getImageContentType(filename),\n });\n }\n\n async uploadVideo(\n file: Buffer | Uint8Array | string,\n filename: string,\n options: UploadOptions = {}\n ): Promise<UploadResult> {\n const key = options.key || `videos/${Date.now()}-${filename}`;\n \n return this.upload(file, {\n ...options,\n key,\n contentType: options.contentType || this.getVideoContentType(filename),\n });\n }\n\n async delete(key: string): Promise<void> {\n this.ensureInitialized();\n\n const command = new DeleteObjectCommand({\n Bucket: this.bucket,\n Key: key,\n });\n\n await this.client!.send(command);\n logger.info('File deleted from S3', { key, bucket: this.bucket });\n }\n\n async getSignedUrl(key: string, options: SignedUrlOptions = {}): Promise<string> {\n this.ensureInitialized();\n\n const command = new GetObjectCommand({\n Bucket: this.bucket,\n Key: key,\n });\n\n return getSignedUrl(this.client!, command, {\n expiresIn: options.expiresIn || 3600,\n });\n }\n\n async getPublicUrl(key: string): Promise<string> {\n return `https://${this.bucket}.s3.${config.get('AWS_REGION') || 'us-east-1'}.amazonaws.com/${key}`;\n }\n\n async listFiles(prefix?: string, maxKeys: number = 1000): Promise<FileObject[]> {\n this.ensureInitialized();\n\n const command = new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: prefix,\n MaxKeys: maxKeys,\n });\n\n const response = await this.client!.send(command);\n \n return (response.Contents || []).map((item) => ({\n key: item.Key || '',\n lastModified: item.LastModified,\n size: item.Size,\n }));\n }\n\n private generateKey(): string {\n const timestamp = Date.now();\n const random = Math.random().toString(36).substring(2, 15);\n return `uploads/${timestamp}-${random}`;\n }\n\n private guessContentType(key: string): string {\n const ext = key.split('.').pop()?.toLowerCase();\n \n const contentTypes: Record<string, string> = {\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n mp4: 'video/mp4',\n webm: 'video/webm',\n mov: 'video/quicktime',\n avi: 'video/x-msvideo',\n pdf: 'application/pdf',\n json: 'application/json',\n txt: 'text/plain',\n };\n\n return contentTypes[ext || ''] || 'application/octet-stream';\n }\n\n private getImageContentType(filename: string): string {\n const ext = filename.split('.').pop()?.toLowerCase();\n const imageTypes: Record<string, string> = {\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n };\n return imageTypes[ext || ''] || 'image/jpeg';\n }\n\n private getVideoContentType(filename: string): string {\n const ext = filename.split('.').pop()?.toLowerCase();\n const videoTypes: Record<string, string> = {\n mp4: 'video/mp4',\n webm: 'video/webm',\n mov: 'video/quicktime',\n avi: 'video/x-msvideo',\n mkv: 'video/x-matroska',\n ogv: 'video/ogg',\n };\n return videoTypes[ext || ''] || 'video/mp4';\n }\n}\n\nexport const s3Service = new S3Service();\n\nexport const upload = {\n initialize: (config: S3Config) => s3Service.initialize(config),\n \n file: (file: Buffer | Uint8Array | string, options?: UploadOptions) => \n s3Service.upload(file, options),\n \n image: (file: Buffer | Uint8Array | string, filename: string, options?: UploadOptions) =>\n s3Service.uploadImage(file, filename, options),\n \n video: (file: Buffer | Uint8Array | string, filename: string, options?: UploadOptions) =>\n s3Service.uploadVideo(file, filename, options),\n \n delete: (key: string) => s3Service.delete(key),\n getSignedUrl: (key: string, options?: SignedUrlOptions) => s3Service.getSignedUrl(key, options),\n getPublicUrl: (key: string) => s3Service.getPublicUrl(key),\n listFiles: (prefix?: string, maxKeys?: number) => s3Service.listFiles(prefix, maxKeys),\n};\n\nexport default upload;\n"]}