@scalemule/nextjs 0.0.12 → 0.0.13

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.
package/dist/index.js CHANGED
@@ -2908,9 +2908,15 @@ function useFeatureFlags(options = {}) {
2908
2908
  setLoading(false);
2909
2909
  }
2910
2910
  }, [client, enabled, environment, keys, keysKey, publishableKey, gatewayUrl]);
2911
+ const bootstrapCoversKeys = react.useMemo(() => {
2912
+ if (!hasBootstrap || !keys || keys.length === 0) return false;
2913
+ return keys.every((k) => k in initialFlags);
2914
+ }, [hasBootstrap, keys, initialFlags]);
2911
2915
  react.useEffect(() => {
2912
- void refresh();
2913
- }, [refresh]);
2916
+ if (!bootstrapCoversKeys) {
2917
+ void refresh();
2918
+ }
2919
+ }, [refresh, bootstrapCoversKeys]);
2914
2920
  const isEnabled = react.useCallback(
2915
2921
  (flagKey, fallback = false) => {
2916
2922
  const evaluation = flags[flagKey];
package/dist/index.mjs CHANGED
@@ -2906,9 +2906,15 @@ function useFeatureFlags(options = {}) {
2906
2906
  setLoading(false);
2907
2907
  }
2908
2908
  }, [client, enabled, environment, keys, keysKey, publishableKey, gatewayUrl]);
