@smooai/config 2.0.4 → 2.1.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.
- package/README.md +77 -0
- package/dist/{chunk-Z3CZGNU5.mjs → chunk-3B5SRHJ2.mjs} +36 -5
- package/dist/chunk-3B5SRHJ2.mjs.map +1 -0
- package/dist/chunk-FAF45VZA.mjs +24 -0
- package/dist/chunk-FAF45VZA.mjs.map +1 -0
- package/dist/chunk-GP2JUWOD.mjs +53 -0
- package/dist/chunk-GP2JUWOD.mjs.map +1 -0
- package/dist/chunk-N6Z4AZZA.mjs +26 -0
- package/dist/chunk-N6Z4AZZA.mjs.map +1 -0
- package/dist/{chunk-SKX7CPGS.mjs → chunk-VL7AIM2X.mjs} +3 -23
- package/dist/chunk-VL7AIM2X.mjs.map +1 -0
- package/dist/chunk-VUYQFQ63.mjs +30 -0
- package/dist/chunk-VUYQFQ63.mjs.map +1 -0
- package/dist/nextjs/getConfig.d.mts +31 -0
- package/dist/nextjs/getConfig.d.ts +31 -0
- package/dist/nextjs/getConfig.js +172 -0
- package/dist/nextjs/getConfig.js.map +1 -0
- package/dist/nextjs/getConfig.mjs +9 -0
- package/dist/nextjs/getConfig.mjs.map +1 -0
- package/dist/nextjs/hooks.d.mts +22 -0
- package/dist/nextjs/hooks.d.ts +22 -0
- package/dist/nextjs/hooks.js +89 -0
- package/dist/nextjs/hooks.js.map +1 -0
- package/dist/nextjs/hooks.mjs +13 -0
- package/dist/nextjs/hooks.mjs.map +1 -0
- package/dist/nextjs/index.d.mts +28 -0
- package/dist/nextjs/index.d.ts +28 -0
- package/dist/nextjs/index.js +255 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/nextjs/index.mjs +40 -0
- package/dist/nextjs/index.mjs.map +1 -0
- package/dist/platform/client.d.mts +18 -1
- package/dist/platform/client.d.ts +18 -1
- package/dist/platform/client.js +35 -4
- package/dist/platform/client.js.map +1 -1
- package/dist/platform/client.mjs +1 -1
- package/dist/react/hooks.js.map +1 -1
- package/dist/react/hooks.mjs +3 -2
- package/dist/react/index.d.mts +4 -2
- package/dist/react/index.d.ts +4 -2
- package/dist/react/index.js +37 -4
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +8 -4
- package/dist/vite/index.d.mts +6 -0
- package/dist/vite/index.d.ts +6 -0
- package/dist/vite/index.js +249 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/index.mjs +27 -0
- package/dist/vite/index.mjs.map +1 -0
- package/dist/vite/preloadConfig.d.mts +30 -0
- package/dist/vite/preloadConfig.d.ts +30 -0
- package/dist/vite/preloadConfig.js +180 -0
- package/dist/vite/preloadConfig.js.map +1 -0
- package/dist/vite/preloadConfig.mjs +13 -0
- package/dist/vite/preloadConfig.mjs.map +1 -0
- package/package.json +6 -2
- package/dist/chunk-SKX7CPGS.mjs.map +0 -1
- 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
|
-
|
|
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-
|
|
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
|
-
|
|
3
|
-
} from "./chunk-
|
|
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-
|
|
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
|