@smooai/config 2.0.3 → 2.1.0

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.
Files changed (58) hide show
  1. package/README.md +77 -0
  2. package/dist/{chunk-Z3CZGNU5.mjs → chunk-3B5SRHJ2.mjs} +36 -5
  3. package/dist/chunk-3B5SRHJ2.mjs.map +1 -0
  4. package/dist/chunk-FAF45VZA.mjs +24 -0
  5. package/dist/chunk-FAF45VZA.mjs.map +1 -0
  6. package/dist/chunk-GP2JUWOD.mjs +53 -0
  7. package/dist/chunk-GP2JUWOD.mjs.map +1 -0
  8. package/dist/chunk-N6Z4AZZA.mjs +26 -0
  9. package/dist/chunk-N6Z4AZZA.mjs.map +1 -0
  10. package/dist/{chunk-SKX7CPGS.mjs → chunk-VL7AIM2X.mjs} +3 -23
  11. package/dist/chunk-VL7AIM2X.mjs.map +1 -0
  12. package/dist/chunk-VUYQFQ63.mjs +30 -0
  13. package/dist/chunk-VUYQFQ63.mjs.map +1 -0
  14. package/dist/nextjs/getConfig.d.mts +31 -0
  15. package/dist/nextjs/getConfig.d.ts +31 -0
  16. package/dist/nextjs/getConfig.js +172 -0
  17. package/dist/nextjs/getConfig.js.map +1 -0
  18. package/dist/nextjs/getConfig.mjs +9 -0
  19. package/dist/nextjs/getConfig.mjs.map +1 -0
  20. package/dist/nextjs/hooks.d.mts +22 -0
  21. package/dist/nextjs/hooks.d.ts +22 -0
  22. package/dist/nextjs/hooks.js +89 -0
  23. package/dist/nextjs/hooks.js.map +1 -0
  24. package/dist/nextjs/hooks.mjs +13 -0
  25. package/dist/nextjs/hooks.mjs.map +1 -0
  26. package/dist/nextjs/index.d.mts +28 -0
  27. package/dist/nextjs/index.d.ts +28 -0
  28. package/dist/nextjs/index.js +255 -0
  29. package/dist/nextjs/index.js.map +1 -0
  30. package/dist/nextjs/index.mjs +40 -0
  31. package/dist/nextjs/index.mjs.map +1 -0
  32. package/dist/platform/client.d.mts +18 -1
  33. package/dist/platform/client.d.ts +18 -1
  34. package/dist/platform/client.js +35 -4
  35. package/dist/platform/client.js.map +1 -1
  36. package/dist/platform/client.mjs +1 -1
  37. package/dist/react/hooks.js.map +1 -1
  38. package/dist/react/hooks.mjs +3 -2
  39. package/dist/react/index.d.mts +4 -2
  40. package/dist/react/index.d.ts +4 -2
  41. package/dist/react/index.js +37 -4
  42. package/dist/react/index.js.map +1 -1
  43. package/dist/react/index.mjs +8 -4
  44. package/dist/vite/index.d.mts +6 -0
  45. package/dist/vite/index.d.ts +6 -0
  46. package/dist/vite/index.js +249 -0
  47. package/dist/vite/index.js.map +1 -0
  48. package/dist/vite/index.mjs +27 -0
  49. package/dist/vite/index.mjs.map +1 -0
  50. package/dist/vite/preloadConfig.d.mts +30 -0
  51. package/dist/vite/preloadConfig.d.ts +30 -0
  52. package/dist/vite/preloadConfig.js +180 -0
  53. package/dist/vite/preloadConfig.js.map +1 -0
  54. package/dist/vite/preloadConfig.mjs +13 -0
  55. package/dist/vite/preloadConfig.mjs.map +1 -0
  56. package/package.json +6 -2
  57. package/dist/chunk-SKX7CPGS.mjs.map +0 -1
  58. package/dist/chunk-Z3CZGNU5.mjs.map +0 -1
package/README.md CHANGED
@@ -269,6 +269,67 @@ function MyComponent() {
269
269
  }
270
270
  ```
271
271
 
272
+ ### Next.js Integration
273
+
274
+ For Next.js applications, use `getConfig` in Server Components and `SmooConfigProvider` to pass values to client components with zero loading flash:
275
+
276
+ ```tsx
277
+ // app/layout.tsx (Server Component)
278
+ import { getConfig, SmooConfigProvider } from '@smooai/config/nextjs';
279
+
280
+ export default async function RootLayout({ children }: { children: React.ReactNode }) {
281
+ const config = await getConfig({
282
+ environment: 'production',
283
+ fetchOptions: { next: { revalidate: 60 } }, // ISR: revalidate every 60s
284
+ });
285
+
286
+ return (
287
+ <html>
288
+ <body>
289
+ <SmooConfigProvider
290
+ initialValues={config}
291
+ baseUrl={process.env.SMOOAI_CONFIG_API_URL}
292
+ apiKey={process.env.SMOOAI_CONFIG_API_KEY}
293
+ orgId={process.env.SMOOAI_CONFIG_ORG_ID}
294
+ environment="production"
295
+ >
296
+ {children}
297
+ </SmooConfigProvider>
298
+ </body>
299
+ </html>
300
+ );
301
+ }
302
+
303
+ // Any client component — values are available synchronously (no loading flash)
304
+ import { usePublicConfig, useFeatureFlag } from '@smooai/config/nextjs';
305
+
306
+ function MyComponent() {
307
+ const { value: apiUrl } = usePublicConfig<string>('API_URL'); // Instant — pre-seeded from SSR
308
+ const { value: enableNewUI } = useFeatureFlag<boolean>('ENABLE_NEW_UI');
309
+ return <div>API: {apiUrl}</div>;
310
+ }
311
+ ```
312
+
313
+ ### Vite React Integration
314
+
315
+ For Vite-based React apps, call `preloadConfig()` before mounting React to start fetching early:
316
+
317
+ ```tsx
318
+ // main.tsx
319
+ import { preloadConfig } from '@smooai/config/vite';
320
+ import { ConfigProvider } from '@smooai/config/vite';
321
+ import { createRoot } from 'react-dom/client';
322
+
323
+ // Start fetching config immediately (before React renders)
324
+ preloadConfig({ environment: 'production' });
325
+
326
+ createRoot(document.getElementById('root')!).render(
327
+ <ConfigProvider baseUrl="https://config.smooai.dev" apiKey="your-public-key" orgId="your-org-id" environment="production">
328
+ <App />
329
+ </ConfigProvider>,
330
+ );
331
+ ```
332
+
272
333
  ### Python SDK Client
273
334
 
274
335
  ```python