2909
+ const bootstrapCoversKeys = useMemo(() => {
2910
+ if (!hasBootstrap || !keys || keys.length === 0) return false;
2911
+ return keys.every((k) => k in initialFlags);
2912
+ }, [hasBootstrap, keys, initialFlags]);
2909
2913
  useEffect(() => {
2910
- void refresh();
2911
- }, [refresh]);
2914
+ if (!bootstrapCoversKeys) {
2915
+ void refresh();
2916
+ }
2917
+ }, [refresh, bootstrapCoversKeys]);
2912
2918
  const isEnabled = useCallback(
2913
2919
  (flagKey, fallback = false) => {
2914
2920
  const evaluation = flags[flagKey];
@@ -439,6 +439,11 @@ declare function apiHandler(handler: HandlerFn, options?: HandlerOptions): (requ
439
439
  * Evaluates feature flags on the server during SSR so the client
440
440
  * has correct values on first render -- no loading flash, no race condition.
441
441
  *
442
+ * Results are cached in-memory with a configurable TTL (default: 60s) to
443
+ * prevent excessive API calls. Since feature flags change infrequently,
444
+ * a short cache window is fine and avoids hammering the flags service on
445
+ * every page render.
446
+ *
442
447
  * @example
443
448
  * ```ts
444
449
  * // app/layout.tsx
@@ -461,14 +466,18 @@ declare function apiHandler(handler: HandlerFn, options?: HandlerOptions): (requ
461
466
  * and passes it as ip_address in the evaluation context so IP-based targeting
462
467
  * rules work correctly.
463
468
  *
469
+ * Results are cached in-memory for `cacheTtlMs` milliseconds (default: 60s).
470
+ * Pass `cacheTtlMs: 0` to disable caching.
471
+ *
464
472
  * Returns a Record that can be passed directly to ScaleMuleProvider's
465
473
  * bootstrapFlags prop.
466
474
  *
467
475
  * @param flagKeys - Array of flag keys to evaluate
468
476
  * @param environment - Environment name (default: 'prod')
469
477
  * @param extraContext - Additional context attributes to include
478
+ * @param cacheTtlMs - Cache TTL in milliseconds (default: 60000). Set to 0 to disable.
470
479
  */
471
- declare function getBootstrapFlags(flagKeys: string[], environment?: string, extraContext?: Record<string, unknown>): Promise<Record<string, unknown>>;
480
+ declare function getBootstrapFlags(flagKeys: string[], environment?: string, extraContext?: Record<string, unknown>, cacheTtlMs?: number): Promise<Record<string, unknown>>;
472
481
 
473
482
  /**
474
483
  * Next.js Middleware Helpers
@@ -439,6 +439,11 @@ declare function apiHandler(handler: HandlerFn, options?: HandlerOptions): (requ
439
439
  * Evaluates feature flags on the server during SSR so the client
440
440
  * has correct values on first render -- no loading flash, no race condition.
441
441
  *
442
+ * Results are cached in-memory with a configurable TTL (default: 60s) to
443
+ * prevent excessive API calls. Since feature flags change infrequently,
444
+ * a short cache window is fine and avoids hammering the flags service on
445
+ * every page render.
446
+ *
442
447
  * @example
443
448
  * ```ts
444
449
  * // app/layout.tsx
@@ -461,14 +466,18 @@ declare function apiHandler(handler: HandlerFn, options?: HandlerOptions): (requ
461
466
  * and passes it as ip_address in the evaluation context so IP-based targeting
462
467
  * rules work correctly.
463
468
  *
469
+ * Results are cached in-memory for `cacheTtlMs` milliseconds (default: 60s).
470
+ * Pass `cacheTtlMs: 0` to disable caching.
471
+ *
464
472
  * Returns a Record that can be passed directly to ScaleMuleProvider's
465
473
  * bootstrapFlags prop.
466
474
  *
467
475
  * @param flagKeys - Array of flag keys to evaluate
468
476
  * @param environment - Environment name (default: 'prod')
469
477
  * @param extraContext - Additional context attributes to include
478
+ * @param cacheTtlMs - Cache TTL in milliseconds (default: 60000). Set to 0 to disable.
470
479
  */
471
- declare function getBootstrapFlags(flagKeys: string[], environment?: string, extraContext?: Record<string, unknown>): Promise<Record<string, unknown>>;
480
+ declare function getBootstrapFlags(flagKeys: string[], environment?: string, extraContext?: Record<string, unknown>, cacheTtlMs?: number): Promise<Record<string, unknown>>;
472
481
 
473
482
  /**
474
483
  * Next.js Middleware Helpers
@@ -1772,8 +1772,17 @@ function getClient() {
1772
1772
  }
1773
1773
  return _serverClient;
1774
1774
  }
1775
- async function getBootstrapFlags(flagKeys, environment = "prod", extraContext = {}) {
1775
+ var _flagCache = /* @__PURE__ */ new Map();
1776
+ var DEFAULT_CACHE_TTL_MS = 6e4;
1777
+ async function getBootstrapFlags(flagKeys, environment = "prod", extraContext = {}, cacheTtlMs = DEFAULT_CACHE_TTL_MS) {
1776
1778
  try {
1779
+ const cacheKey = [...flagKeys].sort().join("|") + ":" + environment;
1780
+ if (cacheTtlMs > 0) {
1781
+ const cached = _flagCache.get(cacheKey);
1782
+ if (cached && Date.now() - cached.timestamp < cacheTtlMs) {
1783
+ return cached.result;
1784
+ }
1785
+ }
1777
1786
  const hdrs = await headers.headers();
1778
1787
  const forwardedFor = hdrs.get("x-forwarded-for");
1779
1788
  const realIp = hdrs.get("x-real-ip") || hdrs.get("x-real-client-ip");
@@ -1785,7 +1794,19 @@ async function getBootstrapFlags(flagKeys, environment = "prod", extraContext =
1785
1794
  context.ip_address = clientIp;
1786
1795
  }
1787
1796
  const result = await getClient().flags.evaluateBatch(flagKeys, context, environment);
1788
- return result || {};
1797
+ const flagResult = result || {};
1798
+ if (cacheTtlMs > 0) {
1799
+ _flagCache.set(cacheKey, { result: flagResult, timestamp: Date.now() });
1800
+ if (_flagCache.size > 100) {
1801
+ const now = Date.now();
1802
+ for (const [key, entry] of _flagCache) {
1803
+ if (now - entry.timestamp > cacheTtlMs) {
1804
+ _flagCache.delete(key);
1805
+ }
1806
+ }
1807
+ }
1808
+ }
1809
+ return flagResult;
1789
1810
  } catch {
1790
1811
  return {};
1791
1812
  }
@@ -1917,14 +1938,14 @@ function clearOAuthState(response) {
1917
1938
  }
1918
1939
 
1919
1940
  // src/server/secrets.ts
1920
- var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
1941
+ var DEFAULT_CACHE_TTL_MS2 = 5 * 60 * 1e3;
1921
1942
  var secretsCache = {};
1922
1943
  var globalConfig = {};
1923
1944
  function configureSecrets(config) {
1924
1945
  globalConfig = { ...globalConfig, ...config };
1925
1946
  }
1926
1947
  async function getAppSecret(key) {
1927
- const cacheTtl = globalConfig.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
1948
+ const cacheTtl = globalConfig.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS2;
1928
1949
  const noCache = globalConfig.noCache ?? false;
1929
1950
  if (!noCache) {
1930
1951
  const cached = secretsCache[key];
@@ -1976,14 +1997,14 @@ async function prefetchSecrets(keys) {
1976
1997
  }
1977
1998
 
1978
1999
  // src/server/bundles.ts
1979
- var DEFAULT_CACHE_TTL_MS2 = 5 * 60 * 1e3;
2000
+ var DEFAULT_CACHE_TTL_MS3 = 5 * 60 * 1e3;
1980
2001
  var bundlesCache = {};
1981
2002
  var globalConfig2 = {};
1982
2003
  function configureBundles(config) {
1983
2004
  globalConfig2 = { ...globalConfig2, ...config };
1984
2005
  }
1985
2006
  async function getBundle(key, resolve = true) {
1986
- const cacheTtl = globalConfig2.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS2;
2007
+ const cacheTtl = globalConfig2.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS3;
1987
2008
  const noCache = globalConfig2.noCache ?? false;
1988
2009
  if (!noCache) {
1989
2010
  const cached = bundlesCache[key];
@@ -1770,8 +1770,17 @@ function getClient() {
1770
1770
  }
1771
1771
  return _serverClient;
1772
1772
  }
1773
- async function getBootstrapFlags(flagKeys, environment = "prod", extraContext = {}) {
1773
+ var _flagCache = /* @__PURE__ */ new Map();
1774
+ var DEFAULT_CACHE_TTL_MS = 6e4;
1775
+ async function getBootstrapFlags(flagKeys, environment = "prod", extraContext = {}, cacheTtlMs = DEFAULT_CACHE_TTL_MS) {
1774
1776
  try {
1777
+ const cacheKey = [...flagKeys].sort().join("|") + ":" + environment;
1778
+ if (cacheTtlMs > 0) {
1779
+ const cached = _flagCache.get(cacheKey);
1780
+ if (cached && Date.now() - cached.timestamp < cacheTtlMs) {
1781
+ return cached.result;
1782
+ }
1783
+ }
1775
1784
  const hdrs = await headers();
1776
1785
  const forwardedFor = hdrs.get("x-forwarded-for");
1777
1786
  const realIp = hdrs.get("x-real-ip") || hdrs.get("x-real-client-ip");
@@ -1783,7 +1792,19 @@ async function getBootstrapFlags(flagKeys, environment = "prod", extraContext =
1783
1792
  context.ip_address = clientIp;
1784
1793
  }
1785
1794
  const result = await getClient().flags.evaluateBatch(flagKeys, context, environment);
1786
- return result || {};
1795
+ const flagResult = result || {};
1796
+ if (cacheTtlMs > 0) {
1797
+ _flagCache.set(cacheKey, { result: flagResult, timestamp: Date.now() });
1798
+ if (_flagCache.size > 100) {
1799
+ const now = Date.now();
1800
+ for (const [key, entry] of _flagCache) {
1801
+ if (now - entry.timestamp > cacheTtlMs) {
1802
+ _flagCache.delete(key);
1803
+ }
1804
+ }
1805
+ }
1806
+ }
1807
+ return flagResult;
1787
1808
  } catch {
1788
1809
  return {};
1789
1810
  }
@@ -1915,14 +1936,14 @@ function clearOAuthState(response) {
1915
1936
  }
1916
1937
 
1917
1938
  // src/server/secrets.ts
1918
- var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
1939
+ var DEFAULT_CACHE_TTL_MS2 = 5 * 60 * 1e3;
1919
1940
  var secretsCache = {};
1920
1941
  var globalConfig = {};
1921
1942
  function configureSecrets(config) {
1922
1943
  globalConfig = { ...globalConfig, ...config };
1923
1944
  }
1924
1945
  async function getAppSecret(key) {
1925
- const cacheTtl = globalConfig.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
1946
+ const cacheTtl = globalConfig.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS2;
1926
1947
  const noCache = globalConfig.noCache ?? false;
1927
1948
  if (!noCache) {
1928
1949
  const cached = secretsCache[key];
@@ -1974,14 +1995,14 @@ async function prefetchSecrets(keys) {
1974
1995
  }
1975
1996
 
1976
1997
  // src/server/bundles.ts
1977
- var DEFAULT_CACHE_TTL_MS2 = 5 * 60 * 1e3;
1998
+ var DEFAULT_CACHE_TTL_MS3 = 5 * 60 * 1e3;
1978
1999
  var bundlesCache = {};
1979
2000
  var globalConfig2 = {};
1980
2001
  function configureBundles(config) {
1981
2002
  globalConfig2 = { ...globalConfig2, ...config };
1982
2003
  }
1983
2004
  async function getBundle(key, resolve = true) {
1984
- const cacheTtl = globalConfig2.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS2;
2005
+ const cacheTtl = globalConfig2.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS3;
1985
2006
  const noCache = globalConfig2.noCache ?? false;
1986
2007
  if (!noCache) {
1987
2008
  const cached = bundlesCache[key];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/nextjs",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "ScaleMule SDK for Next.js applications - authentication, storage, and user management",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",