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