@@ -352,6 +413,22 @@ allValues, err := client.GetAllValues("")
352
413
 
353
414
  Each tier gets its own schema, validation, and JSON Schema output for cross-language consumption.
354
415
 
416
+ ### Security: B2M Key Restrictions
417
+
418
+ The Smoo AI Config API enforces tier-based access control depending on the API key type:
419
+
420
+ | Operation | B2M (Public Key) | M2M (Secret Key) |
421
+ | -------------------- | ----------------- | ---------------- |
422
+ | Read public values | Yes | Yes |
423
+ | Read feature flags | Yes | Yes |
424
+ | Read secret values | **No** (filtered) | Yes |
425
+ | Write config values | **No** (403) | Yes |
426
+ | Delete config values | **No** (403) | Yes |
427
+
428
+ **Browser-to-Machine (B2M)** keys are designed for browser-based clients. Secret-tier values are automatically filtered from bulk responses and return 403 for individual lookups. B2M keys are read-only for public and feature flag tiers.
429
+
430
+ **Machine-to-Machine (M2M)** keys have full access to all tiers and write operations.
431
+
355
432
  ## Development
356
433
 
357
434
  ### Prerequisites
@@ -37,9 +37,10 @@ var ConfigClient = class {
37
37
  }
38
38
  return entry.value;
39
39
  }
