feat(backend-config): create @bytelyst/backend-config package with shared Zod schema

- baseBackendConfigSchema: PORT, HOST, NODE_ENV, CORS_ORIGIN, SERVICE_NAME,
  DB_PROVIDER, COSMOS_*, JWT_SECRET, PLATFORM_JWKS_URL
- parseBackendConfig() helper for env parsing
- Products extend via baseBackendConfigSchema.extend({...})
- 8 tests passing
This commit is contained in:
saravanakumardb1 2026-03-20 07:51:22 -07:00
parent f5d64d58ce
commit 08661bbd97
5 changed files with 173 additions and 0 deletions

View File

@ -0,0 +1,30 @@
{
"name": "@bytelyst/backend-config",
"version": "0.1.0",
"description": "Shared Zod config schema base for Fastify product backends",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"clean": "rm -rf dist"
},
"dependencies": {
"zod": "^3.24.2"
},
"devDependencies": {
"typescript": "^5.7.3",
"vitest": "^3.0.5"
},
"files": [
"dist"
]
}

View File

@ -0,0 +1,82 @@
import { describe, it, expect } from 'vitest';
import { baseBackendConfigSchema, parseBackendConfig } from './index.js';
describe('baseBackendConfigSchema', () => {
it('parses minimal valid env', () => {
const config = baseBackendConfigSchema.parse({
JWT_SECRET: 'test-secret',
});
expect(config.PORT).toBe(3000);
expect(config.HOST).toBe('0.0.0.0');
expect(config.NODE_ENV).toBe('development');
expect(config.DB_PROVIDER).toBe('cosmos');
expect(config.COSMOS_DATABASE).toBe('lysnrai');
expect(config.JWT_SECRET).toBe('test-secret');
expect(config.PLATFORM_JWKS_URL).toBeUndefined();
});
it('applies overrides', () => {
const config = baseBackendConfigSchema.parse({
PORT: '4010',
NODE_ENV: 'production',
DB_PROVIDER: 'memory',
JWT_SECRET: 'prod-secret',
PLATFORM_JWKS_URL: 'https://example.com/.well-known/jwks.json',
});
expect(config.PORT).toBe(4010);
expect(config.NODE_ENV).toBe('production');
expect(config.DB_PROVIDER).toBe('memory');
expect(config.PLATFORM_JWKS_URL).toBe('https://example.com/.well-known/jwks.json');
});
it('rejects missing JWT_SECRET', () => {
expect(() => baseBackendConfigSchema.parse({})).toThrow();
});
it('rejects invalid NODE_ENV', () => {
expect(() => baseBackendConfigSchema.parse({ JWT_SECRET: 's', NODE_ENV: 'staging' })).toThrow();
});
it('rejects invalid DB_PROVIDER', () => {
expect(() =>
baseBackendConfigSchema.parse({ JWT_SECRET: 's', DB_PROVIDER: 'postgres' })
).toThrow();
});
});
describe('baseBackendConfigSchema.extend()', () => {
const extendedSchema = baseBackendConfigSchema.extend({
PLATFORM_SERVICE_URL: baseBackendConfigSchema.shape.HOST.default('http://localhost:4003'),
CUSTOM_FLAG: baseBackendConfigSchema.shape.NODE_ENV.optional(),
});
it('parses extended config with product-specific fields', () => {
const config = extendedSchema.parse({
JWT_SECRET: 'test-secret',
PORT: '4018',
SERVICE_NAME: 'actiontrail-backend',
});
expect(config.PORT).toBe(4018);
expect(config.SERVICE_NAME).toBe('actiontrail-backend');
expect(config.PLATFORM_SERVICE_URL).toBe('http://localhost:4003');
});
});
describe('parseBackendConfig', () => {
it('parses from explicit env object', () => {
const config = parseBackendConfig(baseBackendConfigSchema, {
JWT_SECRET: 'from-env',
PORT: '9999',
});
expect(config.JWT_SECRET).toBe('from-env');
expect(config.PORT).toBe(9999);
});
it('works with extended schemas', () => {
const schema = baseBackendConfigSchema.extend({
WEBHOOK_SECRET: baseBackendConfigSchema.shape.HOST.default('dev-webhook'),
});
const config = parseBackendConfig(schema, { JWT_SECRET: 'x' });
expect(config.WEBHOOK_SECRET).toBe('dev-webhook');
});
});

View File

@ -0,0 +1,39 @@
import { z } from 'zod';
/**
* Base Zod schema shared by all product backends.
*
* Products extend this with `.extend({...})` to add product-specific fields.
* The base covers: server, CORS, Cosmos DB, JWT auth, DB provider.
*/
export const baseBackendConfigSchema = 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().default('backend'),
DB_PROVIDER: z.enum(['cosmos', 'memory']).default('cosmos'),
COSMOS_ENDPOINT: z.string().default(''),
COSMOS_KEY: z.string().default(''),
COSMOS_DATABASE: z.string().default('lysnrai'),
JWT_SECRET: z.string().min(1, 'JWT_SECRET is required'),
PLATFORM_JWKS_URL: z.string().url().optional(),
});
export type BaseBackendConfig = z.infer<typeof baseBackendConfigSchema>;
/**
* Parse and validate backend config from process.env.
*
* @param schema Zod object schema (typically `baseBackendConfigSchema.extend({...})`)
* @param env environment object (defaults to process.env)
* @returns Validated, typed config
*/
export function parseBackendConfig<T extends z.ZodRawShape>(
schema: z.ZodObject<T>,
env: Record<string, string | undefined> = process.env as Record<string, string | undefined>
): z.infer<z.ZodObject<T>> {
return schema.parse(env);
}

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"declaration": true
},
"include": ["src"]
}

13
pnpm-lock.yaml generated
View File

@ -350,6 +350,19 @@ importers:
specifier: ^19.2.4
version: 19.2.4(react@19.2.4)
packages/backend-config:
dependencies:
zod:
specifier: ^3.24.2
version: 3.25.76
devDependencies:
typescript:
specifier: ^5.7.3
version: 5.9.3
vitest:
specifier: ^3.0.5
version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.31.1)(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
packages/blob:
dependencies:
'@bytelyst/storage':