@smplkit/sdk 1.1.1 → 1.1.3

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 CHANGED
@@ -1,32 +1,51 @@
1
- # smplkit
1
+ # smplkit TypeScript SDK
2
2
 
3
- Official TypeScript SDK for the [smplkit](https://docs.smplkit.com) platform.
3
+ The official TypeScript SDK for [smplkit](https://www.smplkit.com) — simple application infrastructure that just works.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @smplkit/sdk
8
+ npm install smplkit-sdk
9
9
  ```
10
10
 
11
+ ## Requirements
12
+
13
+ - Node.js 18+
14
+
11
15
  ## Quick Start
12
16
 
13
17
  ```typescript
14
- import { SmplkitClient } from "@smplkit/sdk";
18
+ import { SmplClient } from "smplkit-sdk";
19
+
20
+ // Option 1: Explicit API key
21
+ const client = new SmplClient({ apiKey: "sk_api_..." });
22
+
23
+ // Option 2: Environment variable (SMPLKIT_API_KEY)
24
+ // export SMPLKIT_API_KEY=sk_api_...
25
+ const client2 = new SmplClient();
26
+
27
+ // Option 3: Configuration file (~/.smplkit)
28
+ // [default]
29
+ // api_key = "sk_api_..."
30
+ const client3 = new SmplClient();
31
+ ```
32
+
33
+ ```typescript
34
+ import { SmplClient } from "smplkit-sdk";
15
35
 
16
- const client = new SmplkitClient({ apiKey: "sk_api_..." });
36
+ const client = new SmplClient({ apiKey: "sk_api_..." });
17
37
 
18
- // Fetch a config by key
19
- const config = await client.config.get({ key: "user_service" });
20
- console.log(config.values);
38
+ // Get a config by key
39
+ const config = await client.config.getByKey("user_service");
21
40
 
22
41
  // List all configs
23
42
  const configs = await client.config.list();
24
43
 
25
44
  // Create a config
26
45
  const newConfig = await client.config.create({
27
- name: "Payment Service",
28
- key: "payment_service",
29
- description: "Configuration for the payment service",
46
+ name: "My Service",
47
+ key: "my_service",
48
+ description: "Configuration for my service",
30
49
  values: { timeout: 30, retries: 3 },
31
50
  });
32
51
 
@@ -36,10 +55,23 @@ await client.config.delete(newConfig.id);
36
55
 
37
56
  ## Configuration
38
57
 
58
+ The API key is resolved using the following priority:
59
+
60
+ 1. **Explicit argument:** Pass `apiKey` in the constructor options.
61
+ 2. **Environment variable:** Set `SMPLKIT_API_KEY`.
62
+ 3. **Configuration file:** Add `api_key` under `[default]` in `~/.smplkit` (TOML format):
63
+
64
+ ```toml
65
+ [default]
66
+ api_key = "sk_api_..."
67
+ ```
68
+
69
+ If none of these are set, the SDK throws `SmplError` with a message listing all three methods.
70
+
39
71
  ```typescript
40
- const client = new SmplkitClient({
41
- apiKey: "sk_api_...", // Required
42
- timeout: 30000, // Optional, in milliseconds (default: 30000)
72
+ const client = new SmplClient({
73
+ apiKey: "sk_api_...",
74
+ timeout: 30_000, // default (ms)
43
75
  });
44
76
  ```
45
77
 
@@ -48,35 +80,33 @@ const client = new SmplkitClient({
48
80
  All SDK errors extend `SmplError`:
49
81
 
50
82
  ```typescript
51
- import {
52
- SmplError,
53
- SmplNotFoundError,
54
- SmplConflictError,
55
- SmplValidationError,
56
- SmplConnectionError,
57
- SmplTimeoutError,
58
- } from "@smplkit/sdk";
83
+ import { SmplError, SmplNotFoundError } from "smplkit-sdk";
59
84
 
60
85
  try {
61
- const config = await client.config.get({ key: "nonexistent" });
62
- } catch (error) {
63
- if (error instanceof SmplNotFoundError) {
64
- console.log("Config not found");
65
- } else if (error instanceof SmplValidationError) {
66
- console.log("Invalid request:", error.message);
67
- } else if (error instanceof SmplConnectionError) {
68
- console.log("Network error:", error.message);
69
- } else if (error instanceof SmplTimeoutError) {
70
- console.log("Request timed out");
71
- } else if (error instanceof SmplError) {
72
- console.log("SDK error:", error.statusCode, error.message);
86
+ const config = await client.config.getByKey("nonexistent");
87
+ } catch (err) {
88
+ if (err instanceof SmplNotFoundError) {
89
+ console.log("Not found:", err.message);
90
+ } else if (err instanceof SmplError) {
91
+ console.log("SDK error:", err.statusCode, err.responseBody);
73
92
  }
74
93
  }
75
94
  ```
76
95
 
96
+ | Error | Cause |
97
+ |------------------------|------------------------------|
98
+ | `SmplNotFoundError` | HTTP 404 — resource not found |
99
+ | `SmplConflictError` | HTTP 409 — conflict |
100
+ | `SmplValidationError` | HTTP 422 — validation error |
101
+ | `SmplTimeoutError` | Request timed out |
102
+ | `SmplConnectionError` | Network connectivity issue |
103
+ | `SmplError` | Any other SDK error |
104
+
77
105
  ## Documentation
78
106
 
79
- Full documentation is available at [docs.smplkit.com](https://docs.smplkit.com).
107
+ - [Getting Started](https://docs.smplkit.com/getting-started)
108
+ - [TypeScript SDK Guide](https://docs.smplkit.com/sdks/typescript)
109
+ - [API Reference](https://docs.smplkit.com/api)
80
110
 
81
111
  ## License
82
112
 
@@ -80,7 +80,7 @@ var ConfigRuntime = class {
80
80
  /**
81
81
  * Return the value as a number, or `defaultValue` if absent or not a number.
82
82
  */
83
- getNumber(key, defaultValue = null) {
83
+ getInt(key, defaultValue = null) {
84
84
  const value = this._cache[key];
85
85
  return typeof value === "number" ? value : defaultValue;
86
86
  }
@@ -314,4 +314,4 @@ var ConfigRuntime = class {
314
314
  export {
315
315
  ConfigRuntime
316
316
  };
317
- //# sourceMappingURL=chunk-PZD5PSQY.js.map
317
+ //# sourceMappingURL=chunk-GLOLTIGH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/runtime.ts","../src/config/resolve.ts"],"sourcesContent":["/**\n * ConfigRuntime — runtime-plane value resolution with WebSocket updates.\n *\n * Holds a fully resolved local cache of config values for a specific\n * environment. All value-access methods are synchronous (local reads);\n * only {@link refresh} and {@link close} are async.\n *\n * A background WebSocket connection is maintained for real-time updates.\n * If the WebSocket fails, the runtime operates in cache-only mode and\n * reconnects automatically with exponential backoff.\n */\n\nimport WebSocket from \"ws\";\nimport { resolveChain } from \"./resolve.js\";\nimport type { ChainConfig } from \"./resolve.js\";\nimport type { ConfigChangeEvent, ConfigStats, ConnectionStatus } from \"./runtime-types.js\";\n\n/** @internal */\ninterface ChangeListener {\n callback: (event: ConfigChangeEvent) => void;\n key: string | null;\n}\n\n/** @internal */\ninterface WsConfigChangedMessage {\n type: \"config_changed\";\n config_id: string;\n changes: Array<{\n key: string;\n old_value: unknown;\n new_value: unknown;\n }>;\n}\n\n/** @internal */\ninterface WsConfigDeletedMessage {\n type: \"config_deleted\";\n config_id: string;\n}\n\ntype WsMessage =\n | { type: \"subscribed\"; config_id: string; environment: string }\n | { type: \"error\"; message: string }\n | WsConfigChangedMessage\n | WsConfigDeletedMessage;\n\n/** @internal */\nconst BACKOFF_MS = [1000, 2000, 4000, 8000, 16000, 32000, 60000];\n\n/** @internal Options for constructing a ConfigRuntime. */\nexport interface ConfigRuntimeOptions {\n configKey: string;\n configId: string;\n environment: string;\n chain: ChainConfig[];\n apiKey: string;\n baseUrl: string;\n fetchChain: (() => Promise<ChainConfig[]>) | null;\n}\n\n/**\n * Runtime configuration handle for a specific environment.\n *\n * Obtained by calling {@link Config.connect}. All value-access methods\n * are synchronous and served entirely from a local in-process cache.\n * The cache is populated eagerly on construction and kept current via\n * a background WebSocket connection.\n */\nexport class ConfigRuntime {\n private _cache: Record<string, unknown>;\n private _chain: ChainConfig[];\n private _fetchCount: number;\n private _lastFetchAt: string | null;\n private _closed = false;\n private _wsStatus: ConnectionStatus = \"disconnected\";\n private _ws: InstanceType<typeof WebSocket> | null = null;\n private _reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private _backoffIndex = 0;\n private _listeners: ChangeListener[] = [];\n\n private readonly _configId: string;\n private readonly _environment: string;\n private readonly _apiKey: string;\n private readonly _baseUrl: string;\n private readonly _fetchChain: (() => Promise<ChainConfig[]>) | null;\n\n /** @internal */\n constructor(options: ConfigRuntimeOptions) {\n this._configId = options.configId;\n this._environment = options.environment;\n this._apiKey = options.apiKey;\n this._baseUrl = options.baseUrl;\n this._fetchChain = options.fetchChain;\n this._chain = options.chain;\n this._cache = resolveChain(options.chain, options.environment);\n this._fetchCount = options.chain.length;\n this._lastFetchAt = new Date().toISOString();\n\n // Start WebSocket in background — non-blocking\n this._connectWebSocket();\n }\n\n // ---- Value access (synchronous, local cache) ----\n\n /**\n * Return the resolved value for `key`, or `defaultValue` if absent.\n *\n * @param key - The config key to look up.\n * @param defaultValue - Returned when the key is not present (default: null).\n */\n get(key: string, defaultValue: unknown = null): unknown {\n return key in this._cache ? this._cache[key] : defaultValue;\n }\n\n /**\n * Return the value as a string, or `defaultValue` if absent or not a string.\n */\n getString(key: string, defaultValue: string | null = null): string | null {\n const value = this._cache[key];\n return typeof value === \"string\" ? value : defaultValue;\n }\n\n /**\n * Return the value as a number, or `defaultValue` if absent or not a number.\n */\n getInt(key: string, defaultValue: number | null = null): number | null {\n const value = this._cache[key];\n return typeof value === \"number\" ? value : defaultValue;\n }\n\n /**\n * Return the value as a boolean, or `defaultValue` if absent or not a boolean.\n */\n getBool(key: string, defaultValue: boolean | null = null): boolean | null {\n const value = this._cache[key];\n return typeof value === \"boolean\" ? value : defaultValue;\n }\n\n /**\n * Return whether `key` is present in the resolved configuration.\n */\n exists(key: string): boolean {\n return key in this._cache;\n }\n\n /**\n * Return a shallow copy of the full resolved configuration.\n */\n getAll(): Record<string, unknown> {\n return { ...this._cache };\n }\n\n // ---- Change listeners ----\n\n /**\n * Register a listener that fires when a config value changes.\n *\n * @param callback - Called with a {@link ConfigChangeEvent} on each change.\n * @param options.key - If provided, the listener fires only for this key.\n * If omitted, the listener fires for all changes.\n */\n onChange(callback: (event: ConfigChangeEvent) => void, options?: { key?: string }): void {\n this._listeners.push({\n callback,\n key: options?.key ?? null,\n });\n }\n\n // ---- Diagnostics ----\n\n /**\n * Return diagnostic statistics for this runtime.\n */\n stats(): ConfigStats {\n return {\n fetchCount: this._fetchCount,\n lastFetchAt: this._lastFetchAt,\n };\n }\n\n /**\n * Return the current WebSocket connection status.\n */\n connectionStatus(): ConnectionStatus {\n return this._wsStatus;\n }\n\n // ---- Lifecycle ----\n\n /**\n * Force a manual refresh of the cached configuration.\n *\n * Re-fetches the full config chain via HTTP, re-resolves values, updates\n * the local cache, and fires listeners for any detected changes.\n *\n * @throws {Error} If no `fetchChain` function was provided on construction.\n */\n async refresh(): Promise<void> {\n if (!this._fetchChain) {\n throw new Error(\"No fetchChain function provided; cannot refresh.\");\n }\n\n const newChain = await this._fetchChain();\n const oldCache = this._cache;\n\n this._chain = newChain;\n this._cache = resolveChain(newChain, this._environment);\n this._fetchCount += newChain.length;\n this._lastFetchAt = new Date().toISOString();\n\n this._diffAndFire(oldCache, this._cache, \"manual\");\n }\n\n /**\n * Close the runtime connection.\n *\n * Shuts down the WebSocket and cancels any pending reconnect timer.\n * Safe to call multiple times.\n */\n async close(): Promise<void> {\n this._closed = true;\n this._wsStatus = \"disconnected\";\n\n if (this._reconnectTimer !== null) {\n clearTimeout(this._reconnectTimer);\n this._reconnectTimer = null;\n }\n\n if (this._ws !== null) {\n this._ws.close();\n this._ws = null;\n }\n }\n\n /**\n * Async dispose support for `await using` (TypeScript 5.2+).\n */\n async [Symbol.asyncDispose](): Promise<void> {\n await this.close();\n }\n\n // ---- WebSocket internals ----\n\n private _buildWsUrl(): string {\n let url = this._baseUrl;\n if (url.startsWith(\"https://\")) {\n url = \"wss://\" + url.slice(\"https://\".length);\n } else if (url.startsWith(\"http://\")) {\n url = \"ws://\" + url.slice(\"http://\".length);\n } else {\n url = \"wss://\" + url;\n }\n url = url.replace(/\\/$/, \"\");\n return `${url}/api/ws/v1/configs?api_key=${this._apiKey}`;\n }\n\n private _connectWebSocket(): void {\n if (this._closed) return;\n\n this._wsStatus = \"connecting\";\n const wsUrl = this._buildWsUrl();\n\n try {\n const ws = new WebSocket(wsUrl);\n this._ws = ws;\n\n ws.on(\"open\", () => {\n if (this._closed) {\n ws.close();\n return;\n }\n this._backoffIndex = 0;\n this._wsStatus = \"connected\";\n ws.send(\n JSON.stringify({\n type: \"subscribe\",\n config_id: this._configId,\n environment: this._environment,\n }),\n );\n });\n\n ws.on(\"message\", (data: WebSocket.RawData) => {\n try {\n const msg = JSON.parse(String(data)) as WsMessage;\n this._handleMessage(msg);\n } catch {\n // ignore unparseable messages\n }\n });\n\n ws.on(\"close\", () => {\n if (!this._closed) {\n this._wsStatus = \"disconnected\";\n this._scheduleReconnect();\n }\n });\n\n ws.on(\"error\", () => {\n // 'close' will fire after 'error'; reconnect is handled there\n });\n } catch {\n if (!this._closed) {\n this._scheduleReconnect();\n }\n }\n }\n\n private _scheduleReconnect(): void {\n if (this._closed) return;\n\n const delay = BACKOFF_MS[Math.min(this._backoffIndex, BACKOFF_MS.length - 1)];\n this._backoffIndex++;\n this._wsStatus = \"connecting\";\n\n this._reconnectTimer = setTimeout(() => {\n this._reconnectTimer = null;\n // On reconnect, resync the cache to pick up changes missed while offline\n if (this._fetchChain) {\n this._fetchChain()\n .then((newChain) => {\n const oldCache = this._cache;\n this._chain = newChain;\n this._cache = resolveChain(newChain, this._environment);\n this._fetchCount += newChain.length;\n this._lastFetchAt = new Date().toISOString();\n this._diffAndFire(oldCache, this._cache, \"manual\");\n })\n .catch(() => {\n // ignore fetch errors during reconnect\n })\n .finally(() => {\n this._connectWebSocket();\n });\n } else {\n this._connectWebSocket();\n }\n }, delay);\n }\n\n private _handleMessage(msg: WsMessage): void {\n if (msg.type === \"config_changed\") {\n this._applyChanges(msg.config_id, msg.changes);\n } else if (msg.type === \"config_deleted\") {\n this._closed = true;\n void this.close();\n }\n }\n\n private _applyChanges(\n configId: string,\n changes: Array<{ key: string; old_value: unknown; new_value: unknown }>,\n ): void {\n const chainEntry = this._chain.find((c) => c.id === configId);\n if (!chainEntry) return;\n\n for (const change of changes) {\n const { key, new_value } = change;\n\n // Get or create the environment entry\n const envEntry =\n chainEntry.environments[this._environment] !== undefined &&\n chainEntry.environments[this._environment] !== null\n ? (chainEntry.environments[this._environment] as Record<string, unknown>)\n : null;\n const envValues =\n envEntry !== null && typeof envEntry === \"object\"\n ? ((envEntry.values ?? {}) as Record<string, unknown>)\n : null;\n\n if (new_value === null || new_value === undefined) {\n // Deletion: remove from base values and env values\n delete chainEntry.values[key];\n if (envValues) delete envValues[key];\n } else if (envValues && key in envValues) {\n // Update existing env-specific override\n envValues[key] = new_value;\n } else if (key in chainEntry.values) {\n // Update existing base value\n chainEntry.values[key] = new_value;\n } else {\n // New key — put in base values\n chainEntry.values[key] = new_value;\n }\n }\n\n const oldCache = this._cache;\n this._cache = resolveChain(this._chain, this._environment);\n this._diffAndFire(oldCache, this._cache, \"websocket\");\n }\n\n private _diffAndFire(\n oldCache: Record<string, unknown>,\n newCache: Record<string, unknown>,\n source: \"websocket\" | \"poll\" | \"manual\",\n ): void {\n const allKeys = new Set([...Object.keys(oldCache), ...Object.keys(newCache)]);\n\n for (const key of allKeys) {\n const oldVal = key in oldCache ? oldCache[key] : null;\n const newVal = key in newCache ? newCache[key] : null;\n\n if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {\n const event: ConfigChangeEvent = { key, oldValue: oldVal, newValue: newVal, source };\n this._fireListeners(event);\n }\n }\n }\n\n private _fireListeners(event: ConfigChangeEvent): void {\n for (const listener of this._listeners) {\n if (listener.key === null || listener.key === event.key) {\n try {\n listener.callback(event);\n } catch {\n // ignore listener errors to prevent one bad listener from stopping others\n }\n }\n }\n }\n}\n","/**\n * Deep-merge resolution algorithm for config inheritance chains.\n *\n * Mirrors the Python SDK's `_resolver.py` (ADR-024 §2.5–2.6).\n */\n\n/** A single entry in a config inheritance chain (child-to-root ordering). */\nexport interface ChainConfig {\n /** Config UUID. */\n id: string;\n /** Base key-value pairs. */\n values: Record<string, unknown>;\n /**\n * Per-environment overrides.\n * Each entry is `{ values: { key: value, ... } }` — the server wraps\n * environment-specific values in a nested `values` key.\n */\n environments: Record<string, unknown>;\n}\n\n/**\n * Recursively merge two dicts, with `override` taking precedence.\n *\n * Nested dicts are merged recursively. Non-dict values (strings, numbers,\n * booleans, arrays, null) are replaced wholesale.\n */\nexport function deepMerge(\n base: Record<string, unknown>,\n override: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...base };\n for (const [key, value] of Object.entries(override)) {\n if (\n key in result &&\n typeof result[key] === \"object\" &&\n result[key] !== null &&\n !Array.isArray(result[key]) &&\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value)\n ) {\n result[key] = deepMerge(\n result[key] as Record<string, unknown>,\n value as Record<string, unknown>,\n );\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Resolve the full configuration for an environment given a config chain.\n *\n * Walks from root (last element) to child (first element), accumulating\n * values via deep merge so that child configs override parent configs.\n *\n * For each config in the chain, base `values` are merged with\n * environment-specific values (env wins), then that result is merged\n * on top of the accumulated parent result (child wins over parent).\n *\n * @param chain - Ordered list of config data from child (index 0) to root ancestor (last).\n * @param environment - The environment key to resolve for.\n */\nexport function resolveChain(chain: ChainConfig[], environment: string): Record<string, unknown> {\n let accumulated: Record<string, unknown> = {};\n\n // Walk from root to child (reverse order — chain is child-to-root)\n for (let i = chain.length - 1; i >= 0; i--) {\n const config = chain[i];\n const baseValues: Record<string, unknown> = config.values ?? {};\n\n // Environments are stored as { env_name: { values: { key: val } } }\n const envEntry = (config.environments ?? {})[environment];\n const envValues: Record<string, unknown> =\n envEntry !== null &&\n envEntry !== undefined &&\n typeof envEntry === \"object\" &&\n !Array.isArray(envEntry)\n ? (((envEntry as Record<string, unknown>).values ?? {}) as Record<string, unknown>)\n : {};\n\n // Merge environment overrides on top of base values (env wins)\n const configResolved = deepMerge(baseValues, envValues);\n\n // Merge this config's resolved values on top of accumulated parent values (child wins)\n accumulated = deepMerge(accumulated, configResolved);\n }\n\n return accumulated;\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -124,7 +124,7 @@ var init_runtime = __esm({
124
124
  /**
125
125
  * Return the value as a number, or `defaultValue` if absent or not a number.
126
126
  */
127
- getNumber(key, defaultValue = null) {
127
+ getInt(key, defaultValue = null) {
128
128
  const value = this._cache[key];
129
129
  return typeof value === "number" ? value : defaultValue;
130
130
  }
@@ -363,13 +363,13 @@ __export(index_exports, {
363
363
  Config: () => Config,
364
364
  ConfigClient: () => ConfigClient,
365
365
  ConfigRuntime: () => ConfigRuntime,
366
+ SmplClient: () => SmplClient,
366
367
  SmplConflictError: () => SmplConflictError,
367
368
  SmplConnectionError: () => SmplConnectionError,
368
369
  SmplError: () => SmplError,
369
370
  SmplNotFoundError: () => SmplNotFoundError,
370
371
  SmplTimeoutError: () => SmplTimeoutError,
371
- SmplValidationError: () => SmplValidationError,
372
- SmplkitClient: () => SmplkitClient
372
+ SmplValidationError: () => SmplValidationError
373
373
  });
374
374
  module.exports = __toCommonJS(index_exports);
375
375
 
@@ -844,15 +844,32 @@ var ConfigClient = class {
844
844
  }
845
845
  };
846
846
 
847
+ // src/resolve.ts
848
+ var import_node_fs = require("fs");
849
+ var import_node_os = require("os");
850
+ var import_node_path = require("path");
851
+ var NO_API_KEY_MESSAGE = "No API key provided. Set one of:\n 1. Pass apiKey to the constructor\n 2. Set the SMPLKIT_API_KEY environment variable\n 3. Add api_key to [default] in ~/.smplkit";
852
+ function resolveApiKey(explicit) {
853
+ if (explicit) return explicit;
854
+ const envVal = process.env.SMPLKIT_API_KEY;
855
+ if (envVal) return envVal;
856
+ const configPath = (0, import_node_path.join)((0, import_node_os.homedir)(), ".smplkit");
857
+ try {
858
+ const content = (0, import_node_fs.readFileSync)(configPath, "utf-8");
859
+ const match = content.match(/\[default\]\s*[\s\S]*?api_key\s*=\s*"([^"]+)"/);
860
+ if (match?.[1]) return match[1];
861
+ } catch {
862
+ }
863
+ throw new SmplError(NO_API_KEY_MESSAGE);
864
+ }
865
+
847
866
  // src/client.ts
848
- var SmplkitClient = class {
867
+ var SmplClient = class {
849
868
  /** Client for config management-plane operations. */
850
869
  config;
851
- constructor(options) {
852
- if (!options.apiKey) {
853
- throw new Error("apiKey is required");
854
- }
855
- this.config = new ConfigClient(options.apiKey, options.timeout);
870
+ constructor(options = {}) {
871
+ const apiKey = resolveApiKey(options.apiKey);
872
+ this.config = new ConfigClient(apiKey, options.timeout);
856
873
  }
857
874
  };
858
875
 
@@ -863,12 +880,12 @@ init_runtime();
863
880
  Config,
864
881
  ConfigClient,
865
882
  ConfigRuntime,
883
+ SmplClient,
866
884
  SmplConflictError,
867
885
  SmplConnectionError,
868
886
  SmplError,
869
887
  SmplNotFoundError,
870
888
  SmplTimeoutError,
871
- SmplValidationError,
872
- SmplkitClient
889
+ SmplValidationError
873
890
  });
874
891
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config/resolve.ts","../src/config/runtime.ts","../src/index.ts","../src/config/client.ts","../src/errors.ts","../src/config/types.ts","../src/client.ts"],"sourcesContent":["/**\n * Deep-merge resolution algorithm for config inheritance chains.\n *\n * Mirrors the Python SDK's `_resolver.py` (ADR-024 §2.5–2.6).\n */\n\n/** A single entry in a config inheritance chain (child-to-root ordering). */\nexport interface ChainConfig {\n /** Config UUID. */\n id: string;\n /** Base key-value pairs. */\n values: Record<string, unknown>;\n /**\n * Per-environment overrides.\n * Each entry is `{ values: { key: value, ... } }` — the server wraps\n * environment-specific values in a nested `values` key.\n */\n environments: Record<string, unknown>;\n}\n\n/**\n * Recursively merge two dicts, with `override` taking precedence.\n *\n * Nested dicts are merged recursively. Non-dict values (strings, numbers,\n * booleans, arrays, null) are replaced wholesale.\n */\nexport function deepMerge(\n base: Record<string, unknown>,\n override: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...base };\n for (const [key, value] of Object.entries(override)) {\n if (\n key in result &&\n typeof result[key] === \"object\" &&\n result[key] !== null &&\n !Array.isArray(result[key]) &&\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value)\n ) {\n result[key] = deepMerge(\n result[key] as Record<string, unknown>,\n value as Record<string, unknown>,\n );\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Resolve the full configuration for an environment given a config chain.\n *\n * Walks from root (last element) to child (first element), accumulating\n * values via deep merge so that child configs override parent configs.\n *\n * For each config in the chain, base `values` are merged with\n * environment-specific values (env wins), then that result is merged\n * on top of the accumulated parent result (child wins over parent).\n *\n * @param chain - Ordered list of config data from child (index 0) to root ancestor (last).\n * @param environment - The environment key to resolve for.\n */\nexport function resolveChain(chain: ChainConfig[], environment: string): Record<string, unknown> {\n let accumulated: Record<string, unknown> = {};\n\n // Walk from root to child (reverse order — chain is child-to-root)\n for (let i = chain.length - 1; i >= 0; i--) {\n const config = chain[i];\n const baseValues: Record<string, unknown> = config.values ?? {};\n\n // Environments are stored as { env_name: { values: { key: val } } }\n const envEntry = (config.environments ?? {})[environment];\n const envValues: Record<string, unknown> =\n envEntry !== null &&\n envEntry !== undefined &&\n typeof envEntry === \"object\" &&\n !Array.isArray(envEntry)\n ? (((envEntry as Record<string, unknown>).values ?? {}) as Record<string, unknown>)\n : {};\n\n // Merge environment overrides on top of base values (env wins)\n const configResolved = deepMerge(baseValues, envValues);\n\n // Merge this config's resolved values on top of accumulated parent values (child wins)\n accumulated = deepMerge(accumulated, configResolved);\n }\n\n return accumulated;\n}\n","/**\n * ConfigRuntime — runtime-plane value resolution with WebSocket updates.\n *\n * Holds a fully resolved local cache of config values for a specific\n * environment. All value-access methods are synchronous (local reads);\n * only {@link refresh} and {@link close} are async.\n *\n * A background WebSocket connection is maintained for real-time updates.\n * If the WebSocket fails, the runtime operates in cache-only mode and\n * reconnects automatically with exponential backoff.\n */\n\nimport WebSocket from \"ws\";\nimport { resolveChain } from \"./resolve.js\";\nimport type { ChainConfig } from \"./resolve.js\";\nimport type { ConfigChangeEvent, ConfigStats, ConnectionStatus } from \"./runtime-types.js\";\n\n/** @internal */\ninterface ChangeListener {\n callback: (event: ConfigChangeEvent) => void;\n key: string | null;\n}\n\n/** @internal */\ninterface WsConfigChangedMessage {\n type: \"config_changed\";\n config_id: string;\n changes: Array<{\n key: string;\n old_value: unknown;\n new_value: unknown;\n }>;\n}\n\n/** @internal */\ninterface WsConfigDeletedMessage {\n type: \"config_deleted\";\n config_id: string;\n}\n\ntype WsMessage =\n | { type: \"subscribed\"; config_id: string; environment: string }\n | { type: \"error\"; message: string }\n | WsConfigChangedMessage\n | WsConfigDeletedMessage;\n\n/** @internal */\nconst BACKOFF_MS = [1000, 2000, 4000, 8000, 16000, 32000, 60000];\n\n/** @internal Options for constructing a ConfigRuntime. */\nexport interface ConfigRuntimeOptions {\n configKey: string;\n configId: string;\n environment: string;\n chain: ChainConfig[];\n apiKey: string;\n baseUrl: string;\n fetchChain: (() => Promise<ChainConfig[]>) | null;\n}\n\n/**\n * Runtime configuration handle for a specific environment.\n *\n * Obtained by calling {@link Config.connect}. All value-access methods\n * are synchronous and served entirely from a local in-process cache.\n * The cache is populated eagerly on construction and kept current via\n * a background WebSocket connection.\n */\nexport class ConfigRuntime {\n private _cache: Record<string, unknown>;\n private _chain: ChainConfig[];\n private _fetchCount: number;\n private _lastFetchAt: string | null;\n private _closed = false;\n private _wsStatus: ConnectionStatus = \"disconnected\";\n private _ws: InstanceType<typeof WebSocket> | null = null;\n private _reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private _backoffIndex = 0;\n private _listeners: ChangeListener[] = [];\n\n private readonly _configId: string;\n private readonly _environment: string;\n private readonly _apiKey: string;\n private readonly _baseUrl: string;\n private readonly _fetchChain: (() => Promise<ChainConfig[]>) | null;\n\n /** @internal */\n constructor(options: ConfigRuntimeOptions) {\n this._configId = options.configId;\n this._environment = options.environment;\n this._apiKey = options.apiKey;\n this._baseUrl = options.baseUrl;\n this._fetchChain = options.fetchChain;\n this._chain = options.chain;\n this._cache = resolveChain(options.chain, options.environment);\n this._fetchCount = options.chain.length;\n this._lastFetchAt = new Date().toISOString();\n\n // Start WebSocket in background — non-blocking\n this._connectWebSocket();\n }\n\n // ---- Value access (synchronous, local cache) ----\n\n /**\n * Return the resolved value for `key`, or `defaultValue` if absent.\n *\n * @param key - The config key to look up.\n * @param defaultValue - Returned when the key is not present (default: null).\n */\n get(key: string, defaultValue: unknown = null): unknown {\n return key in this._cache ? this._cache[key] : defaultValue;\n }\n\n /**\n * Return the value as a string, or `defaultValue` if absent or not a string.\n */\n getString(key: string, defaultValue: string | null = null): string | null {\n const value = this._cache[key];\n return typeof value === \"string\" ? value : defaultValue;\n }\n\n /**\n * Return the value as a number, or `defaultValue` if absent or not a number.\n */\n getNumber(key: string, defaultValue: number | null = null): number | null {\n const value = this._cache[key];\n return typeof value === \"number\" ? value : defaultValue;\n }\n\n /**\n * Return the value as a boolean, or `defaultValue` if absent or not a boolean.\n */\n getBool(key: string, defaultValue: boolean | null = null): boolean | null {\n const value = this._cache[key];\n return typeof value === \"boolean\" ? value : defaultValue;\n }\n\n /**\n * Return whether `key` is present in the resolved configuration.\n */\n exists(key: string): boolean {\n return key in this._cache;\n }\n\n /**\n * Return a shallow copy of the full resolved configuration.\n */\n getAll(): Record<string, unknown> {\n return { ...this._cache };\n }\n\n // ---- Change listeners ----\n\n /**\n * Register a listener that fires when a config value changes.\n *\n * @param callback - Called with a {@link ConfigChangeEvent} on each change.\n * @param options.key - If provided, the listener fires only for this key.\n * If omitted, the listener fires for all changes.\n */\n onChange(callback: (event: ConfigChangeEvent) => void, options?: { key?: string }): void {\n this._listeners.push({\n callback,\n key: options?.key ?? null,\n });\n }\n\n // ---- Diagnostics ----\n\n /**\n * Return diagnostic statistics for this runtime.\n */\n stats(): ConfigStats {\n return {\n fetchCount: this._fetchCount,\n lastFetchAt: this._lastFetchAt,\n };\n }\n\n /**\n * Return the current WebSocket connection status.\n */\n connectionStatus(): ConnectionStatus {\n return this._wsStatus;\n }\n\n // ---- Lifecycle ----\n\n /**\n * Force a manual refresh of the cached configuration.\n *\n * Re-fetches the full config chain via HTTP, re-resolves values, updates\n * the local cache, and fires listeners for any detected changes.\n *\n * @throws {Error} If no `fetchChain` function was provided on construction.\n */\n async refresh(): Promise<void> {\n if (!this._fetchChain) {\n throw new Error(\"No fetchChain function provided; cannot refresh.\");\n }\n\n const newChain = await this._fetchChain();\n const oldCache = this._cache;\n\n this._chain = newChain;\n this._cache = resolveChain(newChain, this._environment);\n this._fetchCount += newChain.length;\n this._lastFetchAt = new Date().toISOString();\n\n this._diffAndFire(oldCache, this._cache, \"manual\");\n }\n\n /**\n * Close the runtime connection.\n *\n * Shuts down the WebSocket and cancels any pending reconnect timer.\n * Safe to call multiple times.\n */\n async close(): Promise<void> {\n this._closed = true;\n this._wsStatus = \"disconnected\";\n\n if (this._reconnectTimer !== null) {\n clearTimeout(this._reconnectTimer);\n this._reconnectTimer = null;\n }\n\n if (this._ws !== null) {\n this._ws.close();\n this._ws = null;\n }\n }\n\n /**\n * Async dispose support for `await using` (TypeScript 5.2+).\n */\n async [Symbol.asyncDispose](): Promise<void> {\n await this.close();\n }\n\n // ---- WebSocket internals ----\n\n private _buildWsUrl(): string {\n let url = this._baseUrl;\n if (url.startsWith(\"https://\")) {\n url = \"wss://\" + url.slice(\"https://\".length);\n } else if (url.startsWith(\"http://\")) {\n url = \"ws://\" + url.slice(\"http://\".length);\n } else {\n url = \"wss://\" + url;\n }\n url = url.replace(/\\/$/, \"\");\n return `${url}/api/ws/v1/configs?api_key=${this._apiKey}`;\n }\n\n private _connectWebSocket(): void {\n if (this._closed) return;\n\n this._wsStatus = \"connecting\";\n const wsUrl = this._buildWsUrl();\n\n try {\n const ws = new WebSocket(wsUrl);\n this._ws = ws;\n\n ws.on(\"open\", () => {\n if (this._closed) {\n ws.close();\n return;\n }\n this._backoffIndex = 0;\n this._wsStatus = \"connected\";\n ws.send(\n JSON.stringify({\n type: \"subscribe\",\n config_id: this._configId,\n environment: this._environment,\n }),\n );\n });\n\n ws.on(\"message\", (data: WebSocket.RawData) => {\n try {\n const msg = JSON.parse(String(data)) as WsMessage;\n this._handleMessage(msg);\n } catch {\n // ignore unparseable messages\n }\n });\n\n ws.on(\"close\", () => {\n if (!this._closed) {\n this._wsStatus = \"disconnected\";\n this._scheduleReconnect();\n }\n });\n\n ws.on(\"error\", () => {\n // 'close' will fire after 'error'; reconnect is handled there\n });\n } catch {\n if (!this._closed) {\n this._scheduleReconnect();\n }\n }\n }\n\n private _scheduleReconnect(): void {\n if (this._closed) return;\n\n const delay = BACKOFF_MS[Math.min(this._backoffIndex, BACKOFF_MS.length - 1)];\n this._backoffIndex++;\n this._wsStatus = \"connecting\";\n\n this._reconnectTimer = setTimeout(() => {\n this._reconnectTimer = null;\n // On reconnect, resync the cache to pick up changes missed while offline\n if (this._fetchChain) {\n this._fetchChain()\n .then((newChain) => {\n const oldCache = this._cache;\n this._chain = newChain;\n this._cache = resolveChain(newChain, this._environment);\n this._fetchCount += newChain.length;\n this._lastFetchAt = new Date().toISOString();\n this._diffAndFire(oldCache, this._cache, \"manual\");\n })\n .catch(() => {\n // ignore fetch errors during reconnect\n })\n .finally(() => {\n this._connectWebSocket();\n });\n } else {\n this._connectWebSocket();\n }\n }, delay);\n }\n\n private _handleMessage(msg: WsMessage): void {\n if (msg.type === \"config_changed\") {\n this._applyChanges(msg.config_id, msg.changes);\n } else if (msg.type === \"config_deleted\") {\n this._closed = true;\n void this.close();\n }\n }\n\n private _applyChanges(\n configId: string,\n changes: Array<{ key: string; old_value: unknown; new_value: unknown }>,\n ): void {\n const chainEntry = this._chain.find((c) => c.id === configId);\n if (!chainEntry) return;\n\n for (const change of changes) {\n const { key, new_value } = change;\n\n // Get or create the environment entry\n const envEntry =\n chainEntry.environments[this._environment] !== undefined &&\n chainEntry.environments[this._environment] !== null\n ? (chainEntry.environments[this._environment] as Record<string, unknown>)\n : null;\n const envValues =\n envEntry !== null && typeof envEntry === \"object\"\n ? ((envEntry.values ?? {}) as Record<string, unknown>)\n : null;\n\n if (new_value === null || new_value === undefined) {\n // Deletion: remove from base values and env values\n delete chainEntry.values[key];\n if (envValues) delete envValues[key];\n } else if (envValues && key in envValues) {\n // Update existing env-specific override\n envValues[key] = new_value;\n } else if (key in chainEntry.values) {\n // Update existing base value\n chainEntry.values[key] = new_value;\n } else {\n // New key — put in base values\n chainEntry.values[key] = new_value;\n }\n }\n\n const oldCache = this._cache;\n this._cache = resolveChain(this._chain, this._environment);\n this._diffAndFire(oldCache, this._cache, \"websocket\");\n }\n\n private _diffAndFire(\n oldCache: Record<string, unknown>,\n newCache: Record<string, unknown>,\n source: \"websocket\" | \"poll\" | \"manual\",\n ): void {\n const allKeys = new Set([...Object.keys(oldCache), ...Object.keys(newCache)]);\n\n for (const key of allKeys) {\n const oldVal = key in oldCache ? oldCache[key] : null;\n const newVal = key in newCache ? newCache[key] : null;\n\n if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {\n const event: ConfigChangeEvent = { key, oldValue: oldVal, newValue: newVal, source };\n this._fireListeners(event);\n }\n }\n }\n\n private _fireListeners(event: ConfigChangeEvent): void {\n for (const listener of this._listeners) {\n if (listener.key === null || listener.key === event.key) {\n try {\n listener.callback(event);\n } catch {\n // ignore listener errors to prevent one bad listener from stopping others\n }\n }\n }\n }\n}\n","/**\n * smplkit — Official TypeScript SDK for the smplkit platform.\n *\n * @packageDocumentation\n */\n\n// Main client\nexport { SmplkitClient } from \"./client.js\";\nexport type { SmplkitClientOptions } from \"./client.js\";\n\n// Config — management plane\nexport { ConfigClient } from \"./config/client.js\";\nexport type { CreateConfigOptions, GetConfigOptions } from \"./config/types.js\";\nexport { Config } from \"./config/types.js\";\n\n// Config — runtime plane\nexport { ConfigRuntime } from \"./config/runtime.js\";\nexport type {\n ConfigChangeEvent,\n ConfigStats,\n ConnectionStatus,\n ConnectOptions,\n} from \"./config/runtime-types.js\";\n\n// Error hierarchy\nexport {\n SmplError,\n SmplConnectionError,\n SmplTimeoutError,\n SmplNotFoundError,\n SmplConflictError,\n SmplValidationError,\n} from \"./errors.js\";\n","/**\n * ConfigClient — management-plane operations for configs.\n *\n * Uses the generated OpenAPI types (`src/generated/config.d.ts`) via\n * `openapi-fetch` for all HTTP calls, keeping the client layer fully\n * type-safe without hand-coded request/response shapes.\n */\n\nimport createClient from \"openapi-fetch\";\nimport type { components, operations } from \"../generated/config.d.ts\";\nimport {\n SmplConflictError,\n SmplNotFoundError,\n SmplValidationError,\n SmplError,\n SmplConnectionError,\n SmplTimeoutError,\n} from \"../errors.js\";\nimport { Config } from \"./types.js\";\nimport type { ConfigUpdatePayload, CreateConfigOptions, GetConfigOptions } from \"./types.js\";\n\nconst BASE_URL = \"https://config.smplkit.com\";\n\ntype ApiConfig = components[\"schemas\"][\"Config\"];\ntype ConfigResource = components[\"schemas\"][\"ConfigResource\"];\n\n/** @internal */\nfunction resourceToConfig(resource: ConfigResource, client: ConfigClient): Config {\n const attrs: ApiConfig = resource.attributes;\n return new Config(client, {\n id: resource.id ?? \"\",\n key: attrs.key ?? \"\",\n name: attrs.name,\n description: attrs.description ?? null,\n parent: attrs.parent ?? null,\n values: (attrs.values ?? {}) as Record<string, unknown>,\n environments: (attrs.environments ?? {}) as Record<string, unknown>,\n createdAt: attrs.created_at ? new Date(attrs.created_at) : null,\n updatedAt: attrs.updated_at ? new Date(attrs.updated_at) : null,\n });\n}\n\n/**\n * Map fetch or HTTP errors to typed SDK exceptions.\n * @internal\n */\nasync function checkError(response: Response, context: string): Promise<never> {\n const body = await response.text().catch(() => \"\");\n switch (response.status) {\n case 404:\n throw new SmplNotFoundError(body || context, 404, body);\n case 409:\n throw new SmplConflictError(body || context, 409, body);\n case 422:\n throw new SmplValidationError(body || context, 422, body);\n default:\n throw new SmplError(`HTTP ${response.status}: ${body}`, response.status, body);\n }\n}\n\n/**\n * Re-raise fetch-level errors (network, timeout) as typed SDK exceptions.\n * @internal\n */\nfunction wrapFetchError(err: unknown): never {\n if (\n err instanceof SmplNotFoundError ||\n err instanceof SmplConflictError ||\n err instanceof SmplValidationError ||\n err instanceof SmplError\n ) {\n throw err;\n }\n if (err instanceof TypeError) {\n throw new SmplConnectionError(`Network error: ${err.message}`);\n }\n throw new SmplConnectionError(\n `Request failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n}\n\n/**\n * Build a JSON:API request body for create/update operations.\n * @internal\n */\nfunction buildRequestBody(options: {\n id?: string | null;\n name: string;\n key?: string | null;\n description?: string | null;\n parent?: string | null;\n values?: Record<string, unknown> | null;\n environments?: Record<string, unknown> | null;\n}): operations[\"create_config\"][\"requestBody\"][\"content\"][\"application/json\"] {\n const attrs: ApiConfig = {\n name: options.name,\n };\n if (options.key !== undefined) attrs.key = options.key;\n if (options.description !== undefined) attrs.description = options.description;\n if (options.parent !== undefined) attrs.parent = options.parent;\n if (options.values !== undefined)\n attrs.values = options.values as { [key: string]: unknown } | null;\n if (options.environments !== undefined)\n attrs.environments = options.environments as { [key: string]: unknown } | null;\n\n return {\n data: {\n id: options.id ?? null,\n type: \"config\",\n attributes: attrs,\n },\n };\n}\n\n/**\n * Client for the smplkit Config API (management plane).\n *\n * All methods are async and return `Promise<T>`. Network and server\n * errors are mapped to typed SDK exceptions.\n *\n * Obtained via `SmplkitClient.config`.\n */\nexport class ConfigClient {\n /** @internal — used by Config instances for reconnecting and WebSocket auth. */\n readonly _apiKey: string;\n\n /** @internal */\n readonly _baseUrl: string = BASE_URL;\n\n /** @internal */\n private readonly _http: ReturnType<typeof createClient<import(\"../generated/config.d.ts\").paths>>;\n\n /** @internal */\n constructor(apiKey: string, timeout?: number) {\n this._apiKey = apiKey;\n const ms = timeout ?? 30_000;\n this._http = createClient<import(\"../generated/config.d.ts\").paths>({\n baseUrl: BASE_URL,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n Accept: \"application/json\",\n },\n // openapi-fetch custom fetch receives a pre-built Request object\n fetch: async (request: Request): Promise<Response> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), ms);\n try {\n return await fetch(new Request(request, { signal: controller.signal }));\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new SmplTimeoutError(`Request timed out after ${ms}ms`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n },\n });\n }\n\n /**\n * Fetch a single config by key or UUID.\n *\n * Exactly one of `key` or `id` must be provided.\n *\n * @throws {SmplNotFoundError} If no matching config exists.\n */\n async get(options: GetConfigOptions): Promise<Config> {\n const { key, id } = options;\n if ((key === undefined) === (id === undefined)) {\n throw new Error(\"Exactly one of 'key' or 'id' must be provided.\");\n }\n return id !== undefined ? this._getById(id) : this._getByKey(key!);\n }\n\n /**\n * List all configs for the account.\n */\n async list(): Promise<Config[]> {\n let data: components[\"schemas\"][\"ConfigListResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs\", {});\n if (result.error !== undefined) await checkError(result.response, \"Failed to list configs\");\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data) return [];\n return data.data.map((r) => resourceToConfig(r, this));\n }\n\n /**\n * Create a new config.\n *\n * @throws {SmplValidationError} If the server rejects the request.\n */\n async create(options: CreateConfigOptions): Promise<Config> {\n const body = buildRequestBody({\n name: options.name,\n key: options.key,\n description: options.description,\n parent: options.parent,\n values: options.values,\n });\n\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.POST(\"/api/v1/configs\", { body });\n if (result.error !== undefined) await checkError(result.response, \"Failed to create config\");\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data) throw new SmplValidationError(\"Failed to create config\");\n return resourceToConfig(data.data, this);\n }\n\n /**\n * Delete a config by UUID.\n *\n * @throws {SmplNotFoundError} If the config does not exist.\n * @throws {SmplConflictError} If the config has child configs.\n */\n async delete(configId: string): Promise<void> {\n try {\n const result = await this._http.DELETE(\"/api/v1/configs/{id}\", {\n params: { path: { id: configId } },\n });\n if (result.error !== undefined && result.response.status !== 204)\n await checkError(result.response, `Failed to delete config ${configId}`);\n } catch (err) {\n wrapFetchError(err);\n }\n }\n\n /**\n * Internal: PUT a full config update and return the updated model.\n *\n * Called by {@link Config} instance methods.\n * @internal\n */\n async _updateConfig(payload: ConfigUpdatePayload): Promise<Config> {\n const body = buildRequestBody({\n id: payload.configId,\n name: payload.name,\n key: payload.key,\n description: payload.description,\n parent: payload.parent,\n values: payload.values,\n environments: payload.environments,\n });\n\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.PUT(\"/api/v1/configs/{id}\", {\n params: { path: { id: payload.configId } },\n body,\n });\n if (result.error !== undefined)\n await checkError(result.response, `Failed to update config ${payload.configId}`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data)\n throw new SmplValidationError(`Failed to update config ${payload.configId}`);\n return resourceToConfig(data.data, this);\n }\n\n // ---- Private helpers ----\n\n private async _getById(configId: string): Promise<Config> {\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs/{id}\", {\n params: { path: { id: configId } },\n });\n if (result.error !== undefined)\n await checkError(result.response, `Config ${configId} not found`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data) throw new SmplNotFoundError(`Config ${configId} not found`);\n return resourceToConfig(data.data, this);\n }\n\n private async _getByKey(key: string): Promise<Config> {\n let data: components[\"schemas\"][\"ConfigListResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs\", {\n params: { query: { \"filter[key]\": key } },\n });\n if (result.error !== undefined)\n await checkError(result.response, `Config with key '${key}' not found`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data || data.data.length === 0) {\n throw new SmplNotFoundError(`Config with key '${key}' not found`);\n }\n return resourceToConfig(data.data[0], this);\n }\n}\n","/**\n * Structured SDK error types.\n *\n * All smplkit errors extend {@link SmplError}, allowing callers to catch\n * the base class for generic handling or specific subclasses for\n * fine-grained control.\n */\n\n/** Base exception for all smplkit SDK errors. */\nexport class SmplError extends Error {\n /** The HTTP status code, if the error originated from an HTTP response. */\n public readonly statusCode?: number;\n\n /** The raw response body, if available. */\n public readonly responseBody?: string;\n\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message);\n this.name = \"SmplError\";\n this.statusCode = statusCode;\n this.responseBody = responseBody;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when a network request fails (e.g., DNS resolution, connection refused). */\nexport class SmplConnectionError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode, responseBody);\n this.name = \"SmplConnectionError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when an operation exceeds its timeout. */\nexport class SmplTimeoutError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode, responseBody);\n this.name = \"SmplTimeoutError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when a requested resource does not exist (HTTP 404). */\nexport class SmplNotFoundError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 404, responseBody);\n this.name = \"SmplNotFoundError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when an operation conflicts with current state (HTTP 409). */\nexport class SmplConflictError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 409, responseBody);\n this.name = \"SmplConflictError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when the server rejects a request due to validation errors (HTTP 422). */\nexport class SmplValidationError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 422, responseBody);\n this.name = \"SmplValidationError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Config resource — management-plane model with runtime connect support.\n *\n * Instances are returned by {@link ConfigClient} methods and provide\n * management-plane operations (`update`, `setValues`, `setValue`) as well\n * as the {@link connect} entry point for runtime value resolution.\n */\n\nimport type { ConfigRuntime } from \"./runtime.js\";\nimport type { ConnectOptions } from \"./runtime-types.js\";\n\n/**\n * Internal type used by {@link ConfigClient}. Not part of the public API.\n * @internal\n */\nexport interface ConfigUpdatePayload {\n configId: string;\n name: string;\n key: string | null;\n description: string | null;\n parent: string | null;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n}\n\n/**\n * A configuration resource fetched from the smplkit Config service.\n *\n * Instances are returned by {@link ConfigClient} methods and provide\n * management-plane operations as well as the {@link connect} entry point\n * for runtime value resolution.\n */\nexport class Config {\n /** UUID of the config. */\n id: string;\n\n /** Human-readable key (e.g. `\"user_service\"`). */\n key: string;\n\n /** Display name. */\n name: string;\n\n /** Optional description. */\n description: string | null;\n\n /** Parent config UUID, or null if this is a root config. */\n parent: string | null;\n\n /** Base key-value pairs. */\n values: Record<string, unknown>;\n\n /**\n * Per-environment overrides.\n * Stored as `{ env_name: { values: { key: value } } }` to match the\n * server's format.\n */\n environments: Record<string, unknown>;\n\n /** When the config was created, or null if unavailable. */\n createdAt: Date | null;\n\n /** When the config was last updated, or null if unavailable. */\n updatedAt: Date | null;\n\n /**\n * Internal reference to the parent client.\n * @internal\n */\n private readonly _client: {\n _updateConfig(payload: ConfigUpdatePayload): Promise<Config>;\n get(options: { id: string }): Promise<Config>;\n readonly _apiKey: string;\n readonly _baseUrl: string;\n };\n\n /** @internal */\n constructor(\n client: {\n _updateConfig(payload: ConfigUpdatePayload): Promise<Config>;\n get(options: { id: string }): Promise<Config>;\n readonly _apiKey: string;\n readonly _baseUrl: string;\n },\n fields: {\n id: string;\n key: string;\n name: string;\n description: string | null;\n parent: string | null;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n createdAt: Date | null;\n updatedAt: Date | null;\n },\n ) {\n this._client = client;\n this.id = fields.id;\n this.key = fields.key;\n this.name = fields.name;\n this.description = fields.description;\n this.parent = fields.parent;\n this.values = fields.values;\n this.environments = fields.environments;\n this.createdAt = fields.createdAt;\n this.updatedAt = fields.updatedAt;\n }\n\n /**\n * Update this config's attributes on the server.\n *\n * Builds the request from current attribute values, overriding with any\n * provided options. Updates local attributes in place on success.\n *\n * @param options.name - New display name.\n * @param options.description - New description (pass empty string to clear).\n * @param options.values - New base values (replaces entirely).\n * @param options.environments - New environments dict (replaces entirely).\n */\n async update(options: {\n name?: string;\n description?: string;\n values?: Record<string, unknown>;\n environments?: Record<string, unknown>;\n }): Promise<void> {\n const updated = await this._client._updateConfig({\n configId: this.id,\n name: options.name ?? this.name,\n key: this.key,\n description: options.description !== undefined ? options.description : this.description,\n parent: this.parent,\n values: options.values ?? this.values,\n environments: options.environments ?? this.environments,\n });\n this.name = updated.name;\n this.description = updated.description;\n this.values = updated.values;\n this.environments = updated.environments;\n this.updatedAt = updated.updatedAt;\n }\n\n /**\n * Replace base or environment-specific values.\n *\n * When `environment` is provided, replaces that environment's `values`\n * sub-dict (other environments are preserved). When omitted, replaces\n * the base `values`.\n *\n * @param values - The complete set of values to set.\n * @param environment - Target environment, or omit for base values.\n */\n async setValues(values: Record<string, unknown>, environment?: string): Promise<void> {\n let newValues: Record<string, unknown>;\n let newEnvs: Record<string, unknown>;\n\n if (environment === undefined) {\n newValues = values;\n newEnvs = this.environments;\n } else {\n newValues = this.values;\n // Preserve any extra metadata on the environment entry (like other sub-keys),\n // but replace the `values` sub-dict entirely.\n const existingEntry =\n typeof this.environments[environment] === \"object\" &&\n this.environments[environment] !== null\n ? { ...(this.environments[environment] as Record<string, unknown>) }\n : {};\n existingEntry.values = values;\n newEnvs = { ...this.environments, [environment]: existingEntry };\n }\n\n const updated = await this._client._updateConfig({\n configId: this.id,\n name: this.name,\n key: this.key,\n description: this.description,\n parent: this.parent,\n values: newValues,\n environments: newEnvs,\n });\n this.values = updated.values;\n this.environments = updated.environments;\n this.updatedAt = updated.updatedAt;\n }\n\n /**\n * Set a single key within base or environment-specific values.\n *\n * Merges the key into existing values rather than replacing all values.\n *\n * @param key - The config key to set.\n * @param value - The value to assign.\n * @param environment - Target environment, or omit for base values.\n */\n async setValue(key: string, value: unknown, environment?: string): Promise<void> {\n if (environment === undefined) {\n const merged = { ...this.values, [key]: value };\n await this.setValues(merged);\n } else {\n const envEntry =\n typeof this.environments[environment] === \"object\" &&\n this.environments[environment] !== null\n ? (this.environments[environment] as Record<string, unknown>)\n : {};\n const existing = {\n ...(typeof envEntry.values === \"object\" && envEntry.values !== null\n ? (envEntry.values as Record<string, unknown>)\n : {}),\n };\n existing[key] = value;\n await this.setValues(existing, environment);\n }\n }\n\n /**\n * Connect to this config for runtime value resolution.\n *\n * Eagerly fetches this config and its full parent chain, resolves values\n * for the given environment via deep merge, and returns a\n * {@link ConfigRuntime} with a fully populated local cache.\n *\n * A background WebSocket connection is started for real-time updates.\n * If the WebSocket fails to connect, the runtime operates in cache-only\n * mode and reconnects automatically.\n *\n * Supports both `await` and `await using` (TypeScript 5.2+)::\n *\n * ```typescript\n * // Simple await\n * const runtime = await config.connect(\"production\");\n * try { ... } finally { await runtime.close(); }\n *\n * // await using (auto-close)\n * await using runtime = await config.connect(\"production\");\n * ```\n *\n * @param environment - The environment to resolve for (e.g. `\"production\"`).\n * @param options.timeout - Milliseconds to wait for the initial fetch.\n */\n async connect(environment: string, options?: ConnectOptions): Promise<ConfigRuntime> {\n // Lazy import to avoid loading ws at module-init time\n const { ConfigRuntime } = await import(\"./runtime.js\");\n\n const timeout = options?.timeout ?? 30_000;\n const chain = await this._buildChain(timeout);\n\n return new ConfigRuntime({\n configKey: this.key,\n configId: this.id,\n environment,\n chain,\n apiKey: this._client._apiKey,\n baseUrl: this._client._baseUrl,\n fetchChain: () => this._buildChain(timeout),\n });\n }\n\n /**\n * Walk the parent chain and return config data objects, child-to-root.\n * @internal\n */\n private async _buildChain(\n _timeout: number,\n ): Promise<\n Array<{ id: string; values: Record<string, unknown>; environments: Record<string, unknown> }>\n > {\n const chain: Array<{\n id: string;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n }> = [{ id: this.id, values: this.values, environments: this.environments }];\n\n let parentId = this.parent;\n while (parentId !== null) {\n const parentConfig = await this._client.get({ id: parentId });\n chain.push({\n id: parentConfig.id,\n values: parentConfig.values,\n environments: parentConfig.environments,\n });\n parentId = parentConfig.parent;\n }\n\n return chain;\n }\n\n toString(): string {\n return `Config(id=${this.id}, key=${this.key}, name=${this.name})`;\n }\n}\n\n/** Options for creating a new config. */\nexport interface CreateConfigOptions {\n /** Display name for the config. */\n name: string;\n /** Human-readable key. Auto-generated by the server if omitted. */\n key?: string;\n /** Optional description. */\n description?: string;\n /** Parent config UUID. Defaults to the account's `common` config if omitted. */\n parent?: string;\n /** Initial base values. */\n values?: Record<string, unknown>;\n}\n\n/** Options for fetching a single config. Exactly one of `key` or `id` must be provided. */\nexport interface GetConfigOptions {\n /** Fetch by human-readable key. */\n key?: string;\n /** Fetch by UUID. */\n id?: string;\n}\n","/**\n * Top-level SDK client — SmplkitClient.\n *\n * The main entry point for the smplkit TypeScript SDK. Provides access\n * to sub-clients for each API domain (config, flags, logging, etc.).\n */\n\nimport { ConfigClient } from \"./config/client.js\";\n\n/** Configuration options for the {@link SmplkitClient}. */\nexport interface SmplkitClientOptions {\n /** API key for authenticating with the smplkit platform. */\n apiKey: string;\n\n /**\n * Request timeout in milliseconds.\n * @default 30000\n */\n timeout?: number;\n}\n\n/**\n * Entry point for the smplkit TypeScript SDK.\n *\n * @example\n * ```typescript\n * import { SmplkitClient } from \"@smplkit/sdk\";\n *\n * const client = new SmplkitClient({ apiKey: \"sk_api_...\" });\n * const cfg = await client.config.get({ key: \"common\" });\n * ```\n */\nexport class SmplkitClient {\n /** Client for config management-plane operations. */\n readonly config: ConfigClient;\n\n constructor(options: SmplkitClientOptions) {\n if (!options.apiKey) {\n throw new Error(\"apiKey is required\");\n }\n\n this.config = new ConfigClient(options.apiKey, options.timeout);\n }\n}\n"],"mappings":"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","names":["WebSocket","ConfigRuntime","createClient"]}
1
+ {"version":3,"sources":["../src/config/resolve.ts","../src/config/runtime.ts","../src/index.ts","../src/config/client.ts","../src/errors.ts","../src/config/types.ts","../src/resolve.ts","../src/client.ts"],"sourcesContent":["/**\n * Deep-merge resolution algorithm for config inheritance chains.\n *\n * Mirrors the Python SDK's `_resolver.py` (ADR-024 §2.5–2.6).\n */\n\n/** A single entry in a config inheritance chain (child-to-root ordering). */\nexport interface ChainConfig {\n /** Config UUID. */\n id: string;\n /** Base key-value pairs. */\n values: Record<string, unknown>;\n /**\n * Per-environment overrides.\n * Each entry is `{ values: { key: value, ... } }` — the server wraps\n * environment-specific values in a nested `values` key.\n */\n environments: Record<string, unknown>;\n}\n\n/**\n * Recursively merge two dicts, with `override` taking precedence.\n *\n * Nested dicts are merged recursively. Non-dict values (strings, numbers,\n * booleans, arrays, null) are replaced wholesale.\n */\nexport function deepMerge(\n base: Record<string, unknown>,\n override: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...base };\n for (const [key, value] of Object.entries(override)) {\n if (\n key in result &&\n typeof result[key] === \"object\" &&\n result[key] !== null &&\n !Array.isArray(result[key]) &&\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value)\n ) {\n result[key] = deepMerge(\n result[key] as Record<string, unknown>,\n value as Record<string, unknown>,\n );\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Resolve the full configuration for an environment given a config chain.\n *\n * Walks from root (last element) to child (first element), accumulating\n * values via deep merge so that child configs override parent configs.\n *\n * For each config in the chain, base `values` are merged with\n * environment-specific values (env wins), then that result is merged\n * on top of the accumulated parent result (child wins over parent).\n *\n * @param chain - Ordered list of config data from child (index 0) to root ancestor (last).\n * @param environment - The environment key to resolve for.\n */\nexport function resolveChain(chain: ChainConfig[], environment: string): Record<string, unknown> {\n let accumulated: Record<string, unknown> = {};\n\n // Walk from root to child (reverse order — chain is child-to-root)\n for (let i = chain.length - 1; i >= 0; i--) {\n const config = chain[i];\n const baseValues: Record<string, unknown> = config.values ?? {};\n\n // Environments are stored as { env_name: { values: { key: val } } }\n const envEntry = (config.environments ?? {})[environment];\n const envValues: Record<string, unknown> =\n envEntry !== null &&\n envEntry !== undefined &&\n typeof envEntry === \"object\" &&\n !Array.isArray(envEntry)\n ? (((envEntry as Record<string, unknown>).values ?? {}) as Record<string, unknown>)\n : {};\n\n // Merge environment overrides on top of base values (env wins)\n const configResolved = deepMerge(baseValues, envValues);\n\n // Merge this config's resolved values on top of accumulated parent values (child wins)\n accumulated = deepMerge(accumulated, configResolved);\n }\n\n return accumulated;\n}\n","/**\n * ConfigRuntime — runtime-plane value resolution with WebSocket updates.\n *\n * Holds a fully resolved local cache of config values for a specific\n * environment. All value-access methods are synchronous (local reads);\n * only {@link refresh} and {@link close} are async.\n *\n * A background WebSocket connection is maintained for real-time updates.\n * If the WebSocket fails, the runtime operates in cache-only mode and\n * reconnects automatically with exponential backoff.\n */\n\nimport WebSocket from \"ws\";\nimport { resolveChain } from \"./resolve.js\";\nimport type { ChainConfig } from \"./resolve.js\";\nimport type { ConfigChangeEvent, ConfigStats, ConnectionStatus } from \"./runtime-types.js\";\n\n/** @internal */\ninterface ChangeListener {\n callback: (event: ConfigChangeEvent) => void;\n key: string | null;\n}\n\n/** @internal */\ninterface WsConfigChangedMessage {\n type: \"config_changed\";\n config_id: string;\n changes: Array<{\n key: string;\n old_value: unknown;\n new_value: unknown;\n }>;\n}\n\n/** @internal */\ninterface WsConfigDeletedMessage {\n type: \"config_deleted\";\n config_id: string;\n}\n\ntype WsMessage =\n | { type: \"subscribed\"; config_id: string; environment: string }\n | { type: \"error\"; message: string }\n | WsConfigChangedMessage\n | WsConfigDeletedMessage;\n\n/** @internal */\nconst BACKOFF_MS = [1000, 2000, 4000, 8000, 16000, 32000, 60000];\n\n/** @internal Options for constructing a ConfigRuntime. */\nexport interface ConfigRuntimeOptions {\n configKey: string;\n configId: string;\n environment: string;\n chain: ChainConfig[];\n apiKey: string;\n baseUrl: string;\n fetchChain: (() => Promise<ChainConfig[]>) | null;\n}\n\n/**\n * Runtime configuration handle for a specific environment.\n *\n * Obtained by calling {@link Config.connect}. All value-access methods\n * are synchronous and served entirely from a local in-process cache.\n * The cache is populated eagerly on construction and kept current via\n * a background WebSocket connection.\n */\nexport class ConfigRuntime {\n private _cache: Record<string, unknown>;\n private _chain: ChainConfig[];\n private _fetchCount: number;\n private _lastFetchAt: string | null;\n private _closed = false;\n private _wsStatus: ConnectionStatus = \"disconnected\";\n private _ws: InstanceType<typeof WebSocket> | null = null;\n private _reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private _backoffIndex = 0;\n private _listeners: ChangeListener[] = [];\n\n private readonly _configId: string;\n private readonly _environment: string;\n private readonly _apiKey: string;\n private readonly _baseUrl: string;\n private readonly _fetchChain: (() => Promise<ChainConfig[]>) | null;\n\n /** @internal */\n constructor(options: ConfigRuntimeOptions) {\n this._configId = options.configId;\n this._environment = options.environment;\n this._apiKey = options.apiKey;\n this._baseUrl = options.baseUrl;\n this._fetchChain = options.fetchChain;\n this._chain = options.chain;\n this._cache = resolveChain(options.chain, options.environment);\n this._fetchCount = options.chain.length;\n this._lastFetchAt = new Date().toISOString();\n\n // Start WebSocket in background — non-blocking\n this._connectWebSocket();\n }\n\n // ---- Value access (synchronous, local cache) ----\n\n /**\n * Return the resolved value for `key`, or `defaultValue` if absent.\n *\n * @param key - The config key to look up.\n * @param defaultValue - Returned when the key is not present (default: null).\n */\n get(key: string, defaultValue: unknown = null): unknown {\n return key in this._cache ? this._cache[key] : defaultValue;\n }\n\n /**\n * Return the value as a string, or `defaultValue` if absent or not a string.\n */\n getString(key: string, defaultValue: string | null = null): string | null {\n const value = this._cache[key];\n return typeof value === \"string\" ? value : defaultValue;\n }\n\n /**\n * Return the value as a number, or `defaultValue` if absent or not a number.\n */\n getInt(key: string, defaultValue: number | null = null): number | null {\n const value = this._cache[key];\n return typeof value === \"number\" ? value : defaultValue;\n }\n\n /**\n * Return the value as a boolean, or `defaultValue` if absent or not a boolean.\n */\n getBool(key: string, defaultValue: boolean | null = null): boolean | null {\n const value = this._cache[key];\n return typeof value === \"boolean\" ? value : defaultValue;\n }\n\n /**\n * Return whether `key` is present in the resolved configuration.\n */\n exists(key: string): boolean {\n return key in this._cache;\n }\n\n /**\n * Return a shallow copy of the full resolved configuration.\n */\n getAll(): Record<string, unknown> {\n return { ...this._cache };\n }\n\n // ---- Change listeners ----\n\n /**\n * Register a listener that fires when a config value changes.\n *\n * @param callback - Called with a {@link ConfigChangeEvent} on each change.\n * @param options.key - If provided, the listener fires only for this key.\n * If omitted, the listener fires for all changes.\n */\n onChange(callback: (event: ConfigChangeEvent) => void, options?: { key?: string }): void {\n this._listeners.push({\n callback,\n key: options?.key ?? null,\n });\n }\n\n // ---- Diagnostics ----\n\n /**\n * Return diagnostic statistics for this runtime.\n */\n stats(): ConfigStats {\n return {\n fetchCount: this._fetchCount,\n lastFetchAt: this._lastFetchAt,\n };\n }\n\n /**\n * Return the current WebSocket connection status.\n */\n connectionStatus(): ConnectionStatus {\n return this._wsStatus;\n }\n\n // ---- Lifecycle ----\n\n /**\n * Force a manual refresh of the cached configuration.\n *\n * Re-fetches the full config chain via HTTP, re-resolves values, updates\n * the local cache, and fires listeners for any detected changes.\n *\n * @throws {Error} If no `fetchChain` function was provided on construction.\n */\n async refresh(): Promise<void> {\n if (!this._fetchChain) {\n throw new Error(\"No fetchChain function provided; cannot refresh.\");\n }\n\n const newChain = await this._fetchChain();\n const oldCache = this._cache;\n\n this._chain = newChain;\n this._cache = resolveChain(newChain, this._environment);\n this._fetchCount += newChain.length;\n this._lastFetchAt = new Date().toISOString();\n\n this._diffAndFire(oldCache, this._cache, \"manual\");\n }\n\n /**\n * Close the runtime connection.\n *\n * Shuts down the WebSocket and cancels any pending reconnect timer.\n * Safe to call multiple times.\n */\n async close(): Promise<void> {\n this._closed = true;\n this._wsStatus = \"disconnected\";\n\n if (this._reconnectTimer !== null) {\n clearTimeout(this._reconnectTimer);\n this._reconnectTimer = null;\n }\n\n if (this._ws !== null) {\n this._ws.close();\n this._ws = null;\n }\n }\n\n /**\n * Async dispose support for `await using` (TypeScript 5.2+).\n */\n async [Symbol.asyncDispose](): Promise<void> {\n await this.close();\n }\n\n // ---- WebSocket internals ----\n\n private _buildWsUrl(): string {\n let url = this._baseUrl;\n if (url.startsWith(\"https://\")) {\n url = \"wss://\" + url.slice(\"https://\".length);\n } else if (url.startsWith(\"http://\")) {\n url = \"ws://\" + url.slice(\"http://\".length);\n } else {\n url = \"wss://\" + url;\n }\n url = url.replace(/\\/$/, \"\");\n return `${url}/api/ws/v1/configs?api_key=${this._apiKey}`;\n }\n\n private _connectWebSocket(): void {\n if (this._closed) return;\n\n this._wsStatus = \"connecting\";\n const wsUrl = this._buildWsUrl();\n\n try {\n const ws = new WebSocket(wsUrl);\n this._ws = ws;\n\n ws.on(\"open\", () => {\n if (this._closed) {\n ws.close();\n return;\n }\n this._backoffIndex = 0;\n this._wsStatus = \"connected\";\n ws.send(\n JSON.stringify({\n type: \"subscribe\",\n config_id: this._configId,\n environment: this._environment,\n }),\n );\n });\n\n ws.on(\"message\", (data: WebSocket.RawData) => {\n try {\n const msg = JSON.parse(String(data)) as WsMessage;\n this._handleMessage(msg);\n } catch {\n // ignore unparseable messages\n }\n });\n\n ws.on(\"close\", () => {\n if (!this._closed) {\n this._wsStatus = \"disconnected\";\n this._scheduleReconnect();\n }\n });\n\n ws.on(\"error\", () => {\n // 'close' will fire after 'error'; reconnect is handled there\n });\n } catch {\n if (!this._closed) {\n this._scheduleReconnect();\n }\n }\n }\n\n private _scheduleReconnect(): void {\n if (this._closed) return;\n\n const delay = BACKOFF_MS[Math.min(this._backoffIndex, BACKOFF_MS.length - 1)];\n this._backoffIndex++;\n this._wsStatus = \"connecting\";\n\n this._reconnectTimer = setTimeout(() => {\n this._reconnectTimer = null;\n // On reconnect, resync the cache to pick up changes missed while offline\n if (this._fetchChain) {\n this._fetchChain()\n .then((newChain) => {\n const oldCache = this._cache;\n this._chain = newChain;\n this._cache = resolveChain(newChain, this._environment);\n this._fetchCount += newChain.length;\n this._lastFetchAt = new Date().toISOString();\n this._diffAndFire(oldCache, this._cache, \"manual\");\n })\n .catch(() => {\n // ignore fetch errors during reconnect\n })\n .finally(() => {\n this._connectWebSocket();\n });\n } else {\n this._connectWebSocket();\n }\n }, delay);\n }\n\n private _handleMessage(msg: WsMessage): void {\n if (msg.type === \"config_changed\") {\n this._applyChanges(msg.config_id, msg.changes);\n } else if (msg.type === \"config_deleted\") {\n this._closed = true;\n void this.close();\n }\n }\n\n private _applyChanges(\n configId: string,\n changes: Array<{ key: string; old_value: unknown; new_value: unknown }>,\n ): void {\n const chainEntry = this._chain.find((c) => c.id === configId);\n if (!chainEntry) return;\n\n for (const change of changes) {\n const { key, new_value } = change;\n\n // Get or create the environment entry\n const envEntry =\n chainEntry.environments[this._environment] !== undefined &&\n chainEntry.environments[this._environment] !== null\n ? (chainEntry.environments[this._environment] as Record<string, unknown>)\n : null;\n const envValues =\n envEntry !== null && typeof envEntry === \"object\"\n ? ((envEntry.values ?? {}) as Record<string, unknown>)\n : null;\n\n if (new_value === null || new_value === undefined) {\n // Deletion: remove from base values and env values\n delete chainEntry.values[key];\n if (envValues) delete envValues[key];\n } else if (envValues && key in envValues) {\n // Update existing env-specific override\n envValues[key] = new_value;\n } else if (key in chainEntry.values) {\n // Update existing base value\n chainEntry.values[key] = new_value;\n } else {\n // New key — put in base values\n chainEntry.values[key] = new_value;\n }\n }\n\n const oldCache = this._cache;\n this._cache = resolveChain(this._chain, this._environment);\n this._diffAndFire(oldCache, this._cache, \"websocket\");\n }\n\n private _diffAndFire(\n oldCache: Record<string, unknown>,\n newCache: Record<string, unknown>,\n source: \"websocket\" | \"poll\" | \"manual\",\n ): void {\n const allKeys = new Set([...Object.keys(oldCache), ...Object.keys(newCache)]);\n\n for (const key of allKeys) {\n const oldVal = key in oldCache ? oldCache[key] : null;\n const newVal = key in newCache ? newCache[key] : null;\n\n if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {\n const event: ConfigChangeEvent = { key, oldValue: oldVal, newValue: newVal, source };\n this._fireListeners(event);\n }\n }\n }\n\n private _fireListeners(event: ConfigChangeEvent): void {\n for (const listener of this._listeners) {\n if (listener.key === null || listener.key === event.key) {\n try {\n listener.callback(event);\n } catch {\n // ignore listener errors to prevent one bad listener from stopping others\n }\n }\n }\n }\n}\n","/**\n * smplkit — Official TypeScript SDK for the smplkit platform.\n *\n * @packageDocumentation\n */\n\n// Main client\nexport { SmplClient } from \"./client.js\";\nexport type { SmplClientOptions } from \"./client.js\";\n\n// Config — management plane\nexport { ConfigClient } from \"./config/client.js\";\nexport type { CreateConfigOptions, GetConfigOptions } from \"./config/types.js\";\nexport { Config } from \"./config/types.js\";\n\n// Config — runtime plane\nexport { ConfigRuntime } from \"./config/runtime.js\";\nexport type {\n ConfigChangeEvent,\n ConfigStats,\n ConnectionStatus,\n ConnectOptions,\n} from \"./config/runtime-types.js\";\n\n// Error hierarchy\nexport {\n SmplError,\n SmplConnectionError,\n SmplTimeoutError,\n SmplNotFoundError,\n SmplConflictError,\n SmplValidationError,\n} from \"./errors.js\";\n","/**\n * ConfigClient — management-plane operations for configs.\n *\n * Uses the generated OpenAPI types (`src/generated/config.d.ts`) via\n * `openapi-fetch` for all HTTP calls, keeping the client layer fully\n * type-safe without hand-coded request/response shapes.\n */\n\nimport createClient from \"openapi-fetch\";\nimport type { components, operations } from \"../generated/config.d.ts\";\nimport {\n SmplConflictError,\n SmplNotFoundError,\n SmplValidationError,\n SmplError,\n SmplConnectionError,\n SmplTimeoutError,\n} from \"../errors.js\";\nimport { Config } from \"./types.js\";\nimport type { ConfigUpdatePayload, CreateConfigOptions, GetConfigOptions } from \"./types.js\";\n\nconst BASE_URL = \"https://config.smplkit.com\";\n\ntype ApiConfig = components[\"schemas\"][\"Config\"];\ntype ConfigResource = components[\"schemas\"][\"ConfigResource\"];\n\n/** @internal */\nfunction resourceToConfig(resource: ConfigResource, client: ConfigClient): Config {\n const attrs: ApiConfig = resource.attributes;\n return new Config(client, {\n id: resource.id ?? \"\",\n key: attrs.key ?? \"\",\n name: attrs.name,\n description: attrs.description ?? null,\n parent: attrs.parent ?? null,\n values: (attrs.values ?? {}) as Record<string, unknown>,\n environments: (attrs.environments ?? {}) as Record<string, unknown>,\n createdAt: attrs.created_at ? new Date(attrs.created_at) : null,\n updatedAt: attrs.updated_at ? new Date(attrs.updated_at) : null,\n });\n}\n\n/**\n * Map fetch or HTTP errors to typed SDK exceptions.\n * @internal\n */\nasync function checkError(response: Response, context: string): Promise<never> {\n const body = await response.text().catch(() => \"\");\n switch (response.status) {\n case 404:\n throw new SmplNotFoundError(body || context, 404, body);\n case 409:\n throw new SmplConflictError(body || context, 409, body);\n case 422:\n throw new SmplValidationError(body || context, 422, body);\n default:\n throw new SmplError(`HTTP ${response.status}: ${body}`, response.status, body);\n }\n}\n\n/**\n * Re-raise fetch-level errors (network, timeout) as typed SDK exceptions.\n * @internal\n */\nfunction wrapFetchError(err: unknown): never {\n if (\n err instanceof SmplNotFoundError ||\n err instanceof SmplConflictError ||\n err instanceof SmplValidationError ||\n err instanceof SmplError\n ) {\n throw err;\n }\n if (err instanceof TypeError) {\n throw new SmplConnectionError(`Network error: ${err.message}`);\n }\n throw new SmplConnectionError(\n `Request failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n}\n\n/**\n * Build a JSON:API request body for create/update operations.\n * @internal\n */\nfunction buildRequestBody(options: {\n id?: string | null;\n name: string;\n key?: string | null;\n description?: string | null;\n parent?: string | null;\n values?: Record<string, unknown> | null;\n environments?: Record<string, unknown> | null;\n}): operations[\"create_config\"][\"requestBody\"][\"content\"][\"application/json\"] {\n const attrs: ApiConfig = {\n name: options.name,\n };\n if (options.key !== undefined) attrs.key = options.key;\n if (options.description !== undefined) attrs.description = options.description;\n if (options.parent !== undefined) attrs.parent = options.parent;\n if (options.values !== undefined)\n attrs.values = options.values as { [key: string]: unknown } | null;\n if (options.environments !== undefined)\n attrs.environments = options.environments as { [key: string]: unknown } | null;\n\n return {\n data: {\n id: options.id ?? null,\n type: \"config\",\n attributes: attrs,\n },\n };\n}\n\n/**\n * Client for the smplkit Config API (management plane).\n *\n * All methods are async and return `Promise<T>`. Network and server\n * errors are mapped to typed SDK exceptions.\n *\n * Obtained via `SmplClient.config`.\n */\nexport class ConfigClient {\n /** @internal — used by Config instances for reconnecting and WebSocket auth. */\n readonly _apiKey: string;\n\n /** @internal */\n readonly _baseUrl: string = BASE_URL;\n\n /** @internal */\n private readonly _http: ReturnType<typeof createClient<import(\"../generated/config.d.ts\").paths>>;\n\n /** @internal */\n constructor(apiKey: string, timeout?: number) {\n this._apiKey = apiKey;\n const ms = timeout ?? 30_000;\n this._http = createClient<import(\"../generated/config.d.ts\").paths>({\n baseUrl: BASE_URL,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n Accept: \"application/json\",\n },\n // openapi-fetch custom fetch receives a pre-built Request object\n fetch: async (request: Request): Promise<Response> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), ms);\n try {\n return await fetch(new Request(request, { signal: controller.signal }));\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new SmplTimeoutError(`Request timed out after ${ms}ms`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n },\n });\n }\n\n /**\n * Fetch a single config by key or UUID.\n *\n * Exactly one of `key` or `id` must be provided.\n *\n * @throws {SmplNotFoundError} If no matching config exists.\n */\n async get(options: GetConfigOptions): Promise<Config> {\n const { key, id } = options;\n if ((key === undefined) === (id === undefined)) {\n throw new Error(\"Exactly one of 'key' or 'id' must be provided.\");\n }\n return id !== undefined ? this._getById(id) : this._getByKey(key!);\n }\n\n /**\n * List all configs for the account.\n */\n async list(): Promise<Config[]> {\n let data: components[\"schemas\"][\"ConfigListResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs\", {});\n if (result.error !== undefined) await checkError(result.response, \"Failed to list configs\");\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data) return [];\n return data.data.map((r) => resourceToConfig(r, this));\n }\n\n /**\n * Create a new config.\n *\n * @throws {SmplValidationError} If the server rejects the request.\n */\n async create(options: CreateConfigOptions): Promise<Config> {\n const body = buildRequestBody({\n name: options.name,\n key: options.key,\n description: options.description,\n parent: options.parent,\n values: options.values,\n });\n\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.POST(\"/api/v1/configs\", { body });\n if (result.error !== undefined) await checkError(result.response, \"Failed to create config\");\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data) throw new SmplValidationError(\"Failed to create config\");\n return resourceToConfig(data.data, this);\n }\n\n /**\n * Delete a config by UUID.\n *\n * @throws {SmplNotFoundError} If the config does not exist.\n * @throws {SmplConflictError} If the config has child configs.\n */\n async delete(configId: string): Promise<void> {\n try {\n const result = await this._http.DELETE(\"/api/v1/configs/{id}\", {\n params: { path: { id: configId } },\n });\n if (result.error !== undefined && result.response.status !== 204)\n await checkError(result.response, `Failed to delete config ${configId}`);\n } catch (err) {\n wrapFetchError(err);\n }\n }\n\n /**\n * Internal: PUT a full config update and return the updated model.\n *\n * Called by {@link Config} instance methods.\n * @internal\n */\n async _updateConfig(payload: ConfigUpdatePayload): Promise<Config> {\n const body = buildRequestBody({\n id: payload.configId,\n name: payload.name,\n key: payload.key,\n description: payload.description,\n parent: payload.parent,\n values: payload.values,\n environments: payload.environments,\n });\n\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.PUT(\"/api/v1/configs/{id}\", {\n params: { path: { id: payload.configId } },\n body,\n });\n if (result.error !== undefined)\n await checkError(result.response, `Failed to update config ${payload.configId}`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data)\n throw new SmplValidationError(`Failed to update config ${payload.configId}`);\n return resourceToConfig(data.data, this);\n }\n\n // ---- Private helpers ----\n\n private async _getById(configId: string): Promise<Config> {\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs/{id}\", {\n params: { path: { id: configId } },\n });\n if (result.error !== undefined)\n await checkError(result.response, `Config ${configId} not found`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data) throw new SmplNotFoundError(`Config ${configId} not found`);\n return resourceToConfig(data.data, this);\n }\n\n private async _getByKey(key: string): Promise<Config> {\n let data: components[\"schemas\"][\"ConfigListResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs\", {\n params: { query: { \"filter[key]\": key } },\n });\n if (result.error !== undefined)\n await checkError(result.response, `Config with key '${key}' not found`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data || data.data.length === 0) {\n throw new SmplNotFoundError(`Config with key '${key}' not found`);\n }\n return resourceToConfig(data.data[0], this);\n }\n}\n","/**\n * Structured SDK error types.\n *\n * All smplkit errors extend {@link SmplError}, allowing callers to catch\n * the base class for generic handling or specific subclasses for\n * fine-grained control.\n */\n\n/** Base exception for all smplkit SDK errors. */\nexport class SmplError extends Error {\n /** The HTTP status code, if the error originated from an HTTP response. */\n public readonly statusCode?: number;\n\n /** The raw response body, if available. */\n public readonly responseBody?: string;\n\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message);\n this.name = \"SmplError\";\n this.statusCode = statusCode;\n this.responseBody = responseBody;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when a network request fails (e.g., DNS resolution, connection refused). */\nexport class SmplConnectionError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode, responseBody);\n this.name = \"SmplConnectionError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when an operation exceeds its timeout. */\nexport class SmplTimeoutError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode, responseBody);\n this.name = \"SmplTimeoutError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when a requested resource does not exist (HTTP 404). */\nexport class SmplNotFoundError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 404, responseBody);\n this.name = \"SmplNotFoundError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when an operation conflicts with current state (HTTP 409). */\nexport class SmplConflictError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 409, responseBody);\n this.name = \"SmplConflictError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when the server rejects a request due to validation errors (HTTP 422). */\nexport class SmplValidationError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 422, responseBody);\n this.name = \"SmplValidationError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Config resource — management-plane model with runtime connect support.\n *\n * Instances are returned by {@link ConfigClient} methods and provide\n * management-plane operations (`update`, `setValues`, `setValue`) as well\n * as the {@link connect} entry point for runtime value resolution.\n */\n\nimport type { ConfigRuntime } from \"./runtime.js\";\nimport type { ConnectOptions } from \"./runtime-types.js\";\n\n/**\n * Internal type used by {@link ConfigClient}. Not part of the public API.\n * @internal\n */\nexport interface ConfigUpdatePayload {\n configId: string;\n name: string;\n key: string | null;\n description: string | null;\n parent: string | null;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n}\n\n/**\n * A configuration resource fetched from the smplkit Config service.\n *\n * Instances are returned by {@link ConfigClient} methods and provide\n * management-plane operations as well as the {@link connect} entry point\n * for runtime value resolution.\n */\nexport class Config {\n /** UUID of the config. */\n id: string;\n\n /** Human-readable key (e.g. `\"user_service\"`). */\n key: string;\n\n /** Display name. */\n name: string;\n\n /** Optional description. */\n description: string | null;\n\n /** Parent config UUID, or null if this is a root config. */\n parent: string | null;\n\n /** Base key-value pairs. */\n values: Record<string, unknown>;\n\n /**\n * Per-environment overrides.\n * Stored as `{ env_name: { values: { key: value } } }` to match the\n * server's format.\n */\n environments: Record<string, unknown>;\n\n /** When the config was created, or null if unavailable. */\n createdAt: Date | null;\n\n /** When the config was last updated, or null if unavailable. */\n updatedAt: Date | null;\n\n /**\n * Internal reference to the parent client.\n * @internal\n */\n private readonly _client: {\n _updateConfig(payload: ConfigUpdatePayload): Promise<Config>;\n get(options: { id: string }): Promise<Config>;\n readonly _apiKey: string;\n readonly _baseUrl: string;\n };\n\n /** @internal */\n constructor(\n client: {\n _updateConfig(payload: ConfigUpdatePayload): Promise<Config>;\n get(options: { id: string }): Promise<Config>;\n readonly _apiKey: string;\n readonly _baseUrl: string;\n },\n fields: {\n id: string;\n key: string;\n name: string;\n description: string | null;\n parent: string | null;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n createdAt: Date | null;\n updatedAt: Date | null;\n },\n ) {\n this._client = client;\n this.id = fields.id;\n this.key = fields.key;\n this.name = fields.name;\n this.description = fields.description;\n this.parent = fields.parent;\n this.values = fields.values;\n this.environments = fields.environments;\n this.createdAt = fields.createdAt;\n this.updatedAt = fields.updatedAt;\n }\n\n /**\n * Update this config's attributes on the server.\n *\n * Builds the request from current attribute values, overriding with any\n * provided options. Updates local attributes in place on success.\n *\n * @param options.name - New display name.\n * @param options.description - New description (pass empty string to clear).\n * @param options.values - New base values (replaces entirely).\n * @param options.environments - New environments dict (replaces entirely).\n */\n async update(options: {\n name?: string;\n description?: string;\n values?: Record<string, unknown>;\n environments?: Record<string, unknown>;\n }): Promise<void> {\n const updated = await this._client._updateConfig({\n configId: this.id,\n name: options.name ?? this.name,\n key: this.key,\n description: options.description !== undefined ? options.description : this.description,\n parent: this.parent,\n values: options.values ?? this.values,\n environments: options.environments ?? this.environments,\n });\n this.name = updated.name;\n this.description = updated.description;\n this.values = updated.values;\n this.environments = updated.environments;\n this.updatedAt = updated.updatedAt;\n }\n\n /**\n * Replace base or environment-specific values.\n *\n * When `environment` is provided, replaces that environment's `values`\n * sub-dict (other environments are preserved). When omitted, replaces\n * the base `values`.\n *\n * @param values - The complete set of values to set.\n * @param environment - Target environment, or omit for base values.\n */\n async setValues(values: Record<string, unknown>, environment?: string): Promise<void> {\n let newValues: Record<string, unknown>;\n let newEnvs: Record<string, unknown>;\n\n if (environment === undefined) {\n newValues = values;\n newEnvs = this.environments;\n } else {\n newValues = this.values;\n // Preserve any extra metadata on the environment entry (like other sub-keys),\n // but replace the `values` sub-dict entirely.\n const existingEntry =\n typeof this.environments[environment] === \"object\" &&\n this.environments[environment] !== null\n ? { ...(this.environments[environment] as Record<string, unknown>) }\n : {};\n existingEntry.values = values;\n newEnvs = { ...this.environments, [environment]: existingEntry };\n }\n\n const updated = await this._client._updateConfig({\n configId: this.id,\n name: this.name,\n key: this.key,\n description: this.description,\n parent: this.parent,\n values: newValues,\n environments: newEnvs,\n });\n this.values = updated.values;\n this.environments = updated.environments;\n this.updatedAt = updated.updatedAt;\n }\n\n /**\n * Set a single key within base or environment-specific values.\n *\n * Merges the key into existing values rather than replacing all values.\n *\n * @param key - The config key to set.\n * @param value - The value to assign.\n * @param environment - Target environment, or omit for base values.\n */\n async setValue(key: string, value: unknown, environment?: string): Promise<void> {\n if (environment === undefined) {\n const merged = { ...this.values, [key]: value };\n await this.setValues(merged);\n } else {\n const envEntry =\n typeof this.environments[environment] === \"object\" &&\n this.environments[environment] !== null\n ? (this.environments[environment] as Record<string, unknown>)\n : {};\n const existing = {\n ...(typeof envEntry.values === \"object\" && envEntry.values !== null\n ? (envEntry.values as Record<string, unknown>)\n : {}),\n };\n existing[key] = value;\n await this.setValues(existing, environment);\n }\n }\n\n /**\n * Connect to this config for runtime value resolution.\n *\n * Eagerly fetches this config and its full parent chain, resolves values\n * for the given environment via deep merge, and returns a\n * {@link ConfigRuntime} with a fully populated local cache.\n *\n * A background WebSocket connection is started for real-time updates.\n * If the WebSocket fails to connect, the runtime operates in cache-only\n * mode and reconnects automatically.\n *\n * Supports both `await` and `await using` (TypeScript 5.2+)::\n *\n * ```typescript\n * // Simple await\n * const runtime = await config.connect(\"production\");\n * try { ... } finally { await runtime.close(); }\n *\n * // await using (auto-close)\n * await using runtime = await config.connect(\"production\");\n * ```\n *\n * @param environment - The environment to resolve for (e.g. `\"production\"`).\n * @param options.timeout - Milliseconds to wait for the initial fetch.\n */\n async connect(environment: string, options?: ConnectOptions): Promise<ConfigRuntime> {\n // Lazy import to avoid loading ws at module-init time\n const { ConfigRuntime } = await import(\"./runtime.js\");\n\n const timeout = options?.timeout ?? 30_000;\n const chain = await this._buildChain(timeout);\n\n return new ConfigRuntime({\n configKey: this.key,\n configId: this.id,\n environment,\n chain,\n apiKey: this._client._apiKey,\n baseUrl: this._client._baseUrl,\n fetchChain: () => this._buildChain(timeout),\n });\n }\n\n /**\n * Walk the parent chain and return config data objects, child-to-root.\n * @internal\n */\n private async _buildChain(\n _timeout: number,\n ): Promise<\n Array<{ id: string; values: Record<string, unknown>; environments: Record<string, unknown> }>\n > {\n const chain: Array<{\n id: string;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n }> = [{ id: this.id, values: this.values, environments: this.environments }];\n\n let parentId = this.parent;\n while (parentId !== null) {\n const parentConfig = await this._client.get({ id: parentId });\n chain.push({\n id: parentConfig.id,\n values: parentConfig.values,\n environments: parentConfig.environments,\n });\n parentId = parentConfig.parent;\n }\n\n return chain;\n }\n\n toString(): string {\n return `Config(id=${this.id}, key=${this.key}, name=${this.name})`;\n }\n}\n\n/** Options for creating a new config. */\nexport interface CreateConfigOptions {\n /** Display name for the config. */\n name: string;\n /** Human-readable key. Auto-generated by the server if omitted. */\n key?: string;\n /** Optional description. */\n description?: string;\n /** Parent config UUID. Defaults to the account's `common` config if omitted. */\n parent?: string;\n /** Initial base values. */\n values?: Record<string, unknown>;\n}\n\n/** Options for fetching a single config. Exactly one of `key` or `id` must be provided. */\nexport interface GetConfigOptions {\n /** Fetch by human-readable key. */\n key?: string;\n /** Fetch by UUID. */\n id?: string;\n}\n","/**\n * API key resolution chain: explicit → env var → config file.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { SmplError } from \"./errors.js\";\n\nconst NO_API_KEY_MESSAGE =\n \"No API key provided. Set one of:\\n\" +\n \" 1. Pass apiKey to the constructor\\n\" +\n \" 2. Set the SMPLKIT_API_KEY environment variable\\n\" +\n \" 3. Add api_key to [default] in ~/.smplkit\";\n\nexport function resolveApiKey(explicit?: string): string {\n if (explicit) return explicit;\n\n const envVal = process.env.SMPLKIT_API_KEY;\n if (envVal) return envVal;\n\n const configPath = join(homedir(), \".smplkit\");\n try {\n const content = readFileSync(configPath, \"utf-8\");\n const match = content.match(/\\[default\\]\\s*[\\s\\S]*?api_key\\s*=\\s*\"([^\"]+)\"/);\n if (match?.[1]) return match[1];\n } catch {\n // File doesn't exist or isn't readable — skip\n }\n\n throw new SmplError(NO_API_KEY_MESSAGE);\n}\n","/**\n * Top-level SDK client — SmplClient.\n *\n * The main entry point for the smplkit TypeScript SDK. Provides access\n * to sub-clients for each API domain (config, flags, logging, etc.).\n */\n\nimport { ConfigClient } from \"./config/client.js\";\nimport { resolveApiKey } from \"./resolve.js\";\n\n/** Configuration options for the {@link SmplClient}. */\nexport interface SmplClientOptions {\n /**\n * API key for authenticating with the smplkit platform.\n * When omitted, the SDK resolves it from the `SMPLKIT_API_KEY`\n * environment variable or the `~/.smplkit` configuration file.\n */\n apiKey?: string;\n\n /**\n * Request timeout in milliseconds.\n * @default 30000\n */\n timeout?: number;\n}\n\n/**\n * Entry point for the smplkit TypeScript SDK.\n *\n * @example\n * ```typescript\n * import { SmplClient } from \"@smplkit/sdk\";\n *\n * const client = new SmplClient({ apiKey: \"sk_api_...\" });\n * const cfg = await client.config.get({ key: \"common\" });\n * ```\n */\nexport class SmplClient {\n /** Client for config management-plane operations. */\n readonly config: ConfigClient;\n\n constructor(options: SmplClientOptions = {}) {\n const apiKey = resolveApiKey(options.apiKey);\n this.config = new ConfigClient(apiKey, options.timeout);\n }\n}\n"],"mappings":"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","names":["WebSocket","ConfigRuntime","createClient"]}
package/dist/index.d.cts CHANGED
@@ -115,7 +115,7 @@ declare class ConfigRuntime {
115
115
  /**
116
116
  * Return the value as a number, or `defaultValue` if absent or not a number.
117
117
  */
118
- getNumber(key: string, defaultValue?: number | null): number | null;
118
+ getInt(key: string, defaultValue?: number | null): number | null;
119
119
  /**
120
120
  * Return the value as a boolean, or `defaultValue` if absent or not a boolean.
121
121
  */
@@ -356,7 +356,7 @@ interface GetConfigOptions {
356
356
  * All methods are async and return `Promise<T>`. Network and server
357
357
  * errors are mapped to typed SDK exceptions.
358
358
  *
359
- * Obtained via `SmplkitClient.config`.
359
+ * Obtained via `SmplClient.config`.
360
360
  */
361
361
  declare class ConfigClient {
362
362
  /** @internal — used by Config instances for reconnecting and WebSocket auth. */
@@ -404,16 +404,20 @@ declare class ConfigClient {
404
404
  }
405
405
 
406
406
  /**
407
- * Top-level SDK client — SmplkitClient.
407
+ * Top-level SDK client — SmplClient.
408
408
  *
409
409
  * The main entry point for the smplkit TypeScript SDK. Provides access
410
410
  * to sub-clients for each API domain (config, flags, logging, etc.).
411
411
  */
412
412
 
413
- /** Configuration options for the {@link SmplkitClient}. */
414
- interface SmplkitClientOptions {
415
- /** API key for authenticating with the smplkit platform. */
416
- apiKey: string;
413
+ /** Configuration options for the {@link SmplClient}. */
414
+ interface SmplClientOptions {
415
+ /**
416
+ * API key for authenticating with the smplkit platform.
417
+ * When omitted, the SDK resolves it from the `SMPLKIT_API_KEY`
418
+ * environment variable or the `~/.smplkit` configuration file.
419
+ */
420
+ apiKey?: string;
417
421
  /**
418
422
  * Request timeout in milliseconds.
419
423
  * @default 30000
@@ -425,16 +429,16 @@ interface SmplkitClientOptions {
425
429
  *
426
430
  * @example
427
431
  * ```typescript
428
- * import { SmplkitClient } from "@smplkit/sdk";
432
+ * import { SmplClient } from "@smplkit/sdk";
429
433
  *
430
- * const client = new SmplkitClient({ apiKey: "sk_api_..." });
434
+ * const client = new SmplClient({ apiKey: "sk_api_..." });
431
435
  * const cfg = await client.config.get({ key: "common" });
432
436
  * ```
433
437
  */
434
- declare class SmplkitClient {
438
+ declare class SmplClient {
435
439
  /** Client for config management-plane operations. */
436
440
  readonly config: ConfigClient;
437
- constructor(options: SmplkitClientOptions);
441
+ constructor(options?: SmplClientOptions);
438
442
  }
439
443
 
440
444
  /**
@@ -473,4 +477,4 @@ declare class SmplValidationError extends SmplError {
473
477
  constructor(message: string, statusCode?: number, responseBody?: string);
474
478
  }
475
479
 
476
- export { Config, type ConfigChangeEvent, ConfigClient, ConfigRuntime, type ConfigStats, type ConnectOptions, type ConnectionStatus, type CreateConfigOptions, type GetConfigOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotFoundError, SmplTimeoutError, SmplValidationError, SmplkitClient, type SmplkitClientOptions };
480
+ export { Config, type ConfigChangeEvent, ConfigClient, ConfigRuntime, type ConfigStats, type ConnectOptions, type ConnectionStatus, type CreateConfigOptions, type GetConfigOptions, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotFoundError, SmplTimeoutError, SmplValidationError };
package/dist/index.d.ts CHANGED
@@ -115,7 +115,7 @@ declare class ConfigRuntime {
115
115
  /**
116
116
  * Return the value as a number, or `defaultValue` if absent or not a number.
117
117
  */
118
- getNumber(key: string, defaultValue?: number | null): number | null;
118
+ getInt(key: string, defaultValue?: number | null): number | null;
119
119
  /**
120
120
  * Return the value as a boolean, or `defaultValue` if absent or not a boolean.
121
121
  */
@@ -356,7 +356,7 @@ interface GetConfigOptions {
356
356
  * All methods are async and return `Promise<T>`. Network and server
357
357
  * errors are mapped to typed SDK exceptions.
358
358
  *
359
- * Obtained via `SmplkitClient.config`.
359
+ * Obtained via `SmplClient.config`.
360
360
  */
361
361
  declare class ConfigClient {
362
362
  /** @internal — used by Config instances for reconnecting and WebSocket auth. */
@@ -404,16 +404,20 @@ declare class ConfigClient {
404
404
  }
405
405
 
406
406
  /**
407
- * Top-level SDK client — SmplkitClient.
407
+ * Top-level SDK client — SmplClient.
408
408
  *
409
409
  * The main entry point for the smplkit TypeScript SDK. Provides access
410
410
  * to sub-clients for each API domain (config, flags, logging, etc.).
411
411
  */
412
412
 
413
- /** Configuration options for the {@link SmplkitClient}. */
414
- interface SmplkitClientOptions {
415
- /** API key for authenticating with the smplkit platform. */
416
- apiKey: string;
413
+ /** Configuration options for the {@link SmplClient}. */
414
+ interface SmplClientOptions {
415
+ /**
416
+ * API key for authenticating with the smplkit platform.
417
+ * When omitted, the SDK resolves it from the `SMPLKIT_API_KEY`
418
+ * environment variable or the `~/.smplkit` configuration file.
419
+ */
420
+ apiKey?: string;
417
421
  /**
418
422
  * Request timeout in milliseconds.
419
423
  * @default 30000
@@ -425,16 +429,16 @@ interface SmplkitClientOptions {
425
429
  *
426
430
  * @example
427
431
  * ```typescript
428
- * import { SmplkitClient } from "@smplkit/sdk";
432
+ * import { SmplClient } from "@smplkit/sdk";
429
433
  *
430
- * const client = new SmplkitClient({ apiKey: "sk_api_..." });
434
+ * const client = new SmplClient({ apiKey: "sk_api_..." });
431
435
  * const cfg = await client.config.get({ key: "common" });
432
436
  * ```
433
437
  */
434
- declare class SmplkitClient {
438
+ declare class SmplClient {
435
439
  /** Client for config management-plane operations. */
436
440
  readonly config: ConfigClient;
437
- constructor(options: SmplkitClientOptions);
441
+ constructor(options?: SmplClientOptions);
438
442
  }
439
443
 
440
444
  /**
@@ -473,4 +477,4 @@ declare class SmplValidationError extends SmplError {
473
477
  constructor(message: string, statusCode?: number, responseBody?: string);
474
478
  }
475
479
 
476
- export { Config, type ConfigChangeEvent, ConfigClient, ConfigRuntime, type ConfigStats, type ConnectOptions, type ConnectionStatus, type CreateConfigOptions, type GetConfigOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotFoundError, SmplTimeoutError, SmplValidationError, SmplkitClient, type SmplkitClientOptions };
480
+ export { Config, type ConfigChangeEvent, ConfigClient, ConfigRuntime, type ConfigStats, type ConnectOptions, type ConnectionStatus, type CreateConfigOptions, type GetConfigOptions, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotFoundError, SmplTimeoutError, SmplValidationError };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ConfigRuntime
3
- } from "./chunk-PZD5PSQY.js";
3
+ } from "./chunk-GLOLTIGH.js";
4
4
 
5
5
  // src/config/client.ts
6
6
  import createClient from "openapi-fetch";
@@ -207,7 +207,7 @@ var Config = class {
207
207
  * @param options.timeout - Milliseconds to wait for the initial fetch.
208
208
  */
209
209
  async connect(environment, options) {
210
- const { ConfigRuntime: ConfigRuntime2 } = await import("./runtime-CCRTBKED.js");
210
+ const { ConfigRuntime: ConfigRuntime2 } = await import("./runtime-GALKHNKF.js");
211
211
  const timeout = options?.timeout ?? 3e4;
212
212
  const chain = await this._buildChain(timeout);
213
213
  return new ConfigRuntime2({
@@ -473,27 +473,44 @@ var ConfigClient = class {
473
473
  }
474
474
  };
475
475
 
476
+ // src/resolve.ts
477
+ import { readFileSync } from "fs";
478
+ import { homedir } from "os";
479
+ import { join } from "path";
480
+ var NO_API_KEY_MESSAGE = "No API key provided. Set one of:\n 1. Pass apiKey to the constructor\n 2. Set the SMPLKIT_API_KEY environment variable\n 3. Add api_key to [default] in ~/.smplkit";
481
+ function resolveApiKey(explicit) {
482
+ if (explicit) return explicit;
483
+ const envVal = process.env.SMPLKIT_API_KEY;
484
+ if (envVal) return envVal;
485
+ const configPath = join(homedir(), ".smplkit");
486
+ try {
487
+ const content = readFileSync(configPath, "utf-8");
488
+ const match = content.match(/\[default\]\s*[\s\S]*?api_key\s*=\s*"([^"]+)"/);
489
+ if (match?.[1]) return match[1];
490
+ } catch {
491
+ }
492
+ throw new SmplError(NO_API_KEY_MESSAGE);
493
+ }
494
+
476
495
  // src/client.ts
477
- var SmplkitClient = class {
496
+ var SmplClient = class {
478
497
  /** Client for config management-plane operations. */
479
498
  config;
480
- constructor(options) {
481
- if (!options.apiKey) {
482
- throw new Error("apiKey is required");
483
- }
484
- this.config = new ConfigClient(options.apiKey, options.timeout);
499
+ constructor(options = {}) {
500
+ const apiKey = resolveApiKey(options.apiKey);
501
+ this.config = new ConfigClient(apiKey, options.timeout);
485
502
  }
486
503
  };
487
504
  export {
488
505
  Config,
489
506
  ConfigClient,
490
507
  ConfigRuntime,
508
+ SmplClient,
491
509
  SmplConflictError,
492
510
  SmplConnectionError,
493
511
  SmplError,
494
512
  SmplNotFoundError,
495
513
  SmplTimeoutError,
496
- SmplValidationError,
497
- SmplkitClient
514
+ SmplValidationError
498
515
  };
499
516
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config/client.ts","../src/errors.ts","../src/config/types.ts","../src/client.ts"],"sourcesContent":["/**\n * ConfigClient — management-plane operations for configs.\n *\n * Uses the generated OpenAPI types (`src/generated/config.d.ts`) via\n * `openapi-fetch` for all HTTP calls, keeping the client layer fully\n * type-safe without hand-coded request/response shapes.\n */\n\nimport createClient from \"openapi-fetch\";\nimport type { components, operations } from \"../generated/config.d.ts\";\nimport {\n SmplConflictError,\n SmplNotFoundError,\n SmplValidationError,\n SmplError,\n SmplConnectionError,\n SmplTimeoutError,\n} from \"../errors.js\";\nimport { Config } from \"./types.js\";\nimport type { ConfigUpdatePayload, CreateConfigOptions, GetConfigOptions } from \"./types.js\";\n\nconst BASE_URL = \"https://config.smplkit.com\";\n\ntype ApiConfig = components[\"schemas\"][\"Config\"];\ntype ConfigResource = components[\"schemas\"][\"ConfigResource\"];\n\n/** @internal */\nfunction resourceToConfig(resource: ConfigResource, client: ConfigClient): Config {\n const attrs: ApiConfig = resource.attributes;\n return new Config(client, {\n id: resource.id ?? \"\",\n key: attrs.key ?? \"\",\n name: attrs.name,\n description: attrs.description ?? null,\n parent: attrs.parent ?? null,\n values: (attrs.values ?? {}) as Record<string, unknown>,\n environments: (attrs.environments ?? {}) as Record<string, unknown>,\n createdAt: attrs.created_at ? new Date(attrs.created_at) : null,\n updatedAt: attrs.updated_at ? new Date(attrs.updated_at) : null,\n });\n}\n\n/**\n * Map fetch or HTTP errors to typed SDK exceptions.\n * @internal\n */\nasync function checkError(response: Response, context: string): Promise<never> {\n const body = await response.text().catch(() => \"\");\n switch (response.status) {\n case 404:\n throw new SmplNotFoundError(body || context, 404, body);\n case 409:\n throw new SmplConflictError(body || context, 409, body);\n case 422:\n throw new SmplValidationError(body || context, 422, body);\n default:\n throw new SmplError(`HTTP ${response.status}: ${body}`, response.status, body);\n }\n}\n\n/**\n * Re-raise fetch-level errors (network, timeout) as typed SDK exceptions.\n * @internal\n */\nfunction wrapFetchError(err: unknown): never {\n if (\n err instanceof SmplNotFoundError ||\n err instanceof SmplConflictError ||\n err instanceof SmplValidationError ||\n err instanceof SmplError\n ) {\n throw err;\n }\n if (err instanceof TypeError) {\n throw new SmplConnectionError(`Network error: ${err.message}`);\n }\n throw new SmplConnectionError(\n `Request failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n}\n\n/**\n * Build a JSON:API request body for create/update operations.\n * @internal\n */\nfunction buildRequestBody(options: {\n id?: string | null;\n name: string;\n key?: string | null;\n description?: string | null;\n parent?: string | null;\n values?: Record<string, unknown> | null;\n environments?: Record<string, unknown> | null;\n}): operations[\"create_config\"][\"requestBody\"][\"content\"][\"application/json\"] {\n const attrs: ApiConfig = {\n name: options.name,\n };\n if (options.key !== undefined) attrs.key = options.key;\n if (options.description !== undefined) attrs.description = options.description;\n if (options.parent !== undefined) attrs.parent = options.parent;\n if (options.values !== undefined)\n attrs.values = options.values as { [key: string]: unknown } | null;\n if (options.environments !== undefined)\n attrs.environments = options.environments as { [key: string]: unknown } | null;\n\n return {\n data: {\n id: options.id ?? null,\n type: \"config\",\n attributes: attrs,\n },\n };\n}\n\n/**\n * Client for the smplkit Config API (management plane).\n *\n * All methods are async and return `Promise<T>`. Network and server\n * errors are mapped to typed SDK exceptions.\n *\n * Obtained via `SmplkitClient.config`.\n */\nexport class ConfigClient {\n /** @internal — used by Config instances for reconnecting and WebSocket auth. */\n readonly _apiKey: string;\n\n /** @internal */\n readonly _baseUrl: string = BASE_URL;\n\n /** @internal */\n private readonly _http: ReturnType<typeof createClient<import(\"../generated/config.d.ts\").paths>>;\n\n /** @internal */\n constructor(apiKey: string, timeout?: number) {\n this._apiKey = apiKey;\n const ms = timeout ?? 30_000;\n this._http = createClient<import(\"../generated/config.d.ts\").paths>({\n baseUrl: BASE_URL,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n Accept: \"application/json\",\n },\n // openapi-fetch custom fetch receives a pre-built Request object\n fetch: async (request: Request): Promise<Response> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), ms);\n try {\n return await fetch(new Request(request, { signal: controller.signal }));\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new SmplTimeoutError(`Request timed out after ${ms}ms`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n },\n });\n }\n\n /**\n * Fetch a single config by key or UUID.\n *\n * Exactly one of `key` or `id` must be provided.\n *\n * @throws {SmplNotFoundError} If no matching config exists.\n */\n async get(options: GetConfigOptions): Promise<Config> {\n const { key, id } = options;\n if ((key === undefined) === (id === undefined)) {\n throw new Error(\"Exactly one of 'key' or 'id' must be provided.\");\n }\n return id !== undefined ? this._getById(id) : this._getByKey(key!);\n }\n\n /**\n * List all configs for the account.\n */\n async list(): Promise<Config[]> {\n let data: components[\"schemas\"][\"ConfigListResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs\", {});\n if (result.error !== undefined) await checkError(result.response, \"Failed to list configs\");\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data) return [];\n return data.data.map((r) => resourceToConfig(r, this));\n }\n\n /**\n * Create a new config.\n *\n * @throws {SmplValidationError} If the server rejects the request.\n */\n async create(options: CreateConfigOptions): Promise<Config> {\n const body = buildRequestBody({\n name: options.name,\n key: options.key,\n description: options.description,\n parent: options.parent,\n values: options.values,\n });\n\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.POST(\"/api/v1/configs\", { body });\n if (result.error !== undefined) await checkError(result.response, \"Failed to create config\");\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data) throw new SmplValidationError(\"Failed to create config\");\n return resourceToConfig(data.data, this);\n }\n\n /**\n * Delete a config by UUID.\n *\n * @throws {SmplNotFoundError} If the config does not exist.\n * @throws {SmplConflictError} If the config has child configs.\n */\n async delete(configId: string): Promise<void> {\n try {\n const result = await this._http.DELETE(\"/api/v1/configs/{id}\", {\n params: { path: { id: configId } },\n });\n if (result.error !== undefined && result.response.status !== 204)\n await checkError(result.response, `Failed to delete config ${configId}`);\n } catch (err) {\n wrapFetchError(err);\n }\n }\n\n /**\n * Internal: PUT a full config update and return the updated model.\n *\n * Called by {@link Config} instance methods.\n * @internal\n */\n async _updateConfig(payload: ConfigUpdatePayload): Promise<Config> {\n const body = buildRequestBody({\n id: payload.configId,\n name: payload.name,\n key: payload.key,\n description: payload.description,\n parent: payload.parent,\n values: payload.values,\n environments: payload.environments,\n });\n\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.PUT(\"/api/v1/configs/{id}\", {\n params: { path: { id: payload.configId } },\n body,\n });\n if (result.error !== undefined)\n await checkError(result.response, `Failed to update config ${payload.configId}`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data)\n throw new SmplValidationError(`Failed to update config ${payload.configId}`);\n return resourceToConfig(data.data, this);\n }\n\n // ---- Private helpers ----\n\n private async _getById(configId: string): Promise<Config> {\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs/{id}\", {\n params: { path: { id: configId } },\n });\n if (result.error !== undefined)\n await checkError(result.response, `Config ${configId} not found`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data) throw new SmplNotFoundError(`Config ${configId} not found`);\n return resourceToConfig(data.data, this);\n }\n\n private async _getByKey(key: string): Promise<Config> {\n let data: components[\"schemas\"][\"ConfigListResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs\", {\n params: { query: { \"filter[key]\": key } },\n });\n if (result.error !== undefined)\n await checkError(result.response, `Config with key '${key}' not found`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data || data.data.length === 0) {\n throw new SmplNotFoundError(`Config with key '${key}' not found`);\n }\n return resourceToConfig(data.data[0], this);\n }\n}\n","/**\n * Structured SDK error types.\n *\n * All smplkit errors extend {@link SmplError}, allowing callers to catch\n * the base class for generic handling or specific subclasses for\n * fine-grained control.\n */\n\n/** Base exception for all smplkit SDK errors. */\nexport class SmplError extends Error {\n /** The HTTP status code, if the error originated from an HTTP response. */\n public readonly statusCode?: number;\n\n /** The raw response body, if available. */\n public readonly responseBody?: string;\n\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message);\n this.name = \"SmplError\";\n this.statusCode = statusCode;\n this.responseBody = responseBody;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when a network request fails (e.g., DNS resolution, connection refused). */\nexport class SmplConnectionError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode, responseBody);\n this.name = \"SmplConnectionError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when an operation exceeds its timeout. */\nexport class SmplTimeoutError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode, responseBody);\n this.name = \"SmplTimeoutError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when a requested resource does not exist (HTTP 404). */\nexport class SmplNotFoundError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 404, responseBody);\n this.name = \"SmplNotFoundError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when an operation conflicts with current state (HTTP 409). */\nexport class SmplConflictError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 409, responseBody);\n this.name = \"SmplConflictError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when the server rejects a request due to validation errors (HTTP 422). */\nexport class SmplValidationError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 422, responseBody);\n this.name = \"SmplValidationError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Config resource — management-plane model with runtime connect support.\n *\n * Instances are returned by {@link ConfigClient} methods and provide\n * management-plane operations (`update`, `setValues`, `setValue`) as well\n * as the {@link connect} entry point for runtime value resolution.\n */\n\nimport type { ConfigRuntime } from \"./runtime.js\";\nimport type { ConnectOptions } from \"./runtime-types.js\";\n\n/**\n * Internal type used by {@link ConfigClient}. Not part of the public API.\n * @internal\n */\nexport interface ConfigUpdatePayload {\n configId: string;\n name: string;\n key: string | null;\n description: string | null;\n parent: string | null;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n}\n\n/**\n * A configuration resource fetched from the smplkit Config service.\n *\n * Instances are returned by {@link ConfigClient} methods and provide\n * management-plane operations as well as the {@link connect} entry point\n * for runtime value resolution.\n */\nexport class Config {\n /** UUID of the config. */\n id: string;\n\n /** Human-readable key (e.g. `\"user_service\"`). */\n key: string;\n\n /** Display name. */\n name: string;\n\n /** Optional description. */\n description: string | null;\n\n /** Parent config UUID, or null if this is a root config. */\n parent: string | null;\n\n /** Base key-value pairs. */\n values: Record<string, unknown>;\n\n /**\n * Per-environment overrides.\n * Stored as `{ env_name: { values: { key: value } } }` to match the\n * server's format.\n */\n environments: Record<string, unknown>;\n\n /** When the config was created, or null if unavailable. */\n createdAt: Date | null;\n\n /** When the config was last updated, or null if unavailable. */\n updatedAt: Date | null;\n\n /**\n * Internal reference to the parent client.\n * @internal\n */\n private readonly _client: {\n _updateConfig(payload: ConfigUpdatePayload): Promise<Config>;\n get(options: { id: string }): Promise<Config>;\n readonly _apiKey: string;\n readonly _baseUrl: string;\n };\n\n /** @internal */\n constructor(\n client: {\n _updateConfig(payload: ConfigUpdatePayload): Promise<Config>;\n get(options: { id: string }): Promise<Config>;\n readonly _apiKey: string;\n readonly _baseUrl: string;\n },\n fields: {\n id: string;\n key: string;\n name: string;\n description: string | null;\n parent: string | null;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n createdAt: Date | null;\n updatedAt: Date | null;\n },\n ) {\n this._client = client;\n this.id = fields.id;\n this.key = fields.key;\n this.name = fields.name;\n this.description = fields.description;\n this.parent = fields.parent;\n this.values = fields.values;\n this.environments = fields.environments;\n this.createdAt = fields.createdAt;\n this.updatedAt = fields.updatedAt;\n }\n\n /**\n * Update this config's attributes on the server.\n *\n * Builds the request from current attribute values, overriding with any\n * provided options. Updates local attributes in place on success.\n *\n * @param options.name - New display name.\n * @param options.description - New description (pass empty string to clear).\n * @param options.values - New base values (replaces entirely).\n * @param options.environments - New environments dict (replaces entirely).\n */\n async update(options: {\n name?: string;\n description?: string;\n values?: Record<string, unknown>;\n environments?: Record<string, unknown>;\n }): Promise<void> {\n const updated = await this._client._updateConfig({\n configId: this.id,\n name: options.name ?? this.name,\n key: this.key,\n description: options.description !== undefined ? options.description : this.description,\n parent: this.parent,\n values: options.values ?? this.values,\n environments: options.environments ?? this.environments,\n });\n this.name = updated.name;\n this.description = updated.description;\n this.values = updated.values;\n this.environments = updated.environments;\n this.updatedAt = updated.updatedAt;\n }\n\n /**\n * Replace base or environment-specific values.\n *\n * When `environment` is provided, replaces that environment's `values`\n * sub-dict (other environments are preserved). When omitted, replaces\n * the base `values`.\n *\n * @param values - The complete set of values to set.\n * @param environment - Target environment, or omit for base values.\n */\n async setValues(values: Record<string, unknown>, environment?: string): Promise<void> {\n let newValues: Record<string, unknown>;\n let newEnvs: Record<string, unknown>;\n\n if (environment === undefined) {\n newValues = values;\n newEnvs = this.environments;\n } else {\n newValues = this.values;\n // Preserve any extra metadata on the environment entry (like other sub-keys),\n // but replace the `values` sub-dict entirely.\n const existingEntry =\n typeof this.environments[environment] === \"object\" &&\n this.environments[environment] !== null\n ? { ...(this.environments[environment] as Record<string, unknown>) }\n : {};\n existingEntry.values = values;\n newEnvs = { ...this.environments, [environment]: existingEntry };\n }\n\n const updated = await this._client._updateConfig({\n configId: this.id,\n name: this.name,\n key: this.key,\n description: this.description,\n parent: this.parent,\n values: newValues,\n environments: newEnvs,\n });\n this.values = updated.values;\n this.environments = updated.environments;\n this.updatedAt = updated.updatedAt;\n }\n\n /**\n * Set a single key within base or environment-specific values.\n *\n * Merges the key into existing values rather than replacing all values.\n *\n * @param key - The config key to set.\n * @param value - The value to assign.\n * @param environment - Target environment, or omit for base values.\n */\n async setValue(key: string, value: unknown, environment?: string): Promise<void> {\n if (environment === undefined) {\n const merged = { ...this.values, [key]: value };\n await this.setValues(merged);\n } else {\n const envEntry =\n typeof this.environments[environment] === \"object\" &&\n this.environments[environment] !== null\n ? (this.environments[environment] as Record<string, unknown>)\n : {};\n const existing = {\n ...(typeof envEntry.values === \"object\" && envEntry.values !== null\n ? (envEntry.values as Record<string, unknown>)\n : {}),\n };\n existing[key] = value;\n await this.setValues(existing, environment);\n }\n }\n\n /**\n * Connect to this config for runtime value resolution.\n *\n * Eagerly fetches this config and its full parent chain, resolves values\n * for the given environment via deep merge, and returns a\n * {@link ConfigRuntime} with a fully populated local cache.\n *\n * A background WebSocket connection is started for real-time updates.\n * If the WebSocket fails to connect, the runtime operates in cache-only\n * mode and reconnects automatically.\n *\n * Supports both `await` and `await using` (TypeScript 5.2+)::\n *\n * ```typescript\n * // Simple await\n * const runtime = await config.connect(\"production\");\n * try { ... } finally { await runtime.close(); }\n *\n * // await using (auto-close)\n * await using runtime = await config.connect(\"production\");\n * ```\n *\n * @param environment - The environment to resolve for (e.g. `\"production\"`).\n * @param options.timeout - Milliseconds to wait for the initial fetch.\n */\n async connect(environment: string, options?: ConnectOptions): Promise<ConfigRuntime> {\n // Lazy import to avoid loading ws at module-init time\n const { ConfigRuntime } = await import(\"./runtime.js\");\n\n const timeout = options?.timeout ?? 30_000;\n const chain = await this._buildChain(timeout);\n\n return new ConfigRuntime({\n configKey: this.key,\n configId: this.id,\n environment,\n chain,\n apiKey: this._client._apiKey,\n baseUrl: this._client._baseUrl,\n fetchChain: () => this._buildChain(timeout),\n });\n }\n\n /**\n * Walk the parent chain and return config data objects, child-to-root.\n * @internal\n */\n private async _buildChain(\n _timeout: number,\n ): Promise<\n Array<{ id: string; values: Record<string, unknown>; environments: Record<string, unknown> }>\n > {\n const chain: Array<{\n id: string;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n }> = [{ id: this.id, values: this.values, environments: this.environments }];\n\n let parentId = this.parent;\n while (parentId !== null) {\n const parentConfig = await this._client.get({ id: parentId });\n chain.push({\n id: parentConfig.id,\n values: parentConfig.values,\n environments: parentConfig.environments,\n });\n parentId = parentConfig.parent;\n }\n\n return chain;\n }\n\n toString(): string {\n return `Config(id=${this.id}, key=${this.key}, name=${this.name})`;\n }\n}\n\n/** Options for creating a new config. */\nexport interface CreateConfigOptions {\n /** Display name for the config. */\n name: string;\n /** Human-readable key. Auto-generated by the server if omitted. */\n key?: string;\n /** Optional description. */\n description?: string;\n /** Parent config UUID. Defaults to the account's `common` config if omitted. */\n parent?: string;\n /** Initial base values. */\n values?: Record<string, unknown>;\n}\n\n/** Options for fetching a single config. Exactly one of `key` or `id` must be provided. */\nexport interface GetConfigOptions {\n /** Fetch by human-readable key. */\n key?: string;\n /** Fetch by UUID. */\n id?: string;\n}\n","/**\n * Top-level SDK client — SmplkitClient.\n *\n * The main entry point for the smplkit TypeScript SDK. Provides access\n * to sub-clients for each API domain (config, flags, logging, etc.).\n */\n\nimport { ConfigClient } from \"./config/client.js\";\n\n/** Configuration options for the {@link SmplkitClient}. */\nexport interface SmplkitClientOptions {\n /** API key for authenticating with the smplkit platform. */\n apiKey: string;\n\n /**\n * Request timeout in milliseconds.\n * @default 30000\n */\n timeout?: number;\n}\n\n/**\n * Entry point for the smplkit TypeScript SDK.\n *\n * @example\n * ```typescript\n * import { SmplkitClient } from \"@smplkit/sdk\";\n *\n * const client = new SmplkitClient({ apiKey: \"sk_api_...\" });\n * const cfg = await client.config.get({ key: \"common\" });\n * ```\n */\nexport class SmplkitClient {\n /** Client for config management-plane operations. */\n readonly config: ConfigClient;\n\n constructor(options: SmplkitClientOptions) {\n if (!options.apiKey) {\n throw new Error(\"apiKey is required\");\n }\n\n this.config = new ConfigClient(options.apiKey, options.timeout);\n }\n}\n"],"mappings":"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","names":["ConfigRuntime"]}
1
+ {"version":3,"sources":["../src/config/client.ts","../src/errors.ts","../src/config/types.ts","../src/resolve.ts","../src/client.ts"],"sourcesContent":["/**\n * ConfigClient — management-plane operations for configs.\n *\n * Uses the generated OpenAPI types (`src/generated/config.d.ts`) via\n * `openapi-fetch` for all HTTP calls, keeping the client layer fully\n * type-safe without hand-coded request/response shapes.\n */\n\nimport createClient from \"openapi-fetch\";\nimport type { components, operations } from \"../generated/config.d.ts\";\nimport {\n SmplConflictError,\n SmplNotFoundError,\n SmplValidationError,\n SmplError,\n SmplConnectionError,\n SmplTimeoutError,\n} from \"../errors.js\";\nimport { Config } from \"./types.js\";\nimport type { ConfigUpdatePayload, CreateConfigOptions, GetConfigOptions } from \"./types.js\";\n\nconst BASE_URL = \"https://config.smplkit.com\";\n\ntype ApiConfig = components[\"schemas\"][\"Config\"];\ntype ConfigResource = components[\"schemas\"][\"ConfigResource\"];\n\n/** @internal */\nfunction resourceToConfig(resource: ConfigResource, client: ConfigClient): Config {\n const attrs: ApiConfig = resource.attributes;\n return new Config(client, {\n id: resource.id ?? \"\",\n key: attrs.key ?? \"\",\n name: attrs.name,\n description: attrs.description ?? null,\n parent: attrs.parent ?? null,\n values: (attrs.values ?? {}) as Record<string, unknown>,\n environments: (attrs.environments ?? {}) as Record<string, unknown>,\n createdAt: attrs.created_at ? new Date(attrs.created_at) : null,\n updatedAt: attrs.updated_at ? new Date(attrs.updated_at) : null,\n });\n}\n\n/**\n * Map fetch or HTTP errors to typed SDK exceptions.\n * @internal\n */\nasync function checkError(response: Response, context: string): Promise<never> {\n const body = await response.text().catch(() => \"\");\n switch (response.status) {\n case 404:\n throw new SmplNotFoundError(body || context, 404, body);\n case 409:\n throw new SmplConflictError(body || context, 409, body);\n case 422:\n throw new SmplValidationError(body || context, 422, body);\n default:\n throw new SmplError(`HTTP ${response.status}: ${body}`, response.status, body);\n }\n}\n\n/**\n * Re-raise fetch-level errors (network, timeout) as typed SDK exceptions.\n * @internal\n */\nfunction wrapFetchError(err: unknown): never {\n if (\n err instanceof SmplNotFoundError ||\n err instanceof SmplConflictError ||\n err instanceof SmplValidationError ||\n err instanceof SmplError\n ) {\n throw err;\n }\n if (err instanceof TypeError) {\n throw new SmplConnectionError(`Network error: ${err.message}`);\n }\n throw new SmplConnectionError(\n `Request failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n}\n\n/**\n * Build a JSON:API request body for create/update operations.\n * @internal\n */\nfunction buildRequestBody(options: {\n id?: string | null;\n name: string;\n key?: string | null;\n description?: string | null;\n parent?: string | null;\n values?: Record<string, unknown> | null;\n environments?: Record<string, unknown> | null;\n}): operations[\"create_config\"][\"requestBody\"][\"content\"][\"application/json\"] {\n const attrs: ApiConfig = {\n name: options.name,\n };\n if (options.key !== undefined) attrs.key = options.key;\n if (options.description !== undefined) attrs.description = options.description;\n if (options.parent !== undefined) attrs.parent = options.parent;\n if (options.values !== undefined)\n attrs.values = options.values as { [key: string]: unknown } | null;\n if (options.environments !== undefined)\n attrs.environments = options.environments as { [key: string]: unknown } | null;\n\n return {\n data: {\n id: options.id ?? null,\n type: \"config\",\n attributes: attrs,\n },\n };\n}\n\n/**\n * Client for the smplkit Config API (management plane).\n *\n * All methods are async and return `Promise<T>`. Network and server\n * errors are mapped to typed SDK exceptions.\n *\n * Obtained via `SmplClient.config`.\n */\nexport class ConfigClient {\n /** @internal — used by Config instances for reconnecting and WebSocket auth. */\n readonly _apiKey: string;\n\n /** @internal */\n readonly _baseUrl: string = BASE_URL;\n\n /** @internal */\n private readonly _http: ReturnType<typeof createClient<import(\"../generated/config.d.ts\").paths>>;\n\n /** @internal */\n constructor(apiKey: string, timeout?: number) {\n this._apiKey = apiKey;\n const ms = timeout ?? 30_000;\n this._http = createClient<import(\"../generated/config.d.ts\").paths>({\n baseUrl: BASE_URL,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n Accept: \"application/json\",\n },\n // openapi-fetch custom fetch receives a pre-built Request object\n fetch: async (request: Request): Promise<Response> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), ms);\n try {\n return await fetch(new Request(request, { signal: controller.signal }));\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new SmplTimeoutError(`Request timed out after ${ms}ms`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n },\n });\n }\n\n /**\n * Fetch a single config by key or UUID.\n *\n * Exactly one of `key` or `id` must be provided.\n *\n * @throws {SmplNotFoundError} If no matching config exists.\n */\n async get(options: GetConfigOptions): Promise<Config> {\n const { key, id } = options;\n if ((key === undefined) === (id === undefined)) {\n throw new Error(\"Exactly one of 'key' or 'id' must be provided.\");\n }\n return id !== undefined ? this._getById(id) : this._getByKey(key!);\n }\n\n /**\n * List all configs for the account.\n */\n async list(): Promise<Config[]> {\n let data: components[\"schemas\"][\"ConfigListResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs\", {});\n if (result.error !== undefined) await checkError(result.response, \"Failed to list configs\");\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data) return [];\n return data.data.map((r) => resourceToConfig(r, this));\n }\n\n /**\n * Create a new config.\n *\n * @throws {SmplValidationError} If the server rejects the request.\n */\n async create(options: CreateConfigOptions): Promise<Config> {\n const body = buildRequestBody({\n name: options.name,\n key: options.key,\n description: options.description,\n parent: options.parent,\n values: options.values,\n });\n\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.POST(\"/api/v1/configs\", { body });\n if (result.error !== undefined) await checkError(result.response, \"Failed to create config\");\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data) throw new SmplValidationError(\"Failed to create config\");\n return resourceToConfig(data.data, this);\n }\n\n /**\n * Delete a config by UUID.\n *\n * @throws {SmplNotFoundError} If the config does not exist.\n * @throws {SmplConflictError} If the config has child configs.\n */\n async delete(configId: string): Promise<void> {\n try {\n const result = await this._http.DELETE(\"/api/v1/configs/{id}\", {\n params: { path: { id: configId } },\n });\n if (result.error !== undefined && result.response.status !== 204)\n await checkError(result.response, `Failed to delete config ${configId}`);\n } catch (err) {\n wrapFetchError(err);\n }\n }\n\n /**\n * Internal: PUT a full config update and return the updated model.\n *\n * Called by {@link Config} instance methods.\n * @internal\n */\n async _updateConfig(payload: ConfigUpdatePayload): Promise<Config> {\n const body = buildRequestBody({\n id: payload.configId,\n name: payload.name,\n key: payload.key,\n description: payload.description,\n parent: payload.parent,\n values: payload.values,\n environments: payload.environments,\n });\n\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.PUT(\"/api/v1/configs/{id}\", {\n params: { path: { id: payload.configId } },\n body,\n });\n if (result.error !== undefined)\n await checkError(result.response, `Failed to update config ${payload.configId}`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data)\n throw new SmplValidationError(`Failed to update config ${payload.configId}`);\n return resourceToConfig(data.data, this);\n }\n\n // ---- Private helpers ----\n\n private async _getById(configId: string): Promise<Config> {\n let data: components[\"schemas\"][\"ConfigResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs/{id}\", {\n params: { path: { id: configId } },\n });\n if (result.error !== undefined)\n await checkError(result.response, `Config ${configId} not found`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data) throw new SmplNotFoundError(`Config ${configId} not found`);\n return resourceToConfig(data.data, this);\n }\n\n private async _getByKey(key: string): Promise<Config> {\n let data: components[\"schemas\"][\"ConfigListResponse\"] | undefined;\n try {\n const result = await this._http.GET(\"/api/v1/configs\", {\n params: { query: { \"filter[key]\": key } },\n });\n if (result.error !== undefined)\n await checkError(result.response, `Config with key '${key}' not found`);\n data = result.data;\n } catch (err) {\n wrapFetchError(err);\n }\n if (!data || !data.data || data.data.length === 0) {\n throw new SmplNotFoundError(`Config with key '${key}' not found`);\n }\n return resourceToConfig(data.data[0], this);\n }\n}\n","/**\n * Structured SDK error types.\n *\n * All smplkit errors extend {@link SmplError}, allowing callers to catch\n * the base class for generic handling or specific subclasses for\n * fine-grained control.\n */\n\n/** Base exception for all smplkit SDK errors. */\nexport class SmplError extends Error {\n /** The HTTP status code, if the error originated from an HTTP response. */\n public readonly statusCode?: number;\n\n /** The raw response body, if available. */\n public readonly responseBody?: string;\n\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message);\n this.name = \"SmplError\";\n this.statusCode = statusCode;\n this.responseBody = responseBody;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when a network request fails (e.g., DNS resolution, connection refused). */\nexport class SmplConnectionError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode, responseBody);\n this.name = \"SmplConnectionError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when an operation exceeds its timeout. */\nexport class SmplTimeoutError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode, responseBody);\n this.name = \"SmplTimeoutError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when a requested resource does not exist (HTTP 404). */\nexport class SmplNotFoundError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 404, responseBody);\n this.name = \"SmplNotFoundError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when an operation conflicts with current state (HTTP 409). */\nexport class SmplConflictError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 409, responseBody);\n this.name = \"SmplConflictError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Raised when the server rejects a request due to validation errors (HTTP 422). */\nexport class SmplValidationError extends SmplError {\n constructor(message: string, statusCode?: number, responseBody?: string) {\n super(message, statusCode ?? 422, responseBody);\n this.name = \"SmplValidationError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Config resource — management-plane model with runtime connect support.\n *\n * Instances are returned by {@link ConfigClient} methods and provide\n * management-plane operations (`update`, `setValues`, `setValue`) as well\n * as the {@link connect} entry point for runtime value resolution.\n */\n\nimport type { ConfigRuntime } from \"./runtime.js\";\nimport type { ConnectOptions } from \"./runtime-types.js\";\n\n/**\n * Internal type used by {@link ConfigClient}. Not part of the public API.\n * @internal\n */\nexport interface ConfigUpdatePayload {\n configId: string;\n name: string;\n key: string | null;\n description: string | null;\n parent: string | null;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n}\n\n/**\n * A configuration resource fetched from the smplkit Config service.\n *\n * Instances are returned by {@link ConfigClient} methods and provide\n * management-plane operations as well as the {@link connect} entry point\n * for runtime value resolution.\n */\nexport class Config {\n /** UUID of the config. */\n id: string;\n\n /** Human-readable key (e.g. `\"user_service\"`). */\n key: string;\n\n /** Display name. */\n name: string;\n\n /** Optional description. */\n description: string | null;\n\n /** Parent config UUID, or null if this is a root config. */\n parent: string | null;\n\n /** Base key-value pairs. */\n values: Record<string, unknown>;\n\n /**\n * Per-environment overrides.\n * Stored as `{ env_name: { values: { key: value } } }` to match the\n * server's format.\n */\n environments: Record<string, unknown>;\n\n /** When the config was created, or null if unavailable. */\n createdAt: Date | null;\n\n /** When the config was last updated, or null if unavailable. */\n updatedAt: Date | null;\n\n /**\n * Internal reference to the parent client.\n * @internal\n */\n private readonly _client: {\n _updateConfig(payload: ConfigUpdatePayload): Promise<Config>;\n get(options: { id: string }): Promise<Config>;\n readonly _apiKey: string;\n readonly _baseUrl: string;\n };\n\n /** @internal */\n constructor(\n client: {\n _updateConfig(payload: ConfigUpdatePayload): Promise<Config>;\n get(options: { id: string }): Promise<Config>;\n readonly _apiKey: string;\n readonly _baseUrl: string;\n },\n fields: {\n id: string;\n key: string;\n name: string;\n description: string | null;\n parent: string | null;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n createdAt: Date | null;\n updatedAt: Date | null;\n },\n ) {\n this._client = client;\n this.id = fields.id;\n this.key = fields.key;\n this.name = fields.name;\n this.description = fields.description;\n this.parent = fields.parent;\n this.values = fields.values;\n this.environments = fields.environments;\n this.createdAt = fields.createdAt;\n this.updatedAt = fields.updatedAt;\n }\n\n /**\n * Update this config's attributes on the server.\n *\n * Builds the request from current attribute values, overriding with any\n * provided options. Updates local attributes in place on success.\n *\n * @param options.name - New display name.\n * @param options.description - New description (pass empty string to clear).\n * @param options.values - New base values (replaces entirely).\n * @param options.environments - New environments dict (replaces entirely).\n */\n async update(options: {\n name?: string;\n description?: string;\n values?: Record<string, unknown>;\n environments?: Record<string, unknown>;\n }): Promise<void> {\n const updated = await this._client._updateConfig({\n configId: this.id,\n name: options.name ?? this.name,\n key: this.key,\n description: options.description !== undefined ? options.description : this.description,\n parent: this.parent,\n values: options.values ?? this.values,\n environments: options.environments ?? this.environments,\n });\n this.name = updated.name;\n this.description = updated.description;\n this.values = updated.values;\n this.environments = updated.environments;\n this.updatedAt = updated.updatedAt;\n }\n\n /**\n * Replace base or environment-specific values.\n *\n * When `environment` is provided, replaces that environment's `values`\n * sub-dict (other environments are preserved). When omitted, replaces\n * the base `values`.\n *\n * @param values - The complete set of values to set.\n * @param environment - Target environment, or omit for base values.\n */\n async setValues(values: Record<string, unknown>, environment?: string): Promise<void> {\n let newValues: Record<string, unknown>;\n let newEnvs: Record<string, unknown>;\n\n if (environment === undefined) {\n newValues = values;\n newEnvs = this.environments;\n } else {\n newValues = this.values;\n // Preserve any extra metadata on the environment entry (like other sub-keys),\n // but replace the `values` sub-dict entirely.\n const existingEntry =\n typeof this.environments[environment] === \"object\" &&\n this.environments[environment] !== null\n ? { ...(this.environments[environment] as Record<string, unknown>) }\n : {};\n existingEntry.values = values;\n newEnvs = { ...this.environments, [environment]: existingEntry };\n }\n\n const updated = await this._client._updateConfig({\n configId: this.id,\n name: this.name,\n key: this.key,\n description: this.description,\n parent: this.parent,\n values: newValues,\n environments: newEnvs,\n });\n this.values = updated.values;\n this.environments = updated.environments;\n this.updatedAt = updated.updatedAt;\n }\n\n /**\n * Set a single key within base or environment-specific values.\n *\n * Merges the key into existing values rather than replacing all values.\n *\n * @param key - The config key to set.\n * @param value - The value to assign.\n * @param environment - Target environment, or omit for base values.\n */\n async setValue(key: string, value: unknown, environment?: string): Promise<void> {\n if (environment === undefined) {\n const merged = { ...this.values, [key]: value };\n await this.setValues(merged);\n } else {\n const envEntry =\n typeof this.environments[environment] === \"object\" &&\n this.environments[environment] !== null\n ? (this.environments[environment] as Record<string, unknown>)\n : {};\n const existing = {\n ...(typeof envEntry.values === \"object\" && envEntry.values !== null\n ? (envEntry.values as Record<string, unknown>)\n : {}),\n };\n existing[key] = value;\n await this.setValues(existing, environment);\n }\n }\n\n /**\n * Connect to this config for runtime value resolution.\n *\n * Eagerly fetches this config and its full parent chain, resolves values\n * for the given environment via deep merge, and returns a\n * {@link ConfigRuntime} with a fully populated local cache.\n *\n * A background WebSocket connection is started for real-time updates.\n * If the WebSocket fails to connect, the runtime operates in cache-only\n * mode and reconnects automatically.\n *\n * Supports both `await` and `await using` (TypeScript 5.2+)::\n *\n * ```typescript\n * // Simple await\n * const runtime = await config.connect(\"production\");\n * try { ... } finally { await runtime.close(); }\n *\n * // await using (auto-close)\n * await using runtime = await config.connect(\"production\");\n * ```\n *\n * @param environment - The environment to resolve for (e.g. `\"production\"`).\n * @param options.timeout - Milliseconds to wait for the initial fetch.\n */\n async connect(environment: string, options?: ConnectOptions): Promise<ConfigRuntime> {\n // Lazy import to avoid loading ws at module-init time\n const { ConfigRuntime } = await import(\"./runtime.js\");\n\n const timeout = options?.timeout ?? 30_000;\n const chain = await this._buildChain(timeout);\n\n return new ConfigRuntime({\n configKey: this.key,\n configId: this.id,\n environment,\n chain,\n apiKey: this._client._apiKey,\n baseUrl: this._client._baseUrl,\n fetchChain: () => this._buildChain(timeout),\n });\n }\n\n /**\n * Walk the parent chain and return config data objects, child-to-root.\n * @internal\n */\n private async _buildChain(\n _timeout: number,\n ): Promise<\n Array<{ id: string; values: Record<string, unknown>; environments: Record<string, unknown> }>\n > {\n const chain: Array<{\n id: string;\n values: Record<string, unknown>;\n environments: Record<string, unknown>;\n }> = [{ id: this.id, values: this.values, environments: this.environments }];\n\n let parentId = this.parent;\n while (parentId !== null) {\n const parentConfig = await this._client.get({ id: parentId });\n chain.push({\n id: parentConfig.id,\n values: parentConfig.values,\n environments: parentConfig.environments,\n });\n parentId = parentConfig.parent;\n }\n\n return chain;\n }\n\n toString(): string {\n return `Config(id=${this.id}, key=${this.key}, name=${this.name})`;\n }\n}\n\n/** Options for creating a new config. */\nexport interface CreateConfigOptions {\n /** Display name for the config. */\n name: string;\n /** Human-readable key. Auto-generated by the server if omitted. */\n key?: string;\n /** Optional description. */\n description?: string;\n /** Parent config UUID. Defaults to the account's `common` config if omitted. */\n parent?: string;\n /** Initial base values. */\n values?: Record<string, unknown>;\n}\n\n/** Options for fetching a single config. Exactly one of `key` or `id` must be provided. */\nexport interface GetConfigOptions {\n /** Fetch by human-readable key. */\n key?: string;\n /** Fetch by UUID. */\n id?: string;\n}\n","/**\n * API key resolution chain: explicit → env var → config file.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { SmplError } from \"./errors.js\";\n\nconst NO_API_KEY_MESSAGE =\n \"No API key provided. Set one of:\\n\" +\n \" 1. Pass apiKey to the constructor\\n\" +\n \" 2. Set the SMPLKIT_API_KEY environment variable\\n\" +\n \" 3. Add api_key to [default] in ~/.smplkit\";\n\nexport function resolveApiKey(explicit?: string): string {\n if (explicit) return explicit;\n\n const envVal = process.env.SMPLKIT_API_KEY;\n if (envVal) return envVal;\n\n const configPath = join(homedir(), \".smplkit\");\n try {\n const content = readFileSync(configPath, \"utf-8\");\n const match = content.match(/\\[default\\]\\s*[\\s\\S]*?api_key\\s*=\\s*\"([^\"]+)\"/);\n if (match?.[1]) return match[1];\n } catch {\n // File doesn't exist or isn't readable — skip\n }\n\n throw new SmplError(NO_API_KEY_MESSAGE);\n}\n","/**\n * Top-level SDK client — SmplClient.\n *\n * The main entry point for the smplkit TypeScript SDK. Provides access\n * to sub-clients for each API domain (config, flags, logging, etc.).\n */\n\nimport { ConfigClient } from \"./config/client.js\";\nimport { resolveApiKey } from \"./resolve.js\";\n\n/** Configuration options for the {@link SmplClient}. */\nexport interface SmplClientOptions {\n /**\n * API key for authenticating with the smplkit platform.\n * When omitted, the SDK resolves it from the `SMPLKIT_API_KEY`\n * environment variable or the `~/.smplkit` configuration file.\n */\n apiKey?: string;\n\n /**\n * Request timeout in milliseconds.\n * @default 30000\n */\n timeout?: number;\n}\n\n/**\n * Entry point for the smplkit TypeScript SDK.\n *\n * @example\n * ```typescript\n * import { SmplClient } from \"@smplkit/sdk\";\n *\n * const client = new SmplClient({ apiKey: \"sk_api_...\" });\n * const cfg = await client.config.get({ key: \"common\" });\n * ```\n */\nexport class SmplClient {\n /** Client for config management-plane operations. */\n readonly config: ConfigClient;\n\n constructor(options: SmplClientOptions = {}) {\n const apiKey = resolveApiKey(options.apiKey);\n this.config = new ConfigClient(apiKey, options.timeout);\n }\n}\n"],"mappings":"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","names":["ConfigRuntime"]}
@@ -0,0 +1,7 @@
1
+ import {
2
+ ConfigRuntime
3
+ } from "./chunk-GLOLTIGH.js";
4
+ export {
5
+ ConfigRuntime
6
+ };
7
+ //# sourceMappingURL=runtime-GALKHNKF.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smplkit/sdk",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "type": "module",
5
5
  "description": "Official TypeScript SDK for the smplkit platform",
6
6
  "main": "./dist/index.cjs",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config/runtime.ts","../src/config/resolve.ts"],"sourcesContent":["/**\n * ConfigRuntime — runtime-plane value resolution with WebSocket updates.\n *\n * Holds a fully resolved local cache of config values for a specific\n * environment. All value-access methods are synchronous (local reads);\n * only {@link refresh} and {@link close} are async.\n *\n * A background WebSocket connection is maintained for real-time updates.\n * If the WebSocket fails, the runtime operates in cache-only mode and\n * reconnects automatically with exponential backoff.\n */\n\nimport WebSocket from \"ws\";\nimport { resolveChain } from \"./resolve.js\";\nimport type { ChainConfig } from \"./resolve.js\";\nimport type { ConfigChangeEvent, ConfigStats, ConnectionStatus } from \"./runtime-types.js\";\n\n/** @internal */\ninterface ChangeListener {\n callback: (event: ConfigChangeEvent) => void;\n key: string | null;\n}\n\n/** @internal */\ninterface WsConfigChangedMessage {\n type: \"config_changed\";\n config_id: string;\n changes: Array<{\n key: string;\n old_value: unknown;\n new_value: unknown;\n }>;\n}\n\n/** @internal */\ninterface WsConfigDeletedMessage {\n type: \"config_deleted\";\n config_id: string;\n}\n\ntype WsMessage =\n | { type: \"subscribed\"; config_id: string; environment: string }\n | { type: \"error\"; message: string }\n | WsConfigChangedMessage\n | WsConfigDeletedMessage;\n\n/** @internal */\nconst BACKOFF_MS = [1000, 2000, 4000, 8000, 16000, 32000, 60000];\n\n/** @internal Options for constructing a ConfigRuntime. */\nexport interface ConfigRuntimeOptions {\n configKey: string;\n configId: string;\n environment: string;\n chain: ChainConfig[];\n apiKey: string;\n baseUrl: string;\n fetchChain: (() => Promise<ChainConfig[]>) | null;\n}\n\n/**\n * Runtime configuration handle for a specific environment.\n *\n * Obtained by calling {@link Config.connect}. All value-access methods\n * are synchronous and served entirely from a local in-process cache.\n * The cache is populated eagerly on construction and kept current via\n * a background WebSocket connection.\n */\nexport class ConfigRuntime {\n private _cache: Record<string, unknown>;\n private _chain: ChainConfig[];\n private _fetchCount: number;\n private _lastFetchAt: string | null;\n private _closed = false;\n private _wsStatus: ConnectionStatus = \"disconnected\";\n private _ws: InstanceType<typeof WebSocket> | null = null;\n private _reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private _backoffIndex = 0;\n private _listeners: ChangeListener[] = [];\n\n private readonly _configId: string;\n private readonly _environment: string;\n private readonly _apiKey: string;\n private readonly _baseUrl: string;\n private readonly _fetchChain: (() => Promise<ChainConfig[]>) | null;\n\n /** @internal */\n constructor(options: ConfigRuntimeOptions) {\n this._configId = options.configId;\n this._environment = options.environment;\n this._apiKey = options.apiKey;\n this._baseUrl = options.baseUrl;\n this._fetchChain = options.fetchChain;\n this._chain = options.chain;\n this._cache = resolveChain(options.chain, options.environment);\n this._fetchCount = options.chain.length;\n this._lastFetchAt = new Date().toISOString();\n\n // Start WebSocket in background — non-blocking\n this._connectWebSocket();\n }\n\n // ---- Value access (synchronous, local cache) ----\n\n /**\n * Return the resolved value for `key`, or `defaultValue` if absent.\n *\n * @param key - The config key to look up.\n * @param defaultValue - Returned when the key is not present (default: null).\n */\n get(key: string, defaultValue: unknown = null): unknown {\n return key in this._cache ? this._cache[key] : defaultValue;\n }\n\n /**\n * Return the value as a string, or `defaultValue` if absent or not a string.\n */\n getString(key: string, defaultValue: string | null = null): string | null {\n const value = this._cache[key];\n return typeof value === \"string\" ? value : defaultValue;\n }\n\n /**\n * Return the value as a number, or `defaultValue` if absent or not a number.\n */\n getNumber(key: string, defaultValue: number | null = null): number | null {\n const value = this._cache[key];\n return typeof value === \"number\" ? value : defaultValue;\n }\n\n /**\n * Return the value as a boolean, or `defaultValue` if absent or not a boolean.\n */\n getBool(key: string, defaultValue: boolean | null = null): boolean | null {\n const value = this._cache[key];\n return typeof value === \"boolean\" ? value : defaultValue;\n }\n\n /**\n * Return whether `key` is present in the resolved configuration.\n */\n exists(key: string): boolean {\n return key in this._cache;\n }\n\n /**\n * Return a shallow copy of the full resolved configuration.\n */\n getAll(): Record<string, unknown> {\n return { ...this._cache };\n }\n\n // ---- Change listeners ----\n\n /**\n * Register a listener that fires when a config value changes.\n *\n * @param callback - Called with a {@link ConfigChangeEvent} on each change.\n * @param options.key - If provided, the listener fires only for this key.\n * If omitted, the listener fires for all changes.\n */\n onChange(callback: (event: ConfigChangeEvent) => void, options?: { key?: string }): void {\n this._listeners.push({\n callback,\n key: options?.key ?? null,\n });\n }\n\n // ---- Diagnostics ----\n\n /**\n * Return diagnostic statistics for this runtime.\n */\n stats(): ConfigStats {\n return {\n fetchCount: this._fetchCount,\n lastFetchAt: this._lastFetchAt,\n };\n }\n\n /**\n * Return the current WebSocket connection status.\n */\n connectionStatus(): ConnectionStatus {\n return this._wsStatus;\n }\n\n // ---- Lifecycle ----\n\n /**\n * Force a manual refresh of the cached configuration.\n *\n * Re-fetches the full config chain via HTTP, re-resolves values, updates\n * the local cache, and fires listeners for any detected changes.\n *\n * @throws {Error} If no `fetchChain` function was provided on construction.\n */\n async refresh(): Promise<void> {\n if (!this._fetchChain) {\n throw new Error(\"No fetchChain function provided; cannot refresh.\");\n }\n\n const newChain = await this._fetchChain();\n const oldCache = this._cache;\n\n this._chain = newChain;\n this._cache = resolveChain(newChain, this._environment);\n this._fetchCount += newChain.length;\n this._lastFetchAt = new Date().toISOString();\n\n this._diffAndFire(oldCache, this._cache, \"manual\");\n }\n\n /**\n * Close the runtime connection.\n *\n * Shuts down the WebSocket and cancels any pending reconnect timer.\n * Safe to call multiple times.\n */\n async close(): Promise<void> {\n this._closed = true;\n this._wsStatus = \"disconnected\";\n\n if (this._reconnectTimer !== null) {\n clearTimeout(this._reconnectTimer);\n this._reconnectTimer = null;\n }\n\n if (this._ws !== null) {\n this._ws.close();\n this._ws = null;\n }\n }\n\n /**\n * Async dispose support for `await using` (TypeScript 5.2+).\n */\n async [Symbol.asyncDispose](): Promise<void> {\n await this.close();\n }\n\n // ---- WebSocket internals ----\n\n private _buildWsUrl(): string {\n let url = this._baseUrl;\n if (url.startsWith(\"https://\")) {\n url = \"wss://\" + url.slice(\"https://\".length);\n } else if (url.startsWith(\"http://\")) {\n url = \"ws://\" + url.slice(\"http://\".length);\n } else {\n url = \"wss://\" + url;\n }\n url = url.replace(/\\/$/, \"\");\n return `${url}/api/ws/v1/configs?api_key=${this._apiKey}`;\n }\n\n private _connectWebSocket(): void {\n if (this._closed) return;\n\n this._wsStatus = \"connecting\";\n const wsUrl = this._buildWsUrl();\n\n try {\n const ws = new WebSocket(wsUrl);\n this._ws = ws;\n\n ws.on(\"open\", () => {\n if (this._closed) {\n ws.close();\n return;\n }\n this._backoffIndex = 0;\n this._wsStatus = \"connected\";\n ws.send(\n JSON.stringify({\n type: \"subscribe\",\n config_id: this._configId,\n environment: this._environment,\n }),\n );\n });\n\n ws.on(\"message\", (data: WebSocket.RawData) => {\n try {\n const msg = JSON.parse(String(data)) as WsMessage;\n this._handleMessage(msg);\n } catch {\n // ignore unparseable messages\n }\n });\n\n ws.on(\"close\", () => {\n if (!this._closed) {\n this._wsStatus = \"disconnected\";\n this._scheduleReconnect();\n }\n });\n\n ws.on(\"error\", () => {\n // 'close' will fire after 'error'; reconnect is handled there\n });\n } catch {\n if (!this._closed) {\n this._scheduleReconnect();\n }\n }\n }\n\n private _scheduleReconnect(): void {\n if (this._closed) return;\n\n const delay = BACKOFF_MS[Math.min(this._backoffIndex, BACKOFF_MS.length - 1)];\n this._backoffIndex++;\n this._wsStatus = \"connecting\";\n\n this._reconnectTimer = setTimeout(() => {\n this._reconnectTimer = null;\n // On reconnect, resync the cache to pick up changes missed while offline\n if (this._fetchChain) {\n this._fetchChain()\n .then((newChain) => {\n const oldCache = this._cache;\n this._chain = newChain;\n this._cache = resolveChain(newChain, this._environment);\n this._fetchCount += newChain.length;\n this._lastFetchAt = new Date().toISOString();\n this._diffAndFire(oldCache, this._cache, \"manual\");\n })\n .catch(() => {\n // ignore fetch errors during reconnect\n })\n .finally(() => {\n this._connectWebSocket();\n });\n } else {\n this._connectWebSocket();\n }\n }, delay);\n }\n\n private _handleMessage(msg: WsMessage): void {\n if (msg.type === \"config_changed\") {\n this._applyChanges(msg.config_id, msg.changes);\n } else if (msg.type === \"config_deleted\") {\n this._closed = true;\n void this.close();\n }\n }\n\n private _applyChanges(\n configId: string,\n changes: Array<{ key: string; old_value: unknown; new_value: unknown }>,\n ): void {\n const chainEntry = this._chain.find((c) => c.id === configId);\n if (!chainEntry) return;\n\n for (const change of changes) {\n const { key, new_value } = change;\n\n // Get or create the environment entry\n const envEntry =\n chainEntry.environments[this._environment] !== undefined &&\n chainEntry.environments[this._environment] !== null\n ? (chainEntry.environments[this._environment] as Record<string, unknown>)\n : null;\n const envValues =\n envEntry !== null && typeof envEntry === \"object\"\n ? ((envEntry.values ?? {}) as Record<string, unknown>)\n : null;\n\n if (new_value === null || new_value === undefined) {\n // Deletion: remove from base values and env values\n delete chainEntry.values[key];\n if (envValues) delete envValues[key];\n } else if (envValues && key in envValues) {\n // Update existing env-specific override\n envValues[key] = new_value;\n } else if (key in chainEntry.values) {\n // Update existing base value\n chainEntry.values[key] = new_value;\n } else {\n // New key — put in base values\n chainEntry.values[key] = new_value;\n }\n }\n\n const oldCache = this._cache;\n this._cache = resolveChain(this._chain, this._environment);\n this._diffAndFire(oldCache, this._cache, \"websocket\");\n }\n\n private _diffAndFire(\n oldCache: Record<string, unknown>,\n newCache: Record<string, unknown>,\n source: \"websocket\" | \"poll\" | \"manual\",\n ): void {\n const allKeys = new Set([...Object.keys(oldCache), ...Object.keys(newCache)]);\n\n for (const key of allKeys) {\n const oldVal = key in oldCache ? oldCache[key] : null;\n const newVal = key in newCache ? newCache[key] : null;\n\n if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {\n const event: ConfigChangeEvent = { key, oldValue: oldVal, newValue: newVal, source };\n this._fireListeners(event);\n }\n }\n }\n\n private _fireListeners(event: ConfigChangeEvent): void {\n for (const listener of this._listeners) {\n if (listener.key === null || listener.key === event.key) {\n try {\n listener.callback(event);\n } catch {\n // ignore listener errors to prevent one bad listener from stopping others\n }\n }\n }\n }\n}\n","/**\n * Deep-merge resolution algorithm for config inheritance chains.\n *\n * Mirrors the Python SDK's `_resolver.py` (ADR-024 §2.5–2.6).\n */\n\n/** A single entry in a config inheritance chain (child-to-root ordering). */\nexport interface ChainConfig {\n /** Config UUID. */\n id: string;\n /** Base key-value pairs. */\n values: Record<string, unknown>;\n /**\n * Per-environment overrides.\n * Each entry is `{ values: { key: value, ... } }` — the server wraps\n * environment-specific values in a nested `values` key.\n */\n environments: Record<string, unknown>;\n}\n\n/**\n * Recursively merge two dicts, with `override` taking precedence.\n *\n * Nested dicts are merged recursively. Non-dict values (strings, numbers,\n * booleans, arrays, null) are replaced wholesale.\n */\nexport function deepMerge(\n base: Record<string, unknown>,\n override: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...base };\n for (const [key, value] of Object.entries(override)) {\n if (\n key in result &&\n typeof result[key] === \"object\" &&\n result[key] !== null &&\n !Array.isArray(result[key]) &&\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value)\n ) {\n result[key] = deepMerge(\n result[key] as Record<string, unknown>,\n value as Record<string, unknown>,\n );\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Resolve the full configuration for an environment given a config chain.\n *\n * Walks from root (last element) to child (first element), accumulating\n * values via deep merge so that child configs override parent configs.\n *\n * For each config in the chain, base `values` are merged with\n * environment-specific values (env wins), then that result is merged\n * on top of the accumulated parent result (child wins over parent).\n *\n * @param chain - Ordered list of config data from child (index 0) to root ancestor (last).\n * @param environment - The environment key to resolve for.\n */\nexport function resolveChain(chain: ChainConfig[], environment: string): Record<string, unknown> {\n let accumulated: Record<string, unknown> = {};\n\n // Walk from root to child (reverse order — chain is child-to-root)\n for (let i = chain.length - 1; i >= 0; i--) {\n const config = chain[i];\n const baseValues: Record<string, unknown> = config.values ?? {};\n\n // Environments are stored as { env_name: { values: { key: val } } }\n const envEntry = (config.environments ?? {})[environment];\n const envValues: Record<string, unknown> =\n envEntry !== null &&\n envEntry !== undefined &&\n typeof envEntry === \"object\" &&\n !Array.isArray(envEntry)\n ? (((envEntry as Record<string, unknown>).values ?? {}) as Record<string, unknown>)\n : {};\n\n // Merge environment overrides on top of base values (env wins)\n const configResolved = deepMerge(baseValues, envValues);\n\n // Merge this config's resolved values on top of accumulated parent values (child wins)\n accumulated = deepMerge(accumulated, configResolved);\n }\n\n return accumulated;\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +0,0 @@
1
- import {
2
- ConfigRuntime
3
- } from "./chunk-PZD5PSQY.js";
4
- export {
5
- ConfigRuntime
6
- };
7
- //# sourceMappingURL=runtime-CCRTBKED.js.map