feat(config): add @bytelyst/config package
- Zod-based baseEnvSchema (PORT, HOST, NODE_ENV, CORS_ORIGIN, SERVICE_NAME, COSMOS_*) - loadConfig() with extension support for service-specific fields - loadProductIdentity() reads product.json or falls back to env vars - getProductId() convenience function - Replaces 5 duplicated product-config.ts files across LysnrAI - Peer dep: zod >=3.20.0
This commit is contained in:
parent
2e9dcf49a8
commit
65bf79203b
21
packages/config/package.json
Normal file
21
packages/config/package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@bytelyst/config",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": ["dist"],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": ">=3.20.0"
|
||||
}
|
||||
}
|
||||
21
packages/config/src/base-schema.ts
Normal file
21
packages/config/src/base-schema.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Base environment schema shared by all Fastify microservices.
|
||||
* Each service extends this with its own fields via loadConfig().
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
export const baseEnvSchema = z.object({
|
||||
PORT: z.coerce.number().default(3000),
|
||||
HOST: z.string().default("0.0.0.0"),
|
||||
NODE_ENV: z
|
||||
.enum(["development", "production", "test"])
|
||||
.default("development"),
|
||||
CORS_ORIGIN: z.string().optional(),
|
||||
SERVICE_NAME: z.string(),
|
||||
COSMOS_ENDPOINT: z.string().min(1, "COSMOS_ENDPOINT is required"),
|
||||
COSMOS_KEY: z.string().min(1, "COSMOS_KEY is required"),
|
||||
COSMOS_DATABASE: z.string().default("lysnrai"),
|
||||
});
|
||||
|
||||
export type BaseEnv = z.infer<typeof baseEnvSchema>;
|
||||
8
packages/config/src/index.ts
Normal file
8
packages/config/src/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export { baseEnvSchema, type BaseEnv } from "./base-schema.js";
|
||||
export { loadConfig } from "./loader.js";
|
||||
export {
|
||||
loadProductIdentity,
|
||||
getProductId,
|
||||
_resetProductIdentity,
|
||||
type ProductIdentity,
|
||||
} from "./product-identity.js";
|
||||
26
packages/config/src/loader.ts
Normal file
26
packages/config/src/loader.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Config loader — parses process.env against the base schema + any extensions.
|
||||
*/
|
||||
|
||||
import { z, type ZodRawShape } from "zod";
|
||||
import { baseEnvSchema } from "./base-schema.js";
|
||||
|
||||
/**
|
||||
* Load and validate environment configuration.
|
||||
*
|
||||
* @param extension - Additional Zod fields specific to this service
|
||||
* @returns Parsed and validated config object
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const config = loadConfig({
|
||||
* STRIPE_SECRET_KEY: z.string().min(1),
|
||||
* BILLING_INTERNAL_KEY: z.string().optional(),
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function loadConfig<T extends ZodRawShape>(extension?: T) {
|
||||
const schema = extension ? baseEnvSchema.extend(extension) : baseEnvSchema;
|
||||
return schema.parse(process.env) as z.infer<typeof baseEnvSchema> &
|
||||
(T extends ZodRawShape ? z.infer<z.ZodObject<T>> : Record<string, never>);
|
||||
}
|
||||
75
packages/config/src/product-identity.ts
Normal file
75
packages/config/src/product-identity.ts
Normal file
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Product identity — reads from a product.json file or falls back to env vars.
|
||||
* Eliminates the need for hardcoded product-config.ts files in every service.
|
||||
*/
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
export interface ProductIdentity {
|
||||
productId: string;
|
||||
displayName: string;
|
||||
licensePrefix: string;
|
||||
configDirName: string;
|
||||
envVarPrefix: string;
|
||||
bundleIdSuffix: string;
|
||||
packageName: string;
|
||||
}
|
||||
|
||||
let _cached: ProductIdentity | null = null;
|
||||
|
||||
/**
|
||||
* Load product identity from a JSON file or environment variables.
|
||||
*
|
||||
* @param jsonPath - Path to product.json (optional, tries common locations)
|
||||
* @returns Product identity object
|
||||
*/
|
||||
export function loadProductIdentity(jsonPath?: string): ProductIdentity {
|
||||
if (_cached) return _cached;
|
||||
|
||||
// Try loading from file
|
||||
const paths = jsonPath
|
||||
? [jsonPath]
|
||||
: [
|
||||
resolve("shared/product.json"),
|
||||
resolve("../shared/product.json"),
|
||||
resolve("../../shared/product.json"),
|
||||
];
|
||||
|
||||
for (const p of paths) {
|
||||
try {
|
||||
const raw = readFileSync(p, "utf-8");
|
||||
_cached = JSON.parse(raw) as ProductIdentity;
|
||||
return _cached;
|
||||
} catch {
|
||||
// Try next path
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to env vars / defaults
|
||||
_cached = {
|
||||
productId: process.env.PRODUCT_ID || "lysnrai",
|
||||
displayName: process.env.DISPLAY_NAME || "LysnrAI",
|
||||
licensePrefix: process.env.LICENSE_PREFIX || "LYSNR",
|
||||
configDirName: process.env.CONFIG_DIR_NAME || ".LysnrAI",
|
||||
envVarPrefix: process.env.ENV_VAR_PREFIX || "LYSNR",
|
||||
bundleIdSuffix: process.env.BUNDLE_ID_SUFFIX || "LysnrAI",
|
||||
packageName: process.env.PACKAGE_NAME || "lysnrai",
|
||||
};
|
||||
return _cached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: get just the product ID string.
|
||||
*/
|
||||
export function getProductId(): string {
|
||||
return loadProductIdentity().productId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cache (useful for testing).
|
||||
* @internal
|
||||
*/
|
||||
export function _resetProductIdentity(): void {
|
||||
_cached = null;
|
||||
}
|
||||
9
packages/config/tsconfig.json
Normal file
9
packages/config/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user