* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix * fix: track native source modules with root-anchored runtime ignores
60 lines
2.3 KiB
JavaScript
60 lines
2.3 KiB
JavaScript
import { PlatformError } from './errors.mjs';
|
|
|
|
const FLAG_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
|
|
|
|
function envKey(flag) {
|
|
return `VENDOO_FEATURE_${flag.replace(/[^a-z0-9]/gi, '_').toUpperCase()}`;
|
|
}
|
|
|
|
function parseBoolean(value, fallback) {
|
|
if (value === undefined || value === null || value === '') return fallback;
|
|
if (/^(1|true|yes|on)$/i.test(String(value))) return true;
|
|
if (/^(0|false|no|off)$/i.test(String(value))) return false;
|
|
throw new PlatformError(`Ungültiger Feature-Flag-Wert: ${value}`, { code: 'FEATURE_FLAG_VALUE_INVALID', status: 500 });
|
|
}
|
|
|
|
export class FeatureFlagRegistry {
|
|
#flags = new Map();
|
|
|
|
define(name, { defaultValue = false, owner = 'vendoo.system', description = '', mutable = false } = {}) {
|
|
const normalized = String(name || '');
|
|
if (!FLAG_PATTERN.test(normalized)) {
|
|
throw new PlatformError(`Ungültiger Feature-Flag: ${normalized}`, { code: 'FEATURE_FLAG_NAME_INVALID', status: 500 });
|
|
}
|
|
if (this.#flags.has(normalized)) {
|
|
throw new PlatformError(`Feature-Flag bereits definiert: ${normalized}`, { code: 'FEATURE_FLAG_DUPLICATE', status: 500 });
|
|
}
|
|
const configured = parseBoolean(process.env[envKey(normalized)], Boolean(defaultValue));
|
|
this.#flags.set(normalized, {
|
|
name: normalized,
|
|
value: configured,
|
|
defaultValue: Boolean(defaultValue),
|
|
owner: String(owner),
|
|
description: String(description),
|
|
mutable: Boolean(mutable),
|
|
source: process.env[envKey(normalized)] === undefined ? 'default' : 'environment',
|
|
});
|
|
return configured;
|
|
}
|
|
|
|
isEnabled(name) {
|
|
if (!this.#flags.has(name)) {
|
|
throw new PlatformError(`Unbekannter Feature-Flag: ${name}`, { code: 'FEATURE_FLAG_UNKNOWN', status: 500 });
|
|
}
|
|
return this.#flags.get(name).value;
|
|
}
|
|
|
|
set(name, value) {
|
|
const flag = this.#flags.get(name);
|
|
if (!flag) throw new PlatformError(`Unbekannter Feature-Flag: ${name}`, { code: 'FEATURE_FLAG_UNKNOWN', status: 404, expose: true });
|
|
if (!flag.mutable) throw new PlatformError(`Feature-Flag ist nicht zur Laufzeit änderbar: ${name}`, { code: 'FEATURE_FLAG_IMMUTABLE', status: 409, expose: true });
|
|
flag.value = Boolean(value);
|
|
flag.source = 'runtime';
|
|
return flag.value;
|
|
}
|
|
|
|
list() {
|
|
return [...this.#flags.values()].map(flag => ({ ...flag }));
|
|
}
|
|
}
|