UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

201 lines 6.07 kB
/** * @beignet/core/flags * * Provider-neutral feature flag primitives for Beignet applications. */ function createFlag(valueKind, key, options) { const defaultValue = options.default; if (!matchesValueKind(defaultValue, valueKind)) { throw new Error(`Flag "${key}" default value does not match "${valueKind}" flag value kind.`); } return { kind: "flag", key, valueKind: valueKind, defaultValue, description: options.description, metadata: options.metadata, }; } function defineBooleanFlag(key, options) { return createFlag("boolean", key, options); } function defineStringFlag(key, options) { return createFlag("string", key, options); } function defineNumberFlag(key, options) { return createFlag("number", key, options); } function defineObjectFlag(key, options) { return createFlag("object", key, options); } /** * Define one feature flag declaration. */ export const defineFlag = { boolean: defineBooleanFlag, string: defineStringFlag, number: defineNumberFlag, object: defineObjectFlag, }; /** * Define a typed registry of feature flags. */ export function defineFlags(registry) { return registry; } /** * Evaluate a flag through any `FlagsPort`. */ export function evaluateFlag(flags, flag, options) { return flags.evaluate(flag, options); } /** * Return detailed flag evaluation information through any `FlagsPort`. */ export function getFlagDetails(flags, flag, options) { return flags.details(flag, options); } /** * Create static flags backed by fixed values. */ export function createStaticFlags(options = {}) { return createFlagsFromStore(() => options.values ?? {}, options); } /** * Create mutable in-memory flags for tests, examples, and local development. */ export function createMemoryFlags(options = {}) { const values = new Map(Object.entries(options.values ?? {})); const port = createFlagsFromStore(() => Object.fromEntries(values), options); return { ...port, values, set(flag, value) { if (!matchesValueKind(value, flag.valueKind)) { throw new Error(`Flag "${flag.key}" value does not match "${flag.valueKind}" flag value kind.`); } values.set(flag.key, value); }, reset(flag) { if (flag) { values.delete(flag.key); return; } values.clear(); }, }; } function createFlagsFromStore(readValues, observers) { async function resolve(flag, options, source) { const startedAt = Date.now(); const defaultValue = options?.defaultValue ?? flag.defaultValue; const values = readValues(); const stored = values[flag.key]; const hasStored = Object.hasOwn(values, flag.key); const usedStoredValue = hasStored && matchesValueKind(stored, flag.valueKind); const value = usedStoredValue ? stored : defaultValue; const details = { key: flag.key, valueKind: flag.valueKind, value, defaultValue, reason: usedStoredValue ? "static" : "default", defaulted: !usedStoredValue, }; await observeEvaluation(observers.onEvaluation, { source, flag, context: options?.context, details, durationMs: Date.now() - startedAt, ...correlationFromContext(options?.context), }); if (options?.expose) { await flags.recordExposure(flag, { context: options.context, value: details.value, variant: details.variant, metadata: details.metadata, }); } return details; } const flags = { async evaluate(flag, options) { const details = await resolve(flag, options, "evaluate"); return details.value; }, async details(flag, options) { return resolve(flag, options, "details"); }, async recordExposure(flag, options) { await observeExposure(observers.onExposure, { flag, context: options?.context, value: options?.value, variant: options?.variant, metadata: options?.metadata, ...correlationFromContext(options?.context), }); }, async track(event, options) { await observeTrack(observers.onTrack, { event, context: options?.context, value: options?.value, metadata: options?.metadata, ...correlationFromContext(options?.context), }); }, }; return flags; } function matchesValueKind(value, kind) { switch (kind) { case "boolean": return typeof value === "boolean"; case "string": return typeof value === "string"; case "number": return typeof value === "number" && Number.isFinite(value); case "object": return typeof value === "object" && value !== null; } } function correlationFromContext(context) { return { requestId: context?.requestId, traceId: context?.traceId, spanId: context?.spanId, parentSpanId: context?.parentSpanId, traceparent: context?.traceparent, }; } async function observeEvaluation(observer, observation) { try { await observer?.(observation); } catch { // Observers are diagnostic only. } } async function observeExposure(observer, observation) { try { await observer?.(observation); } catch { // Observers are diagnostic only. } } async function observeTrack(observer, observation) { try { await observer?.(observation); } catch { // Observers are diagnostic only. } } //# sourceMappingURL=index.js.map