@burdenoff/be-sdk 2026.717.2 → 2026.718.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,12 +23,21 @@ var EVALUATE_QUERY = `
23
23
  }
24
24
  }
25
25
  `;
26
+ function readPositiveIntEnv(name, fallback) {
27
+ const raw = process.env[name];
28
+ if (!raw) return fallback;
29
+ const parsed = parseInt(raw, 10);
30
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
31
+ }
32
+ __name(readPositiveIntEnv, "readPositiveIntEnv");
26
33
  var FeatureFlagProvider = class {
27
34
  static {
28
35
  __name(this, "FeatureFlagProvider");
29
36
  }
30
37
  gatewayUrl;
31
38
  cacheTtlMs;
39
+ failureTtlMs;
40
+ requestTimeoutMs;
32
41
  serviceToken;
33
42
  serviceName;
34
43
  defaultActorType;
@@ -37,6 +46,8 @@ var FeatureFlagProvider = class {
37
46
  constructor(config) {
38
47
  this.gatewayUrl = config.gatewayUrl;
39
48
  this.cacheTtlMs = (config.cacheTtlSeconds ?? 60) * 1e3;
49
+ this.requestTimeoutMs = config.requestTimeoutMs ?? readPositiveIntEnv("FEATURE_FLAG_REQUEST_TIMEOUT_MS", 3e3);
50
+ this.failureTtlMs = (config.failureTtlSeconds ?? readPositiveIntEnv("FEATURE_FLAG_FAILURE_TTL_SECONDS", 15)) * 1e3;
40
51
  this.serviceToken = config.serviceToken;
41
52
  this.serviceName = config.serviceName ?? process.env["SERVICE_NAME"] ?? "burdenoff-be-sdk";
42
53
  this.defaultActorType = config.defaultActorType ?? "service";
@@ -211,6 +222,10 @@ var FeatureFlagProvider = class {
211
222
  try {
212
223
  const response = await fetch(this.gatewayUrl, {
213
224
  method: "POST",
225
+ // Bounded: an unreachable gateway must cost milliseconds-to-seconds,
226
+ // never the kernel's TCP connect timeout. On abort the catch below
227
+ // negative-caches and callers fall back to their defaults.
228
+ signal: AbortSignal.timeout(this.requestTimeoutMs),
214
229
  headers: {
215
230
  "Content-Type": "application/json",
216
231
  ...context.actorId && {
@@ -237,11 +252,11 @@ var FeatureFlagProvider = class {
237
252
  })
238
253
  });
239
254
  if (!response.ok) {
240
- return /* @__PURE__ */ new Map();
255
+ return this.rememberFailure(cacheKey);
241
256
  }
242
257
  const json = await response.json();
243
258
  if (json.errors || !json.data?.evaluateFeatureFlags?.evaluations) {
244
- return /* @__PURE__ */ new Map();
259
+ return this.rememberFailure(cacheKey);
245
260
  }
246
261
  const evaluations = /* @__PURE__ */ new Map();
247
262
  for (const evaluation of json.data.evaluateFeatureFlags.evaluations) {
@@ -253,10 +268,23 @@ var FeatureFlagProvider = class {
253
268
  });
254
269
  return evaluations;
255
270
  } catch {
256
- return /* @__PURE__ */ new Map();
271
+ return this.rememberFailure(cacheKey);
257
272
  }
258
273
  }
259
274
  /**
275
+ * Negative-cache a failed fetch: remember an empty evaluation set for
276
+ * `failureTtlMs` so a broken/unreachable gateway costs at most one request
277
+ * timeout per window instead of one per flag evaluation.
278
+ */
279
+ rememberFailure(cacheKey) {
280
+ const evaluations = /* @__PURE__ */ new Map();
281
+ this.cache.set(cacheKey, {
282
+ evaluations,
283
+ expiresAt: Date.now() + this.failureTtlMs
284
+ });
285
+ return evaluations;
286
+ }
287
+ /**
260
288
  * Build a cache key from the evaluation context.
261
289
  *
262
290
  * Safe against undefined/null context — callers like isEnabled() may
@@ -273,5 +301,5 @@ var FeatureFlagProvider = class {
273
301
  };
274
302
 
275
303
  export { FeatureFlagProvider, feature_flags_exports };
276
- //# sourceMappingURL=chunk-MNKU7I2G.js.map
277
- //# sourceMappingURL=chunk-MNKU7I2G.js.map
304
+ //# sourceMappingURL=chunk-WM5COTOD.js.map
305
+ //# sourceMappingURL=chunk-WM5COTOD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/feature-flags/index.ts","../src/feature-flags/provider.ts"],"names":["EVALUATE_QUERY","readPositiveIntEnv","name","fallback","raw","process","env","parsed","parseInt","Number","isFinite","FeatureFlagProvider","gatewayUrl","cacheTtlMs","failureTtlMs","requestTimeoutMs","serviceToken","serviceName","defaultActorType","cache","Map","config","cacheTtlSeconds","failureTtlSeconds","resolveBoolean","flagKey","defaultValue","context","evaluation","getEvaluation","value","reason","flagType","Boolean","variant","undefined","errorCode","resolveString","String","resolveNumber","resolveObject","isEnabled","result","workspaceId","actorType","actorId","evaluateAll","keys","evaluations","fetchEvaluations","key","set","invalidateCache","cacheKey","buildCacheKey","delete","clear","get","cached","expiresAt","Date","now","bearerToken","signServiceJwt","response","fetch","method","signal","AbortSignal","timeout","headers","Authorization","ContextManager","getCfAccessHeaders","body","JSON","stringify","query","variables","input","ok","rememberFailure","json","errors","data","evaluateFeatureFlags","ctx"],"mappings":"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","file":"chunk-WM5COTOD.js","sourcesContent":["/**\n * Feature Flags Module\n *\n * In-house feature flag evaluation for Burdenoff backend services.\n * Compatible with OpenFeature specification concepts.\n *\n * @module feature-flags\n * @category FeatureFlags\n *\n * @example\n * ```typescript\n * import { FeatureFlagProvider } from '@burdenoff/be-sdk/feature-flags';\n *\n * // Create provider (typically done once at service startup)\n * const provider = new FeatureFlagProvider({\n * gatewayUrl: process.env.GLOBAL_INT_GATEWAY_URL || 'http://global-int-gateway:4000/global/graphql',\n * cacheTtlSeconds: 60,\n * });\n *\n * // Check a boolean flag\n * const ctx = { actorId: 'user-123', actorType: 'user' as const, workspaceId: 'ws-456' };\n * const isEnabled = await provider.isEnabled('calendar.recurring-events', ctx);\n *\n * // Resolve with full details\n * const details = await provider.resolveBoolean('calendar.recurring-events', true, ctx);\n * console.log(details.value); // true\n * console.log(details.reason); // 'TARGETING_MATCH'\n * console.log(details.variant); // 'user-opted-in'\n * ```\n */\n\nexport { FeatureFlagProvider } from './provider.js';\nexport type {\n EvaluationContext,\n FeatureFlagProviderConfig,\n FlagEvaluation,\n FlagType,\n ResolutionDetails,\n} from './types.js';\n","/**\n * Burdenoff Feature Flags Server Provider\n *\n * Server-side feature flag provider for backend services.\n * Evaluates flags via the global-tenant-svc GraphQL API through the internal gateway.\n * Supports in-memory caching with configurable TTL.\n *\n * @module feature-flags/provider\n * @category FeatureFlags\n *\n * @example\n * ```typescript\n * import { FeatureFlagProvider } from '@burdenoff/be-sdk/feature-flags';\n *\n * const provider = new FeatureFlagProvider({\n * gatewayUrl: 'http://global-int-gateway:4000/global/graphql',\n * cacheTtlSeconds: 60,\n * });\n *\n * const isEnabled = await provider.resolveBoolean('calendar.recurring-events', false, {\n * actorId: 'user-123',\n * actorType: 'user',\n * workspaceId: 'ws-456',\n * });\n *\n * console.log(isEnabled.value); // true or false\n * console.log(isEnabled.reason); // 'TARGETING_MATCH', 'DEFAULT', etc.\n * ```\n */\n\nimport { ContextManager } from '../context/index.js';\nimport { signServiceJwt } from '../gateway/service-jwt.js';\nimport type { EvaluationContext, FeatureFlagProviderConfig, FlagEvaluation, ResolutionDetails } from './types.js';\n\ninterface CacheEntry {\n evaluations: Map<string, FlagEvaluation>;\n expiresAt: number;\n}\n\nconst EVALUATE_QUERY = `\n query EvaluateFeatureFlags($input: EvaluateFeatureFlagsInput!) {\n evaluateFeatureFlags(input: $input) {\n evaluations {\n key\n value\n variant\n reason\n flagType\n errorCode\n }\n }\n }\n`;\n\n/**\n * Burdenoff Feature Flag Server Provider\n *\n * Evaluates feature flags by querying the global-tenant-svc through\n * the internal gateway. Results are cached in-memory per workspace+actor\n * combination with a configurable TTL.\n */\nfunction readPositiveIntEnv(name: string, fallback: number): number {\n const raw = process.env[name];\n if (!raw) return fallback;\n const parsed = parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\nexport class FeatureFlagProvider {\n private readonly gatewayUrl: string;\n private readonly cacheTtlMs: number;\n private readonly failureTtlMs: number;\n private readonly requestTimeoutMs: number;\n private readonly serviceToken?: string;\n private readonly serviceName: string;\n private readonly defaultActorType: string;\n\n /** In-memory cache keyed by `${workspaceId}:${actorId}` */\n private readonly cache = new Map<string, CacheEntry>();\n\n constructor(config: FeatureFlagProviderConfig) {\n this.gatewayUrl = config.gatewayUrl;\n this.cacheTtlMs = (config.cacheTtlSeconds ?? 60) * 1000;\n // Hard per-request ceiling. Without it an unreachable gateway (NSG\n // silent-drop on the cross-environment path) left the fetch hanging for\n // the kernel's ~130s TCP connect timeout — stalling every flag-gated\n // request, and killing services that await a flag at BOOT (their ACA\n // startup probe expired → ActivationFailed).\n this.requestTimeoutMs = config.requestTimeoutMs ?? readPositiveIntEnv('FEATURE_FLAG_REQUEST_TIMEOUT_MS', 3000);\n // Negative cache so a broken gateway costs at most one timeout per\n // window instead of one per evaluation.\n this.failureTtlMs = (config.failureTtlSeconds ?? readPositiveIntEnv('FEATURE_FLAG_FAILURE_TTL_SECONDS', 15)) * 1000;\n this.serviceToken = config.serviceToken;\n this.serviceName = config.serviceName ?? process.env['SERVICE_NAME'] ?? 'burdenoff-be-sdk';\n this.defaultActorType = config.defaultActorType ?? 'service';\n }\n\n // =========================================================================\n // Public Resolution Methods (OpenFeature-compatible interface)\n // =========================================================================\n\n /**\n * Resolve a boolean feature flag\n *\n * @param flagKey - The flag key (e.g. \"calendar.recurring-events\")\n * @param defaultValue - Default value if resolution fails\n * @param context - Evaluation context with actor and workspace info\n * @returns Resolution details with the resolved boolean value\n */\n async resolveBoolean(\n flagKey: string,\n defaultValue: boolean,\n context: EvaluationContext\n ): Promise<ResolutionDetails<boolean>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'BOOLEAN',\n };\n }\n\n return {\n value: Boolean(evaluation.value),\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n /**\n * Resolve a string feature flag\n */\n async resolveString(\n flagKey: string,\n defaultValue: string,\n context: EvaluationContext\n ): Promise<ResolutionDetails<string>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'STRING',\n };\n }\n\n return {\n value: String(evaluation.value),\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n /**\n * Resolve a number feature flag\n */\n async resolveNumber(\n flagKey: string,\n defaultValue: number,\n context: EvaluationContext\n ): Promise<ResolutionDetails<number>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'NUMBER',\n };\n }\n\n return {\n value: Number(evaluation.value),\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n /**\n * Resolve an object (JSON) feature flag\n */\n async resolveObject<T extends Record<string, unknown>>(\n flagKey: string,\n defaultValue: T,\n context: EvaluationContext\n ): Promise<ResolutionDetails<T>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'JSON',\n };\n }\n\n return {\n value: evaluation.value as T,\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n // =========================================================================\n // Convenience Methods\n // =========================================================================\n\n /**\n * Quick boolean check — returns just the value.\n * Defaults to `true` (default-allow philosophy) if the flag is not found.\n *\n * @example\n * ```typescript\n * if (await provider.isEnabled('calendar.recurring-events', ctx)) {\n * // Feature is enabled\n * }\n * ```\n */\n async isEnabled(flagKey: string, context?: EvaluationContext): Promise<boolean> {\n const result = await this.resolveBoolean(\n flagKey,\n true,\n context ?? { workspaceId: 'system', actorType: 'service', actorId: 'boot' }\n );\n return result.value;\n }\n\n /**\n * Bulk evaluate multiple flags at once.\n * Returns a map of key -> ResolutionDetails.\n */\n async evaluateAll(context: EvaluationContext, keys?: string[]): Promise<Map<string, ResolutionDetails<unknown>>> {\n const evaluations = await this.fetchEvaluations(context, keys);\n const result = new Map<string, ResolutionDetails<unknown>>();\n\n for (const [key, evaluation] of evaluations) {\n result.set(key, {\n value: evaluation.value,\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n });\n }\n\n return result;\n }\n\n /**\n * Invalidate the cache for a specific workspace+actor combination,\n * or all cache entries if no context is provided.\n */\n invalidateCache(context?: EvaluationContext): void {\n if (context) {\n const cacheKey = this.buildCacheKey(context);\n this.cache.delete(cacheKey);\n } else {\n this.cache.clear();\n }\n }\n\n // =========================================================================\n // Private Methods\n // =========================================================================\n\n /**\n * Get a single flag evaluation, fetching from cache or API\n */\n private async getEvaluation(flagKey: string, context: EvaluationContext | undefined): Promise<FlagEvaluation | null> {\n try {\n const evaluations = await this.fetchEvaluations(context);\n return evaluations.get(flagKey) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * Fetch flag evaluations — uses cache if available, otherwise calls the API\n */\n private async fetchEvaluations(\n context: EvaluationContext | undefined,\n keys?: string[]\n ): Promise<Map<string, FlagEvaluation>> {\n const cacheKey = this.buildCacheKey(context);\n const cached = this.cache.get(cacheKey);\n\n if (cached && cached.expiresAt > Date.now()) {\n return cached.evaluations;\n }\n\n // Without a workspace context there's nothing the backend can evaluate\n // against — fall through to caller defaults instead of hitting the API.\n if (!context?.workspaceId) {\n return new Map();\n }\n\n // Cross-layer flag evaluation transits the global PUBLIC gateway, which\n // strips raw actor headers unless the request carries a valid JWT. Use an\n // explicit serviceToken if provided, otherwise mint a short-lived `service`\n // actor JWT (no-op when SERVICE_JWT_SECRET is unset → falls back to header\n // context for same-env/internal-gateway evaluation).\n const bearerToken = this.serviceToken ?? (await signServiceJwt(this.serviceName)) ?? undefined;\n\n // Fetch from API\n try {\n const response = await fetch(this.gatewayUrl, {\n method: 'POST',\n // Bounded: an unreachable gateway must cost milliseconds-to-seconds,\n // never the kernel's TCP connect timeout. On abort the catch below\n // negative-caches and callers fall back to their defaults.\n signal: AbortSignal.timeout(this.requestTimeoutMs),\n headers: {\n 'Content-Type': 'application/json',\n ...(context.actorId && { 'x-actor-id': context.actorId }),\n 'x-actor-type': context.actorType ?? this.defaultActorType,\n 'x-workspace-id': context.workspaceId,\n ...(bearerToken && { Authorization: `Bearer ${bearerToken}` }),\n // Re-present the inbound CF Access service token when the flag check\n // runs against a CF-Access-gated public gateway (alpha). No-op when\n // request context carries no CF creds (local dev, production).\n ...ContextManager.getCfAccessHeaders(),\n },\n body: JSON.stringify({\n query: EVALUATE_QUERY,\n variables: {\n input: {\n workspaceId: context.workspaceId,\n keys: keys ?? null,\n },\n },\n }),\n });\n\n if (!response.ok) {\n // Return empty map on error — default-allow handled by callers\n return this.rememberFailure(cacheKey);\n }\n\n const json = (await response.json()) as {\n data?: {\n evaluateFeatureFlags?: {\n evaluations?: FlagEvaluation[];\n };\n };\n errors?: Array<{ message: string }>;\n };\n\n if (json.errors || !json.data?.evaluateFeatureFlags?.evaluations) {\n return this.rememberFailure(cacheKey);\n }\n\n const evaluations = new Map<string, FlagEvaluation>();\n for (const evaluation of json.data.evaluateFeatureFlags.evaluations) {\n evaluations.set(evaluation.key, evaluation);\n }\n\n // Cache the results\n this.cache.set(cacheKey, {\n evaluations,\n expiresAt: Date.now() + this.cacheTtlMs,\n });\n\n return evaluations;\n } catch {\n // On network error/timeout, return empty map (default-allow handled by\n // callers) and negative-cache so the next evaluations in the window\n // skip the doomed request entirely.\n return this.rememberFailure(cacheKey);\n }\n }\n\n /**\n * Negative-cache a failed fetch: remember an empty evaluation set for\n * `failureTtlMs` so a broken/unreachable gateway costs at most one request\n * timeout per window instead of one per flag evaluation.\n */\n private rememberFailure(cacheKey: string): Map<string, FlagEvaluation> {\n const evaluations = new Map<string, FlagEvaluation>();\n this.cache.set(cacheKey, {\n evaluations,\n expiresAt: Date.now() + this.failureTtlMs,\n });\n return evaluations;\n }\n\n /**\n * Build a cache key from the evaluation context.\n *\n * Safe against undefined/null context — callers like isEnabled() may\n * pass no context during service boot (e.g. checking a default-allow\n * feature flag at startup before the first request builds a real\n * actor context). Without the guard, isEnabled(flag) used to throw\n * \"undefined is not an object (evaluating 'context.workspaceId')\"\n * and crash the svc at startServer().\n */\n private buildCacheKey(context: EvaluationContext | undefined): string {\n const ctx = context ?? ({} as EvaluationContext);\n return `${ctx.workspaceId ?? 'system'}:${ctx.actorId ?? 'anonymous'}:${ctx.actorType ?? 'user'}`;\n }\n}\n"]}
@@ -1 +1 @@
1
- export { E as EvaluationContext, F as FeatureFlagProvider, a as FeatureFlagProviderConfig, b as FlagEvaluation, c as FlagType, R as ResolutionDetails } from '../index-BdPnlJ8_.js';
1
+ export { E as EvaluationContext, F as FeatureFlagProvider, a as FeatureFlagProviderConfig, b as FlagEvaluation, c as FlagType, R as ResolutionDetails } from '../index-D0tR4rjy.js';
@@ -1,4 +1,4 @@
1
- export { FeatureFlagProvider } from '../chunk-MNKU7I2G.js';
1
+ export { FeatureFlagProvider } from '../chunk-WM5COTOD.js';
2
2
  import '../chunk-VFCMR5GX.js';
3
3
  import '../chunk-RZOFS2BQ.js';
4
4
  import '../chunk-XE4OXN2W.js';
@@ -25,11 +25,15 @@ interface FeatureFlagProviderConfig {
25
25
  serviceToken?: string;
26
26
  serviceName?: string;
27
27
  defaultActorType?: 'user' | 'app' | 'service' | 'super_admin';
28
+ requestTimeoutMs?: number;
29
+ failureTtlSeconds?: number;
28
30
  }
29
31
 
30
32
  declare class FeatureFlagProvider {
31
33
  private readonly gatewayUrl;
32
34
  private readonly cacheTtlMs;
35
+ private readonly failureTtlMs;
36
+ private readonly requestTimeoutMs;
33
37
  private readonly serviceToken?;
34
38
  private readonly serviceName;
35
39
  private readonly defaultActorType;
@@ -44,6 +48,7 @@ declare class FeatureFlagProvider {
44
48
  invalidateCache(context?: EvaluationContext): void;
45
49
  private getEvaluation;
46
50
  private fetchEvaluations;
51
+ private rememberFailure;
47
52
  private buildCacheKey;
48
53
  }
49
54
 
package/dist/index.d.ts CHANGED
@@ -34,7 +34,7 @@ export { a as Instrumentation } from './index-CXPvdXhS.js';
34
34
  export { i as Layer } from './index-BCUkR8sC.js';
35
35
  export { i as Crypto } from './index-e_18DzAg.js';
36
36
  export { i as Gateway } from './index-Dy6neH2G.js';
37
- export { i as FeatureFlags } from './index-BdPnlJ8_.js';
37
+ export { i as FeatureFlags } from './index-D0tR4rjy.js';
38
38
  export { i as Captcha, R as RecaptchaVerifyOptions, a as RecaptchaVerifyResult, b as isCaptchaEnabled, v as verifyCaptcha, c as verifyCaptchaIfEnabled } from './index-B70b5mYS.js';
39
39
  export { i as Platform } from './index-lcW5eSTp.js';
40
40
  export { C as CircuitBreakerConfig, h as DEFAULT_CIRCUIT_BREAKER_CONFIG, i as DEFAULT_GATEWAY_RETRY_CONFIG, D as DEFAULT_GATEWAY_ROUTING, b as DEFAULT_LAYER_RESOURCES, c as DEFAULT_LAYER_STREAMS, f as GatewayRequestHeaders, g as GatewayRetryConfig, G as GatewayRoutingConfig, L as LayerConfig, a as LayerFullConfig, d as LayerGatewayConfig, e as LayerResourceUrls, S as ServiceLayer } from './layer-GNAVXBMf.js';
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { TenantNotificationsModule, NotificationsModule } from './chunk-ZMCD5XAK
15
15
  export { tenant_notifications_exports as Notifications, NotificationsModule } from './chunk-ZMCD5XAK.js';
16
16
  export { observability_exports as Observability } from './chunk-5LXDPGDH.js';
17
17
  export { export_exports as Export } from './chunk-PQJ6MN4U.js';
18
- export { feature_flags_exports as FeatureFlags } from './chunk-MNKU7I2G.js';
18
+ export { feature_flags_exports as FeatureFlags } from './chunk-WM5COTOD.js';
19
19
  import { GraphModule } from './chunk-V4QNOY4E.js';
20
20
  export { graph_exports as Graph, GraphModule } from './chunk-V4QNOY4E.js';
21
21
  import './chunk-NJ5GTICM.js';
@@ -38,10 +38,10 @@ export { ContextManager } from './chunk-RZOFS2BQ.js';
38
38
  import { AuthModule } from './chunk-RXMOV3YE.js';
39
39
  export { AuthModule } from './chunk-RXMOV3YE.js';
40
40
  export { utils_exports as Utils } from './chunk-B7I4TABG.js';
41
- export { captcha_exports as Captcha, isCaptchaEnabled, verifyCaptcha, verifyCaptchaIfEnabled } from './chunk-YMZEUTD2.js';
42
41
  export { cache_exports as Cache } from './chunk-I3ZYYDNN.js';
43
42
  import { disposeAll } from './chunk-VBXPFYZ5.js';
44
43
  export { lifecycle_exports as Lifecycle, disposeAll, getRegisteredDisposableCount, registerDisposable, unregisterDisposable } from './chunk-VBXPFYZ5.js';
44
+ export { captcha_exports as Captcha, isCaptchaEnabled, verifyCaptcha, verifyCaptchaIfEnabled } from './chunk-YMZEUTD2.js';
45
45
  export { createInternalClient, createInternalClientForTenant, createInternalClientForWorkspaces } from './chunk-VI4VSNTI.js';
46
46
  export { crypto_exports as Crypto } from './chunk-SBS4S2PJ.js';
47
47
  import { DatabaseModule } from './chunk-A6DGEZJ4.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/be-sdk",
3
- "version": "2026.717.2",
3
+ "version": "2026.718.1",
4
4
  "description": "Backend SDK for Burdenoff products providing unified access to notifications, RBAC, activity tracking, usage monitoring, and more",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/feature-flags/index.ts","../src/feature-flags/provider.ts"],"names":["EVALUATE_QUERY","FeatureFlagProvider","gatewayUrl","cacheTtlMs","serviceToken","serviceName","defaultActorType","cache","Map","config","cacheTtlSeconds","process","env","resolveBoolean","flagKey","defaultValue","context","evaluation","getEvaluation","value","reason","flagType","Boolean","variant","undefined","errorCode","resolveString","String","resolveNumber","Number","resolveObject","isEnabled","result","workspaceId","actorType","actorId","evaluateAll","keys","evaluations","fetchEvaluations","key","set","invalidateCache","cacheKey","buildCacheKey","delete","clear","get","cached","expiresAt","Date","now","bearerToken","signServiceJwt","response","fetch","method","headers","Authorization","ContextManager","getCfAccessHeaders","body","JSON","stringify","query","variables","input","ok","json","errors","data","evaluateFeatureFlags","ctx"],"mappings":"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","file":"chunk-MNKU7I2G.js","sourcesContent":["/**\n * Feature Flags Module\n *\n * In-house feature flag evaluation for Burdenoff backend services.\n * Compatible with OpenFeature specification concepts.\n *\n * @module feature-flags\n * @category FeatureFlags\n *\n * @example\n * ```typescript\n * import { FeatureFlagProvider } from '@burdenoff/be-sdk/feature-flags';\n *\n * // Create provider (typically done once at service startup)\n * const provider = new FeatureFlagProvider({\n * gatewayUrl: process.env.GLOBAL_INT_GATEWAY_URL || 'http://global-int-gateway:4000/global/graphql',\n * cacheTtlSeconds: 60,\n * });\n *\n * // Check a boolean flag\n * const ctx = { actorId: 'user-123', actorType: 'user' as const, workspaceId: 'ws-456' };\n * const isEnabled = await provider.isEnabled('calendar.recurring-events', ctx);\n *\n * // Resolve with full details\n * const details = await provider.resolveBoolean('calendar.recurring-events', true, ctx);\n * console.log(details.value); // true\n * console.log(details.reason); // 'TARGETING_MATCH'\n * console.log(details.variant); // 'user-opted-in'\n * ```\n */\n\nexport { FeatureFlagProvider } from './provider.js';\nexport type {\n EvaluationContext,\n FeatureFlagProviderConfig,\n FlagEvaluation,\n FlagType,\n ResolutionDetails,\n} from './types.js';\n","/**\n * Burdenoff Feature Flags Server Provider\n *\n * Server-side feature flag provider for backend services.\n * Evaluates flags via the global-tenant-svc GraphQL API through the internal gateway.\n * Supports in-memory caching with configurable TTL.\n *\n * @module feature-flags/provider\n * @category FeatureFlags\n *\n * @example\n * ```typescript\n * import { FeatureFlagProvider } from '@burdenoff/be-sdk/feature-flags';\n *\n * const provider = new FeatureFlagProvider({\n * gatewayUrl: 'http://global-int-gateway:4000/global/graphql',\n * cacheTtlSeconds: 60,\n * });\n *\n * const isEnabled = await provider.resolveBoolean('calendar.recurring-events', false, {\n * actorId: 'user-123',\n * actorType: 'user',\n * workspaceId: 'ws-456',\n * });\n *\n * console.log(isEnabled.value); // true or false\n * console.log(isEnabled.reason); // 'TARGETING_MATCH', 'DEFAULT', etc.\n * ```\n */\n\nimport { ContextManager } from '../context/index.js';\nimport { signServiceJwt } from '../gateway/service-jwt.js';\nimport type { EvaluationContext, FeatureFlagProviderConfig, FlagEvaluation, ResolutionDetails } from './types.js';\n\ninterface CacheEntry {\n evaluations: Map<string, FlagEvaluation>;\n expiresAt: number;\n}\n\nconst EVALUATE_QUERY = `\n query EvaluateFeatureFlags($input: EvaluateFeatureFlagsInput!) {\n evaluateFeatureFlags(input: $input) {\n evaluations {\n key\n value\n variant\n reason\n flagType\n errorCode\n }\n }\n }\n`;\n\n/**\n * Burdenoff Feature Flag Server Provider\n *\n * Evaluates feature flags by querying the global-tenant-svc through\n * the internal gateway. Results are cached in-memory per workspace+actor\n * combination with a configurable TTL.\n */\nexport class FeatureFlagProvider {\n private readonly gatewayUrl: string;\n private readonly cacheTtlMs: number;\n private readonly serviceToken?: string;\n private readonly serviceName: string;\n private readonly defaultActorType: string;\n\n /** In-memory cache keyed by `${workspaceId}:${actorId}` */\n private readonly cache = new Map<string, CacheEntry>();\n\n constructor(config: FeatureFlagProviderConfig) {\n this.gatewayUrl = config.gatewayUrl;\n this.cacheTtlMs = (config.cacheTtlSeconds ?? 60) * 1000;\n this.serviceToken = config.serviceToken;\n this.serviceName = config.serviceName ?? process.env['SERVICE_NAME'] ?? 'burdenoff-be-sdk';\n this.defaultActorType = config.defaultActorType ?? 'service';\n }\n\n // =========================================================================\n // Public Resolution Methods (OpenFeature-compatible interface)\n // =========================================================================\n\n /**\n * Resolve a boolean feature flag\n *\n * @param flagKey - The flag key (e.g. \"calendar.recurring-events\")\n * @param defaultValue - Default value if resolution fails\n * @param context - Evaluation context with actor and workspace info\n * @returns Resolution details with the resolved boolean value\n */\n async resolveBoolean(\n flagKey: string,\n defaultValue: boolean,\n context: EvaluationContext\n ): Promise<ResolutionDetails<boolean>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'BOOLEAN',\n };\n }\n\n return {\n value: Boolean(evaluation.value),\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n /**\n * Resolve a string feature flag\n */\n async resolveString(\n flagKey: string,\n defaultValue: string,\n context: EvaluationContext\n ): Promise<ResolutionDetails<string>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'STRING',\n };\n }\n\n return {\n value: String(evaluation.value),\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n /**\n * Resolve a number feature flag\n */\n async resolveNumber(\n flagKey: string,\n defaultValue: number,\n context: EvaluationContext\n ): Promise<ResolutionDetails<number>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'NUMBER',\n };\n }\n\n return {\n value: Number(evaluation.value),\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n /**\n * Resolve an object (JSON) feature flag\n */\n async resolveObject<T extends Record<string, unknown>>(\n flagKey: string,\n defaultValue: T,\n context: EvaluationContext\n ): Promise<ResolutionDetails<T>> {\n const evaluation = await this.getEvaluation(flagKey, context);\n if (!evaluation) {\n return {\n value: defaultValue,\n reason: 'DEFAULT',\n flagType: 'JSON',\n };\n }\n\n return {\n value: evaluation.value as T,\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n };\n }\n\n // =========================================================================\n // Convenience Methods\n // =========================================================================\n\n /**\n * Quick boolean check — returns just the value.\n * Defaults to `true` (default-allow philosophy) if the flag is not found.\n *\n * @example\n * ```typescript\n * if (await provider.isEnabled('calendar.recurring-events', ctx)) {\n * // Feature is enabled\n * }\n * ```\n */\n async isEnabled(flagKey: string, context?: EvaluationContext): Promise<boolean> {\n const result = await this.resolveBoolean(\n flagKey,\n true,\n context ?? { workspaceId: 'system', actorType: 'service', actorId: 'boot' }\n );\n return result.value;\n }\n\n /**\n * Bulk evaluate multiple flags at once.\n * Returns a map of key -> ResolutionDetails.\n */\n async evaluateAll(context: EvaluationContext, keys?: string[]): Promise<Map<string, ResolutionDetails<unknown>>> {\n const evaluations = await this.fetchEvaluations(context, keys);\n const result = new Map<string, ResolutionDetails<unknown>>();\n\n for (const [key, evaluation] of evaluations) {\n result.set(key, {\n value: evaluation.value,\n variant: evaluation.variant ?? undefined,\n reason: evaluation.reason,\n flagType: evaluation.flagType,\n errorCode: evaluation.errorCode ?? undefined,\n });\n }\n\n return result;\n }\n\n /**\n * Invalidate the cache for a specific workspace+actor combination,\n * or all cache entries if no context is provided.\n */\n invalidateCache(context?: EvaluationContext): void {\n if (context) {\n const cacheKey = this.buildCacheKey(context);\n this.cache.delete(cacheKey);\n } else {\n this.cache.clear();\n }\n }\n\n // =========================================================================\n // Private Methods\n // =========================================================================\n\n /**\n * Get a single flag evaluation, fetching from cache or API\n */\n private async getEvaluation(flagKey: string, context: EvaluationContext | undefined): Promise<FlagEvaluation | null> {\n try {\n const evaluations = await this.fetchEvaluations(context);\n return evaluations.get(flagKey) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * Fetch flag evaluations — uses cache if available, otherwise calls the API\n */\n private async fetchEvaluations(\n context: EvaluationContext | undefined,\n keys?: string[]\n ): Promise<Map<string, FlagEvaluation>> {\n const cacheKey = this.buildCacheKey(context);\n const cached = this.cache.get(cacheKey);\n\n if (cached && cached.expiresAt > Date.now()) {\n return cached.evaluations;\n }\n\n // Without a workspace context there's nothing the backend can evaluate\n // against — fall through to caller defaults instead of hitting the API.\n if (!context?.workspaceId) {\n return new Map();\n }\n\n // Cross-layer flag evaluation transits the global PUBLIC gateway, which\n // strips raw actor headers unless the request carries a valid JWT. Use an\n // explicit serviceToken if provided, otherwise mint a short-lived `service`\n // actor JWT (no-op when SERVICE_JWT_SECRET is unset → falls back to header\n // context for same-env/internal-gateway evaluation).\n const bearerToken = this.serviceToken ?? (await signServiceJwt(this.serviceName)) ?? undefined;\n\n // Fetch from API\n try {\n const response = await fetch(this.gatewayUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(context.actorId && { 'x-actor-id': context.actorId }),\n 'x-actor-type': context.actorType ?? this.defaultActorType,\n 'x-workspace-id': context.workspaceId,\n ...(bearerToken && { Authorization: `Bearer ${bearerToken}` }),\n // Re-present the inbound CF Access service token when the flag check\n // runs against a CF-Access-gated public gateway (alpha). No-op when\n // request context carries no CF creds (local dev, production).\n ...ContextManager.getCfAccessHeaders(),\n },\n body: JSON.stringify({\n query: EVALUATE_QUERY,\n variables: {\n input: {\n workspaceId: context.workspaceId,\n keys: keys ?? null,\n },\n },\n }),\n });\n\n if (!response.ok) {\n // Return empty map on error — default-allow handled by callers\n return new Map();\n }\n\n const json = (await response.json()) as {\n data?: {\n evaluateFeatureFlags?: {\n evaluations?: FlagEvaluation[];\n };\n };\n errors?: Array<{ message: string }>;\n };\n\n if (json.errors || !json.data?.evaluateFeatureFlags?.evaluations) {\n return new Map();\n }\n\n const evaluations = new Map<string, FlagEvaluation>();\n for (const evaluation of json.data.evaluateFeatureFlags.evaluations) {\n evaluations.set(evaluation.key, evaluation);\n }\n\n // Cache the results\n this.cache.set(cacheKey, {\n evaluations,\n expiresAt: Date.now() + this.cacheTtlMs,\n });\n\n return evaluations;\n } catch {\n // On network error, return empty map (default-allow handled by callers)\n return new Map();\n }\n }\n\n /**\n * Build a cache key from the evaluation context.\n *\n * Safe against undefined/null context — callers like isEnabled() may\n * pass no context during service boot (e.g. checking a default-allow\n * feature flag at startup before the first request builds a real\n * actor context). Without the guard, isEnabled(flag) used to throw\n * \"undefined is not an object (evaluating 'context.workspaceId')\"\n * and crash the svc at startServer().\n */\n private buildCacheKey(context: EvaluationContext | undefined): string {\n const ctx = context ?? ({} as EvaluationContext);\n return `${ctx.workspaceId ?? 'system'}:${ctx.actorId ?? 'anonymous'}:${ctx.actorType ?? 'user'}`;\n }\n}\n"]}