Copy // Step 3: Typed environment variables
interface Env {
NODE_ENV: "development" | "production" | "test";
PORT: string;
DATABASE_URL: string;
JWT_SECRET?: string;
DEBUG?: string;
}
function loadEnv(): Env {
const required = ["NODE_ENV", "PORT", "DATABASE_URL"];
const missing = required.filter(k => !process.env[k]);
if (missing.length) {
throw new Error(`Missing environment variables: ${missing.join(", ")}`);
}
return process.env as unknown as Env;
}
// Safe fallback for lab
const env = {
NODE_ENV: (process.env.NODE_ENV ?? "development") as "development" | "production" | "test",
PORT: process.env.PORT ?? "3000",
DATABASE_URL: process.env.DATABASE_URL ?? "sqlite:/tmp/app.db",
DEBUG: process.env.DEBUG ?? "false",
};
console.log(`Environment: ${env.NODE_ENV}`);
console.log(`Port: ${env.PORT}`);
console.log(`Database: ${env.DATABASE_URL}`);
// Step 4: Typed process handlers
process.on("uncaughtException", (err: Error) => {
console.error("[FATAL]", err.message);
process.exit(1);
});
process.on("unhandledRejection", (reason: unknown) => {
console.error("[UNHANDLED]", reason);
});
// Graceful shutdown
const shutdown = (signal: string) => {
console.log(`\nReceived ${signal} — shutting down gracefully`);
// cleanup, close DB connections, etc.
process.exit(0);
};
process.on("SIGTERM", () => shutdown("SIGTERM"));
process.on("SIGINT", () => shutdown("SIGINT"));
// Step 5: Path utilities with types
import { resolve, relative, sep } from "path";
function findProjectRoot(startDir: string = process.cwd()): string {
const parts = startDir.split(sep);
for (let i = parts.length; i > 0; i--) {
const dir = parts.slice(0, i).join(sep);
if (existsSync(join(dir, "package.json"))) return dir;
}
return startDir;
}
function relativePath(from: string, to: string): string {
return relative(from, to);
}
console.log("CWD:", process.cwd());
console.log("Platform:", process.platform);
console.log("Node version:", process.version);
// Step 6: Streams with types
import { Readable, Transform, Writable, pipeline } from "stream";
import { promisify } from "util";
const pipelineAsync = promisify(pipeline);
function numberStream(start: number, end: number): Readable {
let current = start;
return new Readable({
objectMode: true,
read() {
if (current <= end) this.push(current++);
else this.push(null);
}
});
}
function doubleTransform(): Transform {
return new Transform({
objectMode: true,
transform(chunk: number, _enc, callback) {
this.push(chunk * 2);
callback();
}
});
}
// Step 7: Config manager with types
class TypedConfig<T extends Record<string, unknown>> {
private data: T;
constructor(defaults: T) { this.data = { ...defaults }; }
get<K extends keyof T>(key: K): T[K] { return this.data[key]; }
set<K extends keyof T>(key: K, val: T[K]): void { this.data[key] = val; }
all(): Readonly<T> { return Object.freeze({ ...this.data }); }
}
const appConfig = new TypedConfig({
name: "innoZverse",
version: "1.0.0",
debug: false,
maxRetries: 3,
timeout: 30_000,
});
console.log("App:", appConfig.get("name"));
appConfig.set("debug", true);
console.log("Debug:", appConfig.get("debug"));
// Step 8: Capstone — file processor
import { readdirSync, statSync } from "fs";
interface FileInfo {
name: string;
path: string;
size: number;
extension: string;
isDirectory: boolean;
}
function scanDirectory(dir: string, maxDepth: number = 2, depth: number = 0): FileInfo[] {
if (!existsSync(dir) || depth > maxDepth) return [];
return readdirSync(dir).flatMap(name => {
const fullPath = join(dir, name);
const stat = statSync(fullPath);
const info: FileInfo = {
name, path: fullPath,
size: stat.size,
extension: extname(name).toLowerCase(),
isDirectory: stat.isDirectory(),
};
if (stat.isDirectory()) return [info, ...scanDirectory(fullPath, maxDepth, depth + 1)];
return [info];
});
}
const files = scanDirectory("/tmp/ts-lab");
console.log("\nProject files:");
files.forEach(f => {
const indent = f.isDirectory ? "📁" : "📄";
const size = f.isDirectory ? "" : ` (${f.size}b)`;
console.log(` ${indent} ${f.name}${size}`);
});
const jsonFiles = files.filter(f => f.extension === ".json");
console.log(`\nJSON files: ${jsonFiles.length}`);