40
- async fetchJson(path) {
40
+ async fetchJson(path, fetchOptions) {
41
41
  const response = await fetch(`${this.baseUrl}${path}`, {
42
- headers: { Authorization: `Bearer ${this.apiKey}` }
42
+ ...fetchOptions,
43
+ headers: { Authorization: `Bearer ${this.apiKey}`, ...fetchOptions?.headers }
43
44
  });
44
45
  if (!response.ok) {
45
46
  throw new Error(`Config API error: HTTP ${response.status} ${response.statusText}`);
@@ -66,11 +67,14 @@ var ConfigClient = class {
66
67
  /**
67
68
  * Get all config values for an environment.
68
69
  * All returned values are cached locally.
70
+ * @param environment - Environment name (defaults to constructor option or SMOOAI_CONFIG_ENV)
71
+ * @param fetchOptions - Optional fetch options (e.g., Next.js `{ next: { revalidate: 60 } }`)
69
72
  */
70
- async getAllValues(environment) {
73
+ async getAllValues(environment, fetchOptions) {
71
74
  const env = environment ?? this.defaultEnvironment;
72
75
  const result = await this.fetchJson(
73
- `/organizations/${this.orgId}/config/values?environment=${encodeURIComponent(env)}`
76
+ `/organizations/${this.orgId}/config/values?environment=${encodeURIComponent(env)}`,
77
+ fetchOptions
74
78
  );
75
79
  const expiresAt = this.computeExpiresAt();
76
80
  for (const [key, value] of Object.entries(result.values)) {
@@ -78,6 +82,33 @@ var ConfigClient = class {
78
82
  }
79
83
  return result.values;
80
84
  }
85
+ /**
86
+ * Pre-populate a single cache entry (e.g., from SSR).
87
+ * Does not make a network request.
88
+ */
89
+ seedCache(key, value, environment) {
90
+ const env = environment ?? this.defaultEnvironment;
91
+ this.cache.set(`${env}:${key}`, { value, expiresAt: this.computeExpiresAt() });
92
+ }
93
+ /**
94
+ * Pre-populate multiple cache entries from a key-value map (e.g., from SSR).
95
+ * Does not make a network request.
96
+ */
97
+ seedCacheFromMap(values, environment) {
98
+ const env = environment ?? this.defaultEnvironment;
99
+ const expiresAt = this.computeExpiresAt();
100
+ for (const [key, value] of Object.entries(values)) {
101
+ this.cache.set(`${env}:${key}`, { value, expiresAt });
102
+ }
103
+ }
104
+ /**
105
+ * Synchronously read a value from the local cache without making a network request.
106
+ * Returns `undefined` if the key is not cached or has expired.
107
+ */
108
+ getCachedValue(key, environment) {
109
+ const env = environment ?? this.defaultEnvironment;
110
+ return this.getCached(`${env}:${key}`);
111
+ }
81
112
  /** Clear the entire local cache. */
82
113
  invalidateCache() {
83
114
  this.cache.clear();
@@ -96,4 +127,4 @@ var ConfigClient = class {
96
127
  export {
97
128
  ConfigClient
98
129
  };
99
- //# sourceMappingURL=chunk-Z3CZGNU5.mjs.map
130
+ //# sourceMappingURL=chunk-3B5SRHJ2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/client.ts"],"sourcesContent":["/**\n * Browser/universal HTTP client for fetching config values from the Smoo AI config server.\n *\n * Environment variables (optional — used as defaults when constructor args are omitted):\n * SMOOAI_CONFIG_API_URL — Base URL of the config API\n * SMOOAI_CONFIG_API_KEY — Bearer token for authentication\n * SMOOAI_CONFIG_ORG_ID — Organization ID\n * SMOOAI_CONFIG_ENV — Default environment name (e.g. \"production\")\n */\n\nexport interface ConfigClientOptions {\n /** Base URL of the config API server. Falls back to SMOOAI_CONFIG_API_URL env var. */\n baseUrl?: string;\n /** API key (M2M / client credentials token). Falls back to SMOOAI_CONFIG_API_KEY env var. */\n apiKey?: string;\n /** Organization ID. Falls back to SMOOAI_CONFIG_ORG_ID env var. */\n orgId?: string;\n /** Default environment name. Falls back to SMOOAI_CONFIG_ENV env var, then \"development\". */\n environment?: string;\n /** Cache TTL in milliseconds. 0 or undefined means cache never expires (manual invalidation only). */\n cacheTtlMs?: number;\n}\n\ninterface CacheEntry {\n value: unknown;\n expiresAt: number; // 0 = never expires\n}\n\nfunction getEnv(key: string): string | undefined {\n if (typeof process !== 'undefined' && process.env) {\n return process.env[key];\n }\n return undefined;\n}\n\nexport class ConfigClient {\n private baseUrl: string;\n private orgId: string;\n private apiKey: string;\n private defaultEnvironment: string;\n private cacheTtlMs: number;\n private cache: Map<string, CacheEntry> = new Map();\n\n constructor(options: ConfigClientOptions = {}) {\n const baseUrl = options.baseUrl ?? getEnv('SMOOAI_CONFIG_API_URL');\n const apiKey = options.apiKey ?? getEnv('SMOOAI_CONFIG_API_KEY');\n const orgId = options.orgId ?? getEnv('SMOOAI_CONFIG_ORG_ID');\n\n if (!baseUrl) throw new Error('@smooai/config: baseUrl is required (or set SMOOAI_CONFIG_API_URL)');\n if (!apiKey) throw new Error('@smooai/config: apiKey is required (or set SMOOAI_CONFIG_API_KEY)');\n if (!orgId) throw new Error('@smooai/config: orgId is required (or set SMOOAI_CONFIG_ORG_ID)');\n\n this.baseUrl = baseUrl.replace(/\\/+$/, '');\n this.apiKey = apiKey;\n this.orgId = orgId;\n this.defaultEnvironment = options.environment ?? getEnv('SMOOAI_CONFIG_ENV') ?? 'development';\n this.cacheTtlMs = options.cacheTtlMs ?? 0;\n }\n\n private computeExpiresAt(): number {\n return this.cacheTtlMs > 0 ? Date.now() + this.cacheTtlMs : 0;\n }\n\n private getCached(cacheKey: string): unknown | undefined {\n const entry = this.cache.get(cacheKey);\n if (!entry) return undefined;\n if (entry.expiresAt > 0 && Date.now() > entry.expiresAt) {\n this.cache.delete(cacheKey);\n return undefined;\n }\n return entry.value;\n }\n\n private async fetchJson<T>(path: string, fetchOptions?: RequestInit): Promise<T> {\n const response = await fetch(`${this.baseUrl}${path}`, {\n ...fetchOptions,\n headers: { Authorization: `Bearer ${this.apiKey}`, ...fetchOptions?.headers },\n });\n if (!response.ok) {\n throw new Error(`Config API error: HTTP ${response.status} ${response.statusText}`);\n }\n return response.json() as Promise<T>;\n }\n\n /**\n * Get a single config value by key.\n * Results are cached locally after the first fetch.\n */\n async getValue(key: string, environment?: string): Promise<unknown> {\n const env = environment ?? this.defaultEnvironment;\n const cacheKey = `${env}:${key}`;\n\n const cached = this.getCached(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n const result = await this.fetchJson<{ value: unknown }>(\n `/organizations/${this.orgId}/config/values/${encodeURIComponent(key)}?environment=${encodeURIComponent(env)}`,\n );\n this.cache.set(cacheKey, { value: result.value, expiresAt: this.computeExpiresAt() });\n return result.value;\n }\n\n /**\n * Get all config values for an environment.\n * All returned values are cached locally.\n * @param environment - Environment name (defaults to constructor option or SMOOAI_CONFIG_ENV)\n * @param fetchOptions - Optional fetch options (e.g., Next.js `{ next: { revalidate: 60 } }`)\n */\n async getAllValues(environment?: string, fetchOptions?: RequestInit): Promise<Record<string, unknown>> {\n const env = environment ?? this.defaultEnvironment;\n\n const result = await this.fetchJson<{ values: Record<string, unknown> }>(\n `/organizations/${this.orgId}/config/values?environment=${encodeURIComponent(env)}`,\n fetchOptions,\n );\n\n const expiresAt = this.computeExpiresAt();\n for (const [key, value] of Object.entries(result.values)) {\n this.cache.set(`${env}:${key}`, { value, expiresAt });\n }\n\n return result.values;\n }\n\n /**\n * Pre-populate a single cache entry (e.g., from SSR).\n * Does not make a network request.\n */\n seedCache(key: string, value: unknown, environment?: string): void {\n const env = environment ?? this.defaultEnvironment;\n this.cache.set(`${env}:${key}`, { value, expiresAt: this.computeExpiresAt() });\n }\n\n /**\n * Pre-populate multiple cache entries from a key-value map (e.g., from SSR).\n * Does not make a network request.\n */\n seedCacheFromMap(values: Record<string, unknown>, environment?: string): void {\n const env = environment ?? this.defaultEnvironment;\n const expiresAt = this.computeExpiresAt();\n for (const [key, value] of Object.entries(values)) {\n this.cache.set(`${env}:${key}`, { value, expiresAt });\n }\n }\n\n /**\n * Synchronously read a value from the local cache without making a network request.\n * Returns `undefined` if the key is not cached or has expired.\n */\n getCachedValue(key: string, environment?: string): unknown | undefined {\n const env = environment ?? this.defaultEnvironment;\n return this.getCached(`${env}:${key}`);\n }\n\n /** Clear the entire local cache. */\n invalidateCache(): void {\n this.cache.clear();\n }\n\n /** Clear cached values for a specific environment. */\n invalidateCacheForEnvironment(environment: string): void {\n const prefix = `${environment}:`;\n for (const key of this.cache.keys()) {\n if (key.startsWith(prefix)) {\n this.cache.delete(key);\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,24 @@
1
+ import {
2
+ ConfigClient
3
+ } from "./chunk-3B5SRHJ2.mjs";
4
+
5
+ // src/nextjs/getConfig.ts
6
+ async function getConfig(options = {}) {
7
+ const { keys, fetchOptions, ...clientOptions } = options;
8
+ const client = new ConfigClient(clientOptions);
9
+ if (keys && keys.length > 0) {
10
+ const result = {};
11
+ await Promise.all(
12
+ keys.map(async (key) => {
13
+ result[key] = await client.getValue(key, clientOptions.environment);
14
+ })
15
+ );
16
+ return result;
17
+ }
18
+ return client.getAllValues(clientOptions.environment, fetchOptions);
19
+ }
20
+
21
+ export {
22
+ getConfig
23
+ };
24
+ //# sourceMappingURL=chunk-FAF45VZA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/nextjs/getConfig.ts"],"sourcesContent":["import { ConfigClient, type ConfigClientOptions } from '../platform/client';\n\nexport interface GetConfigOptions extends ConfigClientOptions {\n /** Config keys to fetch. If omitted, fetches all values for the environment. */\n keys?: string[];\n /** Additional fetch options passed to the underlying HTTP call (e.g., Next.js `{ next: { revalidate: 60 } }`). */\n fetchOptions?: RequestInit;\n}\n\n/**\n * Server-side helper for fetching config values in Next.js Server Components or `getServerSideProps`.\n *\n * ```tsx\n * // app/layout.tsx (Server Component)\n * import { getConfig } from '@smooai/config/nextjs';\n *\n * export default async function RootLayout({ children }) {\n * const config = await getConfig({\n * environment: 'production',\n * fetchOptions: { next: { revalidate: 60 } },\n * });\n * return (\n * <SmooConfigProvider initialValues={config}>\n * {children}\n * </SmooConfigProvider>\n * );\n * }\n * ```\n */\nexport async function getConfig(options: GetConfigOptions = {}): Promise<Record<string, unknown>> {\n const { keys, fetchOptions, ...clientOptions } = options;\n const client = new ConfigClient(clientOptions);\n\n if (keys && keys.length > 0) {\n const result: Record<string, unknown> = {};\n await Promise.all(\n keys.map(async (key) => {\n result[key] = await client.getValue(key, clientOptions.environment);\n }),\n );\n return result;\n }\n\n return client.getAllValues(clientOptions.environment, fetchOptions);\n}\n"],"mappings":";;;;;AA6BA,eAAsB,UAAU,UAA4B,CAAC,GAAqC;AAC9F,QAAM,EAAE,MAAM,cAAc,GAAG,cAAc,IAAI;AACjD,QAAM,SAAS,IAAI,aAAa,aAAa;AAE7C,MAAI,QAAQ,KAAK,SAAS,GAAG;AACzB,UAAM,SAAkC,CAAC;AACzC,UAAM,QAAQ;AAAA,MACV,KAAK,IAAI,OAAO,QAAQ;AACpB,eAAO,GAAG,IAAI,MAAM,OAAO,SAAS,KAAK,cAAc,WAAW;AAAA,MACtE,CAAC;AAAA,IACL;AACA,WAAO;AAAA,EACX;AAEA,SAAO,OAAO,aAAa,cAAc,aAAa,YAAY;AACtE;","names":[]}
@@ -0,0 +1,53 @@
1
+ import {
2
+ useConfigClient
3
+ } from "./chunk-N6Z4AZZA.mjs";
4
+
5
+ // src/nextjs/hooks.ts
6
+ import { useCallback, useEffect, useState } from "react";
7
+ function useConfigValue(key, environment) {
8
+ const client = useConfigClient();
9
+ const [value, setValue] = useState(() => client.getCachedValue(key, environment));
10
+ const [isLoading, setIsLoading] = useState(value === void 0);
11
+ const [error, setError] = useState(null);
12
+ const [fetchCount, setFetchCount] = useState(0);
13
+ const refetch = useCallback(() => {
14
+ client.invalidateCache();
15
+ setFetchCount((c) => c + 1);
16
+ }, [client]);
17
+ useEffect(() => {
18
+ if (value !== void 0 && fetchCount === 0) {
19
+ setIsLoading(false);
20
+ return;
21
+ }
22
+ let cancelled = false;
23
+ setIsLoading(true);
24
+ setError(null);
25
+ client.getValue(key, environment).then((result) => {
26
+ if (!cancelled) {
27
+ setValue(result);
28
+ setIsLoading(false);
29
+ }
30
+ }).catch((err) => {
31
+ if (!cancelled) {
32
+ setError(err instanceof Error ? err : new Error(String(err)));
33
+ setIsLoading(false);
34
+ }
35
+ });
36
+ return () => {
37
+ cancelled = true;
38
+ };
39
+ }, [client, key, environment, fetchCount]);
40
+ return { value, isLoading, error, refetch };
41
+ }
42
+ function usePublicConfig(key, environment) {
43
+ return useConfigValue(key, environment);
44
+ }
45
+ function useFeatureFlag(key, environment) {
46
+ return useConfigValue(key, environment);
47
+ }
48
+
49
+ export {
50
+ usePublicConfig,
51
+ useFeatureFlag
52
+ };
53
+ //# sourceMappingURL=chunk-GP2JUWOD.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/nextjs/hooks.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { useConfigClient } from '../react/ConfigProvider';\n\ninterface UseConfigResult<T = unknown> {\n /** The resolved config value, or undefined while loading (synchronous when pre-seeded). */\n value: T | undefined;\n /** True while the initial fetch is in progress. False immediately if value was pre-seeded. */\n isLoading: boolean;\n /** The error if the fetch failed. */\n error: Error | null;\n /** Re-fetch the value (bypasses cache). */\n refetch: () => void;\n}\n\nfunction useConfigValue(key: string, environment?: string): UseConfigResult {\n const client = useConfigClient();\n\n // Attempt synchronous read from pre-seeded cache (zero loading flash)\n const [value, setValue] = useState<unknown>(() => client.getCachedValue(key, environment));\n const [isLoading, setIsLoading] = useState(value === undefined);\n const [error, setError] = useState<Error | null>(null);\n const [fetchCount, setFetchCount] = useState(0);\n\n const refetch = useCallback(() => {\n client.invalidateCache();\n setFetchCount((c) => c + 1);\n }, [client]);\n\n useEffect(() => {\n // If we already have a cached value and this is the initial mount, skip fetch\n if (value !== undefined && fetchCount === 0) {\n setIsLoading(false);\n return;\n }\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n client\n .getValue(key, environment)\n .then((result) => {\n if (!cancelled) {\n setValue(result);\n setIsLoading(false);\n }\n })\n .catch((err: unknown) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [client, key, environment, fetchCount]);\n\n return { value, isLoading, error, refetch };\n}\n\n/**\n * SSR-aware hook for fetching a public config value.\n * Returns the value synchronously when pre-seeded via `SmooConfigProvider`.\n */\nexport function usePublicConfig<T = unknown>(key: string, environment?: string): UseConfigResult<T> {\n return useConfigValue(key, environment) as UseConfigResult<T>;\n}\n\n/**\n * SSR-aware hook for fetching a feature flag value.\n * Returns the value synchronously when pre-seeded via `SmooConfigProvider`.\n */\nexport function useFeatureFlag<T = unknown>(key: string, environment?: string): UseConfigResult<T> {\n return useConfigValue(key, environment) as UseConfigResult<T>;\n}\n"],"mappings":";;;;;AAEA,SAAS,aAAa,WAAW,gBAAgB;AAcjD,SAAS,eAAe,KAAa,aAAuC;AACxE,QAAM,SAAS,gBAAgB;AAG/B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAkB,MAAM,OAAO,eAAe,KAAK,WAAW,CAAC;AACzF,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,UAAU,MAAS;AAC9D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AACrD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,CAAC;AAE9C,QAAM,UAAU,YAAY,MAAM;AAC9B,WAAO,gBAAgB;AACvB,kBAAc,CAAC,MAAM,IAAI,CAAC;AAAA,EAC9B,GAAG,CAAC,MAAM,CAAC;AAEX,YAAU,MAAM;AAEZ,QAAI,UAAU,UAAa,eAAe,GAAG;AACzC,mBAAa,KAAK;AAClB;AAAA,IACJ;AAEA,QAAI,YAAY;AAChB,iBAAa,IAAI;AACjB,aAAS,IAAI;AAEb,WACK,SAAS,KAAK,WAAW,EACzB,KAAK,CAAC,WAAW;AACd,UAAI,CAAC,WAAW;AACZ,iBAAS,MAAM;AACf,qBAAa,KAAK;AAAA,MACtB;AAAA,IACJ,CAAC,EACA,MAAM,CAAC,QAAiB;AACrB,UAAI,CAAC,WAAW;AACZ,iBAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAC5D,qBAAa,KAAK;AAAA,MACtB;AAAA,IACJ,CAAC;AAEL,WAAO,MAAM;AACT,kBAAY;AAAA,IAChB;AAAA,EACJ,GAAG,CAAC,QAAQ,KAAK,aAAa,UAAU,CAAC;AAEzC,SAAO,EAAE,OAAO,WAAW,OAAO,QAAQ;AAC9C;AAMO,SAAS,gBAA6B,KAAa,aAA0C;AAChG,SAAO,eAAe,KAAK,WAAW;AAC1C;AAMO,SAAS,eAA4B,KAAa,aAA0C;AAC/F,SAAO,eAAe,KAAK,WAAW;AAC1C;","names":[]}
@@ -0,0 +1,26 @@
1
+ import {
2
+ ConfigClient
3
+ } from "./chunk-3B5SRHJ2.mjs";
4
+
5
+ // src/react/ConfigProvider.tsx
6
+ import { createContext, useContext, useMemo } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var ConfigContext = createContext(null);
9
+ function ConfigProvider({ children, ...options }) {
10
+ const client = useMemo(() => new ConfigClient(options), [options.baseUrl, options.apiKey, options.orgId, options.environment]);
11
+ return /* @__PURE__ */ jsx(ConfigContext.Provider, { value: client, children });
12
+ }
13
+ function useConfigClient() {
14
+ const client = useContext(ConfigContext);
15
+ if (!client) {
16
+ throw new Error("useConfigClient must be used within a <ConfigProvider>");
17
+ }
18
+ return client;
19
+ }
20
+
21
+ export {
22
+ ConfigContext,
23
+ ConfigProvider,
24
+ useConfigClient
25
+ };
26
+ //# sourceMappingURL=chunk-N6Z4AZZA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/ConfigProvider.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { ConfigClient, type ConfigClientOptions } from '../platform/client';\n\nexport const ConfigContext = createContext<ConfigClient | null>(null);\n\nexport interface ConfigProviderProps extends ConfigClientOptions {\n children: ReactNode;\n}\n\n/**\n * Provides a ConfigClient instance to all descendant components.\n *\n * ```tsx\n * <ConfigProvider baseUrl=\"https://api.smooai.dev\" apiKey=\"...\" orgId=\"...\" environment=\"production\">\n * <App />\n * </ConfigProvider>\n * ```\n *\n * All props are optional if the corresponding environment variables are set:\n * SMOOAI_CONFIG_API_URL, SMOOAI_CONFIG_API_KEY, SMOOAI_CONFIG_ORG_ID, SMOOAI_CONFIG_ENV\n */\nexport function ConfigProvider({ children, ...options }: ConfigProviderProps) {\n const client = useMemo(() => new ConfigClient(options), [options.baseUrl, options.apiKey, options.orgId, options.environment]);\n\n return <ConfigContext.Provider value={client}>{children}</ConfigContext.Provider>;\n}\n\n/**\n * Access the ConfigClient instance from the nearest ConfigProvider.\n * Throws if used outside a ConfigProvider.\n */\nexport function useConfigClient(): ConfigClient {\n const client = useContext(ConfigContext);\n if (!client) {\n throw new Error('useConfigClient must be used within a <ConfigProvider>');\n }\n return client;\n}\n"],"mappings":";;;;;AAEA,SAAS,eAAe,YAAY,eAA+B;AAwBxD;AArBJ,IAAM,gBAAgB,cAAmC,IAAI;AAkB7D,SAAS,eAAe,EAAE,UAAU,GAAG,QAAQ,GAAwB;AAC1E,QAAM,SAAS,QAAQ,MAAM,IAAI,aAAa,OAAO,GAAG,CAAC,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,WAAW,CAAC;AAE7H,SAAO,oBAAC,cAAc,UAAd,EAAuB,OAAO,QAAS,UAAS;AAC5D;AAMO,SAAS,kBAAgC;AAC5C,QAAM,SAAS,WAAW,aAAa;AACvC,MAAI,CAAC,QAAQ;AACT,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC5E;AACA,SAAO;AACX;","names":[]}
@@ -1,27 +1,9 @@
1
1
  import {
2
- ConfigClient
3
- } from "./chunk-Z3CZGNU5.mjs";
2
+ useConfigClient
3
+ } from "./chunk-N6Z4AZZA.mjs";
4
4
 
5
5
  // src/react/hooks.ts
6
6
  import { useCallback, useEffect, useState } from "react";
7
-
8
- // src/react/ConfigProvider.tsx
9
- import { createContext, useContext, useMemo } from "react";
10
- import { jsx } from "react/jsx-runtime";
11
- var ConfigContext = createContext(null);
12
- function ConfigProvider({ children, ...options }) {
13
- const client = useMemo(() => new ConfigClient(options), [options.baseUrl, options.apiKey, options.orgId, options.environment]);
14
- return /* @__PURE__ */ jsx(ConfigContext.Provider, { value: client, children });
15
- }
16
- function useConfigClient() {
17
- const client = useContext(ConfigContext);
18
- if (!client) {
19
- throw new Error("useConfigClient must be used within a <ConfigProvider>");
20
- }
21
- return client;
22
- }
23
-
24
- // src/react/hooks.ts
25
7
  function useConfigValue(key, environment) {
26
8
  const client = useConfigClient();
27
9
  const [value, setValue] = useState(void 0);
@@ -64,10 +46,8 @@ function useFeatureFlag(key, environment) {
64
46
  }
65
47
 
66
48
  export {
67
- ConfigProvider,
68
- useConfigClient,
69
49
  usePublicConfig,
70
50
  useSecretConfig,
71
51
  useFeatureFlag
72
52
  };
73
- //# sourceMappingURL=chunk-SKX7CPGS.mjs.map
53
+ //# sourceMappingURL=chunk-VL7AIM2X.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/hooks.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { useConfigClient } from './ConfigProvider';\n\ninterface UseConfigResult<T = unknown> {\n /** The resolved config value, or undefined while loading. */\n value: T | undefined;\n /** True while the initial fetch is in progress. */\n isLoading: boolean;\n /** The error if the fetch failed. */\n error: Error | null;\n /** Re-fetch the value (bypasses cache). */\n refetch: () => void;\n}\n\nfunction useConfigValue(key: string, environment?: string): UseConfigResult {\n const client = useConfigClient();\n const [value, setValue] = useState<unknown>(undefined);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const [fetchCount, setFetchCount] = useState(0);\n\n const refetch = useCallback(() => {\n client.invalidateCache();\n setFetchCount((c) => c + 1);\n }, [client]);\n\n useEffect(() => {\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n client\n .getValue(key, environment)\n .then((result) => {\n if (!cancelled) {\n setValue(result);\n setIsLoading(false);\n }\n })\n .catch((err: unknown) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [client, key, environment, fetchCount]);\n\n return { value, isLoading, error, refetch };\n}\n\n/**\n * Fetch a public config value by key.\n *\n * ```tsx\n * const { value, isLoading, error } = usePublicConfig('API_URL');\n * ```\n */\nexport function usePublicConfig<T = unknown>(key: string, environment?: string): UseConfigResult<T> {\n return useConfigValue(key, environment) as UseConfigResult<T>;\n}\n\n/**\n * Fetch a secret config value by key.\n *\n * ```tsx\n * const { value, isLoading } = useSecretConfig('DATABASE_URL');\n * ```\n */\nexport function useSecretConfig<T = unknown>(key: string, environment?: string): UseConfigResult<T> {\n return useConfigValue(key, environment) as UseConfigResult<T>;\n}\n\n/**\n * Fetch a feature flag value by key.\n *\n * ```tsx\n * const { value: enableNewUI } = useFeatureFlag<boolean>('ENABLE_NEW_UI');\n * ```\n */\nexport function useFeatureFlag<T = unknown>(key: string, environment?: string): UseConfigResult<T> {\n return useConfigValue(key, environment) as UseConfigResult<T>;\n}\n"],"mappings":";;;;;AAEA,SAAS,aAAa,WAAW,gBAAgB;AAcjD,SAAS,eAAe,KAAa,aAAuC;AACxE,QAAM,SAAS,gBAAgB;AAC/B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAkB,MAAS;AACrD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AACrD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,CAAC;AAE9C,QAAM,UAAU,YAAY,MAAM;AAC9B,WAAO,gBAAgB;AACvB,kBAAc,CAAC,MAAM,IAAI,CAAC;AAAA,EAC9B,GAAG,CAAC,MAAM,CAAC;AAEX,YAAU,MAAM;AACZ,QAAI,YAAY;AAChB,iBAAa,IAAI;AACjB,aAAS,IAAI;AAEb,WACK,SAAS,KAAK,WAAW,EACzB,KAAK,CAAC,WAAW;AACd,UAAI,CAAC,WAAW;AACZ,iBAAS,MAAM;AACf,qBAAa,KAAK;AAAA,MACtB;AAAA,IACJ,CAAC,EACA,MAAM,CAAC,QAAiB;AACrB,UAAI,CAAC,WAAW;AACZ,iBAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAC5D,qBAAa,KAAK;AAAA,MACtB;AAAA,IACJ,CAAC;AAEL,WAAO,MAAM;AACT,kBAAY;AAAA,IAChB;AAAA,EACJ,GAAG,CAAC,QAAQ,KAAK,aAAa,UAAU,CAAC;AAEzC,SAAO,EAAE,OAAO,WAAW,OAAO,QAAQ;AAC9C;AASO,SAAS,gBAA6B,KAAa,aAA0C;AAChG,SAAO,eAAe,KAAK,WAAW;AAC1C;AASO,SAAS,gBAA6B,KAAa,aAA0C;AAChG,SAAO,eAAe,KAAK,WAAW;AAC1C;AASO,SAAS,eAA4B,KAAa,aAA0C;AAC/F,SAAO,eAAe,KAAK,WAAW;AAC1C;","names":[]}
@@ -0,0 +1,30 @@
1
+ import {
2
+ ConfigClient
3
+ } from "./chunk-3B5SRHJ2.mjs";
4
+
5
+ // src/vite/preloadConfig.ts
6
+ var preloadPromise = null;
7
+ var preloadedValues = null;
8
+ function preloadConfig(options) {
9
+ if (preloadPromise) return preloadPromise;
10
+ const client = new ConfigClient(options);
11
+ preloadPromise = client.getAllValues(options?.environment).then((values) => {
12
+ preloadedValues = values;
13
+ return values;
14
+ });
15
+ return preloadPromise;
16
+ }
17
+ function getPreloadedConfig() {
18
+ return preloadedValues;
19
+ }
20
+ function resetPreload() {
21
+ preloadPromise = null;
22
+ preloadedValues = null;
23
+ }
24
+
25
+ export {
26
+ preloadConfig,
27
+ getPreloadedConfig,
28
+ resetPreload
29
+ };
30
+ //# sourceMappingURL=chunk-VUYQFQ63.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite/preloadConfig.ts"],"sourcesContent":["import { ConfigClient, type ConfigClientOptions } from '../platform/client';\n\nlet preloadPromise: Promise<Record<string, unknown>> | null = null;\nlet preloadedValues: Record<string, unknown> | null = null;\n\n/**\n * Start fetching all config values as early as possible (before React renders).\n * Call this in your entry file before `createRoot()`:\n *\n * ```ts\n * // main.tsx\n * import { preloadConfig } from '@smooai/config/vite';\n *\n * preloadConfig({ environment: 'production' });\n *\n * // ... later, React tree mounts and hooks read from the already-populated cache\n * ```\n *\n * The returned promise resolves with the fetched config values.\n * Subsequent calls return the same promise (singleton behavior).\n */\nexport function preloadConfig(options?: ConfigClientOptions): Promise<Record<string, unknown>> {\n if (preloadPromise) return preloadPromise;\n\n const client = new ConfigClient(options);\n preloadPromise = client.getAllValues(options?.environment).then((values) => {\n preloadedValues = values;\n return values;\n });\n return preloadPromise;\n}\n\n/**\n * Get the preloaded config values synchronously.\n * Returns `null` if `preloadConfig()` hasn't completed yet.\n */\nexport function getPreloadedConfig(): Record<string, unknown> | null {\n return preloadedValues;\n}\n\n/**\n * Reset preload state (primarily for testing).\n */\nexport function resetPreload(): void {\n preloadPromise = null;\n preloadedValues = null;\n}\n"],"mappings":";;;;;AAEA,IAAI,iBAA0D;AAC9D,IAAI,kBAAkD;AAkB/C,SAAS,cAAc,SAAiE;AAC3F,MAAI,eAAgB,QAAO;AAE3B,QAAM,SAAS,IAAI,aAAa,OAAO;AACvC,mBAAiB,OAAO,aAAa,SAAS,WAAW,EAAE,KAAK,CAAC,WAAW;AACxE,sBAAkB;AAClB,WAAO;AAAA,EACX,CAAC;AACD,SAAO;AACX;AAMO,SAAS,qBAAqD;AACjE,SAAO;AACX;AAKO,SAAS,eAAqB;AACjC,mBAAiB;AACjB,oBAAkB;AACtB;","names":[]}
@@ -0,0 +1,31 @@
1
+ import { ConfigClientOptions } from '../platform/client.mjs';
2
+
3
+ interface GetConfigOptions extends ConfigClientOptions {
4
+ /** Config keys to fetch. If omitted, fetches all values for the environment. */
5
+ keys?: string[];
6
+ /** Additional fetch options passed to the underlying HTTP call (e.g., Next.js `{ next: { revalidate: 60 } }`). */
7
+ fetchOptions?: RequestInit;
8
+ }
9
+ /**
10
+ * Server-side helper for fetching config values in Next.js Server Components or `getServerSideProps`.
11
+ *
12
+ * ```tsx
13
+ * // app/layout.tsx (Server Component)
14
+ * import { getConfig } from '@smooai/config/nextjs';
15
+ *
16
+ * export default async function RootLayout({ children }) {
17
+ * const config = await getConfig({
18
+ * environment: 'production',
19
+ * fetchOptions: { next: { revalidate: 60 } },
20
+ * });
21
+ * return (
22
+ * <SmooConfigProvider initialValues={config}>
23
+ * {children}
24
+ * </SmooConfigProvider>
25
+ * );
26
+ * }
27
+ * ```
28
+ */
29
+ declare function getConfig(options?: GetConfigOptions): Promise<Record<string, unknown>>;
30
+
31
+ export { type GetConfigOptions, getConfig };
@@ -0,0 +1,31 @@
1
+ import { ConfigClientOptions } from '../platform/client.js';
2
+
3
+ interface GetConfigOptions extends ConfigClientOptions {
4
+ /** Config keys to fetch. If omitted, fetches all values for the environment. */
5
+ keys?: string[];
6
+ /** Additional fetch options passed to the underlying HTTP call (e.g., Next.js `{ next: { revalidate: 60 } }`). */
7
+ fetchOptions?: RequestInit;
8
+ }
9
+ /**
10
+ * Server-side helper for fetching config values in Next.js Server Components or `getServerSideProps`.
11
+ *
12
+ * ```tsx
13
+ * // app/layout.tsx (Server Component)
14
+ * import { getConfig } from '@smooai/config/nextjs';
15
+ *
16
+ * export default async function RootLayout({ children }) {
17
+ * const config = await getConfig({
18
+ * environment: 'production',
19
+ * fetchOptions: { next: { revalidate: 60 } },
20
+ * });
21
+ * return (
22
+ * <SmooConfigProvider initialValues={config}>
23
+ * {children}
24
+ * </SmooConfigProvider>
25
+ * );
26
+ * }
27
+ * ```
28
+ */
29
+ declare function getConfig(options?: GetConfigOptions): Promise<Record<string, unknown>>;
30
+
31
+ export { type GetConfigOptions, getConfig };
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/nextjs/getConfig.ts
21
+ var getConfig_exports = {};
22
+ __export(getConfig_exports, {
23
+ getConfig: () => getConfig
24
+ });
25
+ module.exports = __toCommonJS(getConfig_exports);
26
+
27
+ // src/platform/client.ts
28
+ function getEnv(key) {
29
+ if (typeof process !== "undefined" && process.env) {
30
+ return process.env[key];
31
+ }
32
+ return void 0;
33
+ }
34
+ var ConfigClient = class {
35
+ baseUrl;
36
+ orgId;
37
+ apiKey;
38
+ defaultEnvironment;
39
+ cacheTtlMs;
40
+ cache = /* @__PURE__ */ new Map();
41
+ constructor(options = {}) {
42
+ const baseUrl = options.baseUrl ?? getEnv("SMOOAI_CONFIG_API_URL");
43
+ const apiKey = options.apiKey ?? getEnv("SMOOAI_CONFIG_API_KEY");
44
+ const orgId = options.orgId ?? getEnv("SMOOAI_CONFIG_ORG_ID");
45
+ if (!baseUrl) throw new Error("@smooai/config: baseUrl is required (or set SMOOAI_CONFIG_API_URL)");
46
+ if (!apiKey) throw new Error("@smooai/config: apiKey is required (or set SMOOAI_CONFIG_API_KEY)");
47
+ if (!orgId) throw new Error("@smooai/config: orgId is required (or set SMOOAI_CONFIG_ORG_ID)");
48
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
49
+ this.apiKey = apiKey;
50
+ this.orgId = orgId;
51
+ this.defaultEnvironment = options.environment ?? getEnv("SMOOAI_CONFIG_ENV") ?? "development";
52
+ this.cacheTtlMs = options.cacheTtlMs ?? 0;
53
+ }
54
+ computeExpiresAt() {
55
+ return this.cacheTtlMs > 0 ? Date.now() + this.cacheTtlMs : 0;
56
+ }
57
+ getCached(cacheKey) {
58
+ const entry = this.cache.get(cacheKey);
59
+ if (!entry) return void 0;
60
+ if (entry.expiresAt > 0 && Date.now() > entry.expiresAt) {
61
+ this.cache.delete(cacheKey);
62
+ return void 0;
63
+ }
64
+ return entry.value;
65
+ }
66
+ async fetchJson(path, fetchOptions) {
67
+ const response = await fetch(`${this.baseUrl}${path}`, {
68
+ ...fetchOptions,
69
+ headers: { Authorization: `Bearer ${this.apiKey}`, ...fetchOptions?.headers }
70
+ });
71
+ if (!response.ok) {
72
+ throw new Error(`Config API error: HTTP ${response.status} ${response.statusText}`);
73
+ }
74
+ return response.json();
75
+ }
76
+ /**
77
+ * Get a single config value by key.
78
+ * Results are cached locally after the first fetch.
79
+ */
80
+ async getValue(key, environment) {
81
+ const env = environment ?? this.defaultEnvironment;
82
+ const cacheKey = `${env}:${key}`;
83
+ const cached = this.getCached(cacheKey);
84
+ if (cached !== void 0) {
85
+ return cached;
86
+ }
87
+ const result = await this.fetchJson(
88
+ `/organizations/${this.orgId}/config/values/${encodeURIComponent(key)}?environment=${encodeURIComponent(env)}`
89
+ );
90
+ this.cache.set(cacheKey, { value: result.value, expiresAt: this.computeExpiresAt() });
91
+ return result.value;
92
+ }
93
+ /**
94
+ * Get all config values for an environment.
95
+ * All returned values are cached locally.
96
+ * @param environment - Environment name (defaults to constructor option or SMOOAI_CONFIG_ENV)
97
+ * @param fetchOptions - Optional fetch options (e.g., Next.js `{ next: { revalidate: 60 } }`)
98
+ */
99
+ async getAllValues(environment, fetchOptions) {
100
+ const env = environment ?? this.defaultEnvironment;
101
+ const result = await this.fetchJson(
102
+ `/organizations/${this.orgId}/config/values?environment=${encodeURIComponent(env)}`,
103
+ fetchOptions
104
+ );
105
+ const expiresAt = this.computeExpiresAt();
106
+ for (const [key, value] of Object.entries(result.values)) {
107
+ this.cache.set(`${env}:${key}`, { value, expiresAt });
108
+ }
109
+ return result.values;
110
+ }
111
+ /**
112
+ * Pre-populate a single cache entry (e.g., from SSR).
113
+ * Does not make a network request.
114
+ */
115
+ seedCache(key, value, environment) {
116
+ const env = environment ?? this.defaultEnvironment;
117
+ this.cache.set(`${env}:${key}`, { value, expiresAt: this.computeExpiresAt() });
118
+ }
119
+ /**
120
+ * Pre-populate multiple cache entries from a key-value map (e.g., from SSR).
121
+ * Does not make a network request.
122
+ */
123
+ seedCacheFromMap(values, environment) {
124
+ const env = environment ?? this.defaultEnvironment;
125
+ const expiresAt = this.computeExpiresAt();
126
+ for (const [key, value] of Object.entries(values)) {
127
+ this.cache.set(`${env}:${key}`, { value, expiresAt });
128
+ }
129
+ }
130
+ /**
131
+ * Synchronously read a value from the local cache without making a network request.
132
+ * Returns `undefined` if the key is not cached or has expired.
133
+ */
134
+ getCachedValue(key, environment) {
135
+ const env = environment ?? this.defaultEnvironment;
136
+ return this.getCached(`${env}:${key}`);
137
+ }
138
+ /** Clear the entire local cache. */
139
+ invalidateCache() {
140
+ this.cache.clear();
141
+ }
142
+ /** Clear cached values for a specific environment. */
143
+ invalidateCacheForEnvironment(environment) {
144
+ const prefix = `${environment}:`;
145
+ for (const key of this.cache.keys()) {
146
+ if (key.startsWith(prefix)) {
147
+ this.cache.delete(key);
148
+ }
149
+ }
150
+ }
151
+ };
152
+
153
+ // src/nextjs/getConfig.ts
154
+ async function getConfig(options = {}) {
155
+ const { keys, fetchOptions, ...clientOptions } = options;
156
+ const client = new ConfigClient(clientOptions);
157
+ if (keys && keys.length > 0) {
158
+ const result = {};
159
+ await Promise.all(
160
+ keys.map(async (key) => {
161
+ result[key] = await client.getValue(key, clientOptions.environment);
162
+ })
163
+ );
164
+ return result;
165
+ }
166
+ return client.getAllValues(clientOptions.environment, fetchOptions);
167
+ }
168
+ // Annotate the CommonJS export names for ESM import in node:
169
+ 0 && (module.exports = {
170
+ getConfig
171
+ });
172
+ //# sourceMappingURL=getConfig.js.map