@smplkit/sdk 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -46,174 +46,12 @@ declare class SharedWebSocket {
46
46
  }
47
47
 
48
48
  /**
49
- * Deep-merge resolution algorithm for config inheritance chains.
49
+ * Config resource — management-plane model with runtime connect support.
50
50
  *
51
- * Mirrors the Python SDK's `_resolver.py` (ADR-024 §2.5–2.6).
52
- */
53
- /** A single entry in a config inheritance chain (child-to-root ordering). */
54
- interface ChainConfig {
55
- /** Config UUID. */
56
- id: string;
57
- /** Base key-value pairs (unwrapped from typed item definitions). */
58
- items: Record<string, unknown>;
59
- /**
60
- * Per-environment overrides.
61
- * Each entry is `{ values: { key: value, ... } }` — values are already
62
- * unwrapped from the server's `{ value: raw }` wrapper by the client layer.
63
- */
64
- environments: Record<string, unknown>;
65
- }
66
-
67
- /**
68
- * Types for the config runtime plane.
69
- */
70
- /** Describes a single value change pushed by the server or detected on refresh. */
71
- interface ConfigChangeEvent {
72
- /** The config key that changed. */
73
- key: string;
74
- /** The previous value (null if the key was absent). */
75
- oldValue: unknown;
76
- /** The updated value (null if the key was removed). */
77
- newValue: unknown;
78
- /** How the change was delivered. */
79
- source: "websocket" | "poll" | "manual";
80
- }
81
- /** Diagnostic statistics for a {@link ConfigRuntime} instance. */
82
- interface ConfigStats {
83
- /**
84
- * Total number of HTTP fetches performed, including the initial connect
85
- * and any reconnection re-syncs or manual refreshes. Incremented by the
86
- * chain length (number of configs fetched) on each fetch.
87
- */
88
- fetchCount: number;
89
- /** ISO-8601 timestamp of the most recent fetch, or null if none yet. */
90
- lastFetchAt: string | null;
91
- }
92
- /** WebSocket connection status. */
93
- type ConnectionStatus = "connected" | "connecting" | "disconnected";
94
- /** Options for {@link Config.connect}. */
95
- interface ConnectOptions {
96
- /**
97
- * Maximum milliseconds to wait for the initial fetch.
98
- * @default 30000
99
- */
100
- timeout?: number;
101
- }
102
-
103
- /**
104
- * ConfigRuntime — runtime-plane value resolution with WebSocket updates.
105
- *
106
- * Holds a fully resolved local cache of config values for a specific
107
- * environment. All value-access methods are synchronous (local reads);
108
- * only {@link refresh} and {@link close} are async.
109
- *
110
- * A background WebSocket connection is maintained for real-time updates.
111
- * If the WebSocket fails, the runtime operates in cache-only mode and
112
- * reconnects automatically with exponential backoff.
113
- */
114
-
115
- /** @internal Options for constructing a ConfigRuntime. */
116
- interface ConfigRuntimeOptions {
117
- configKey: string;
118
- configId: string;
119
- environment: string;
120
- chain: ChainConfig[];
121
- apiKey: string;
122
- baseUrl: string;
123
- fetchChain: (() => Promise<ChainConfig[]>) | null;
124
- sharedWs?: SharedWebSocket | null;
125
- }
126
- /**
127
- * Runtime configuration handle for a specific environment.
128
- *
129
- * Obtained by calling {@link Config.connect}. All value-access methods
130
- * are synchronous and served entirely from a local in-process cache.
131
- * The cache is populated eagerly on construction and kept current via
132
- * a background WebSocket connection.
51
+ * Instances are returned by {@link ConfigClient} methods and provide
52
+ * management-plane operations (`update`, `setValues`, `setValue`) as well
53
+ * as the {@link connect} entry point for runtime value resolution.
133
54
  */
134
- declare class ConfigRuntime {
135
- private _cache;
136
- private _chain;
137
- private _fetchCount;
138
- private _lastFetchAt;
139
- private _closed;
140
- private _listeners;
141
- private readonly _environment;
142
- private readonly _fetchChain;
143
- private _sharedWs;
144
- /** @internal */
145
- constructor(options: ConfigRuntimeOptions);
146
- /**
147
- * Return the resolved value for `key`, or `defaultValue` if absent.
148
- *
149
- * @param key - The config key to look up.
150
- * @param defaultValue - Returned when the key is not present (default: null).
151
- */
152
- get(key: string, defaultValue?: unknown): unknown;
153
- /**
154
- * Return the value as a string, or `defaultValue` if absent or not a string.
155
- */
156
- getString(key: string, defaultValue?: string | null): string | null;
157
- /**
158
- * Return the value as a number, or `defaultValue` if absent or not a number.
159
- */
160
- getInt(key: string, defaultValue?: number | null): number | null;
161
- /**
162
- * Return the value as a boolean, or `defaultValue` if absent or not a boolean.
163
- */
164
- getBool(key: string, defaultValue?: boolean | null): boolean | null;
165
- /**
166
- * Return whether `key` is present in the resolved configuration.
167
- */
168
- exists(key: string): boolean;
169
- /**
170
- * Return a shallow copy of the full resolved configuration.
171
- */
172
- getAll(): Record<string, unknown>;
173
- /**
174
- * Register a listener that fires when a config value changes.
175
- *
176
- * @param callback - Called with a {@link ConfigChangeEvent} on each change.
177
- * @param options.key - If provided, the listener fires only for this key.
178
- * If omitted, the listener fires for all changes.
179
- */
180
- onChange(callback: (event: ConfigChangeEvent) => void, options?: {
181
- key?: string;
182
- }): void;
183
- /**
184
- * Return diagnostic statistics for this runtime.
185
- */
186
- stats(): ConfigStats;
187
- /**
188
- * Return the current WebSocket connection status.
189
- */
190
- connectionStatus(): ConnectionStatus;
191
- /**
192
- * Force a manual refresh of the cached configuration.
193
- *
194
- * Re-fetches the full config chain via HTTP, re-resolves values, updates
195
- * the local cache, and fires listeners for any detected changes.
196
- *
197
- * @throws {Error} If no `fetchChain` function was provided on construction.
198
- */
199
- refresh(): Promise<void>;
200
- /**
201
- * Close the runtime connection.
202
- *
203
- * Unregisters from the shared WebSocket. Safe to call multiple times.
204
- */
205
- close(): Promise<void>;
206
- /**
207
- * Async dispose support for `await using` (TypeScript 5.2+).
208
- */
209
- [Symbol.asyncDispose](): Promise<void>;
210
- private _handleConfigChanged;
211
- private _handleConfigDeleted;
212
- private _applyChanges;
213
- private _diffAndFire;
214
- private _fireListeners;
215
- }
216
-
217
55
  /**
218
56
  * Internal type used by {@link ConfigClient}. Not part of the public API.
219
57
  * @internal
@@ -320,37 +158,15 @@ declare class Config {
320
158
  * @param environment - Target environment, or omit for base values.
321
159
  */
322
160
  setValue(key: string, value: unknown, environment?: string): Promise<void>;
323
- /**
324
- * Connect to this config for runtime value resolution.
325
- *
326
- * Eagerly fetches this config and its full parent chain, resolves values
327
- * for the given environment via deep merge, and returns a
328
- * {@link ConfigRuntime} with a fully populated local cache.
329
- *
330
- * A background WebSocket connection is started for real-time updates.
331
- * If the WebSocket fails to connect, the runtime operates in cache-only
332
- * mode and reconnects automatically.
333
- *
334
- * Supports both `await` and `await using` (TypeScript 5.2+)::
335
- *
336
- * ```typescript
337
- * // Simple await
338
- * const runtime = await config.connect("production");
339
- * try { ... } finally { await runtime.close(); }
340
- *
341
- * // await using (auto-close)
342
- * await using runtime = await config.connect("production");
343
- * ```
344
- *
345
- * @param environment - The environment to resolve for (e.g. `"production"`).
346
- * @param options.timeout - Milliseconds to wait for the initial fetch.
347
- */
348
- connect(environment: string, options?: ConnectOptions): Promise<ConfigRuntime>;
349
161
  /**
350
162
  * Walk the parent chain and return config data objects, child-to-root.
351
163
  * @internal
352
164
  */
353
- private _buildChain;
165
+ _buildChain(_timeout?: unknown): Promise<Array<{
166
+ id: string;
167
+ items: Record<string, unknown>;
168
+ environments: Record<string, unknown>;
169
+ }>>;
354
170
  toString(): string;
355
171
  }
356
172
  /** Options for creating a new config. */
@@ -391,6 +207,13 @@ declare class ConfigClient {
391
207
  private readonly _http;
392
208
  /** @internal — returns the shared WebSocket for real-time updates. */
393
209
  _getSharedWs?: () => SharedWebSocket;
210
+ /** @internal — set by SmplClient after construction. */
211
+ _parent: {
212
+ readonly _environment: string;
213
+ readonly _service: string | null;
214
+ } | null;
215
+ private _configCache;
216
+ private _connected;
394
217
  /** @internal */
395
218
  constructor(apiKey: string, timeout?: number);
396
219
  /**
@@ -418,6 +241,23 @@ declare class ConfigClient {
418
241
  * @throws {SmplConflictError} If the config has child configs.
419
242
  */
420
243
  delete(configId: string): Promise<void>;
244
+ /**
245
+ * Fetch all configs, resolve values for the environment, and cache.
246
+ * @internal — called by SmplClient.connect().
247
+ */
248
+ _connectInternal(environment: string): Promise<void>;
249
+ /**
250
+ * Read a resolved config value (prescriptive access).
251
+ *
252
+ * Requires {@link SmplClient.connect} to have been called.
253
+ *
254
+ * @param configKey - The config key to look up.
255
+ * @param itemKey - Optional specific item key. If omitted, returns all values.
256
+ * @param defaultValue - Default value if the key is missing.
257
+ *
258
+ * @throws {SmplNotConnectedError} If connect() has not been called.
259
+ */
260
+ getValue(configKey: string, itemKey?: string, defaultValue?: unknown): unknown;
421
261
  /**
422
262
  * Internal: PUT a full config update and return the updated model.
423
263
  *
@@ -670,6 +510,11 @@ declare class FlagsClient {
670
510
  private _globalListeners;
671
511
  private _wsManager;
672
512
  private readonly _ensureWs;
513
+ /** @internal — set by SmplClient after construction. */
514
+ _parent: {
515
+ readonly _environment: string;
516
+ readonly _service: string | null;
517
+ } | null;
673
518
  /** @internal */
674
519
  constructor(apiKey: string, ensureWs: () => SharedWebSocket, timeout?: number);
675
520
  /** Create a flag. */
@@ -753,10 +598,9 @@ declare class FlagsClient {
753
598
  /**
754
599
  * Connect to an environment: fetch flag definitions, register on
755
600
  * shared WebSocket, enable local evaluation.
601
+ * @internal — called by SmplClient.connect().
756
602
  */
757
- connect(environment: string, _options?: {
758
- timeout?: number;
759
- }): Promise<void>;
603
+ _connectInternal(environment: string): Promise<void>;
760
604
  /** Disconnect: unregister from WebSocket, flush contexts, clear state. */
761
605
  disconnect(): Promise<void>;
762
606
  /** Re-fetch all flag definitions and clear cache. */
@@ -822,6 +666,17 @@ interface SmplClientOptions {
822
666
  * environment variable or the `~/.smplkit` configuration file.
823
667
  */
824
668
  apiKey?: string;
669
+ /**
670
+ * The environment to connect to (e.g. `"production"`, `"staging"`).
671
+ * When omitted, resolved from the `SMPLKIT_ENVIRONMENT` environment variable.
672
+ */
673
+ environment?: string;
674
+ /**
675
+ * Optional service name. When set, the SDK automatically registers
676
+ * the service as a context instance and includes it in flag
677
+ * evaluation context.
678
+ */
679
+ service?: string;
825
680
  /**
826
681
  * Request timeout in milliseconds.
827
682
  * @default 30000
@@ -835,8 +690,8 @@ interface SmplClientOptions {
835
690
  * ```typescript
836
691
  * import { SmplClient } from "@smplkit/sdk";
837
692
  *
838
- * const client = new SmplClient({ apiKey: "sk_api_..." });
839
- * const cfg = await client.config.get({ key: "common" });
693
+ * const client = new SmplClient({ apiKey: "sk_api_...", environment: "production" });
694
+ * await client.connect();
840
695
  * ```
841
696
  */
842
697
  declare class SmplClient {
@@ -846,13 +701,199 @@ declare class SmplClient {
846
701
  readonly flags: FlagsClient;
847
702
  private _wsManager;
848
703
  private readonly _apiKey;
704
+ /** @internal */
705
+ readonly _environment: string;
706
+ /** @internal */
707
+ readonly _service: string | null;
708
+ private _connected;
709
+ private readonly _timeout;
849
710
  constructor(options?: SmplClientOptions);
711
+ /**
712
+ * Connect to the smplkit platform.
713
+ *
714
+ * Fetches initial flag and config data, opens the shared WebSocket,
715
+ * and registers the service as a context instance (if provided).
716
+ *
717
+ * This method is idempotent — calling it multiple times is safe.
718
+ */
719
+ connect(): Promise<void>;
720
+ /** @internal */
721
+ private _registerServiceContext;
850
722
  /** Lazily create and start the shared WebSocket. @internal */
851
723
  private _ensureWs;
852
724
  /** Close the shared WebSocket and release resources. */
853
725
  close(): void;
854
726
  }
855
727
 
728
+ /**
729
+ * Deep-merge resolution algorithm for config inheritance chains.
730
+ *
731
+ * Mirrors the Python SDK's `_resolver.py` (ADR-024 §2.5–2.6).
732
+ */
733
+ /** A single entry in a config inheritance chain (child-to-root ordering). */
734
+ interface ChainConfig {
735
+ /** Config UUID. */
736
+ id: string;
737
+ /** Base key-value pairs (unwrapped from typed item definitions). */
738
+ items: Record<string, unknown>;
739
+ /**
740
+ * Per-environment overrides.
741
+ * Each entry is `{ values: { key: value, ... } }` — values are already
742
+ * unwrapped from the server's `{ value: raw }` wrapper by the client layer.
743
+ */
744
+ environments: Record<string, unknown>;
745
+ }
746
+
747
+ /**
748
+ * Types for the config runtime plane.
749
+ */
750
+ /** Describes a single value change pushed by the server or detected on refresh. */
751
+ interface ConfigChangeEvent {
752
+ /** The config key that changed. */
753
+ key: string;
754
+ /** The previous value (null if the key was absent). */
755
+ oldValue: unknown;
756
+ /** The updated value (null if the key was removed). */
757
+ newValue: unknown;
758
+ /** How the change was delivered. */
759
+ source: "websocket" | "poll" | "manual";
760
+ }
761
+ /** Diagnostic statistics for a {@link ConfigRuntime} instance. */
762
+ interface ConfigStats {
763
+ /**
764
+ * Total number of HTTP fetches performed, including the initial connect
765
+ * and any reconnection re-syncs or manual refreshes. Incremented by the
766
+ * chain length (number of configs fetched) on each fetch.
767
+ */
768
+ fetchCount: number;
769
+ /** ISO-8601 timestamp of the most recent fetch, or null if none yet. */
770
+ lastFetchAt: string | null;
771
+ }
772
+ /** WebSocket connection status. */
773
+ type ConnectionStatus = "connected" | "connecting" | "disconnected";
774
+ /** Options for {@link Config.connect}. */
775
+ interface ConnectOptions {
776
+ /**
777
+ * Maximum milliseconds to wait for the initial fetch.
778
+ * @default 30000
779
+ */
780
+ timeout?: number;
781
+ }
782
+
783
+ /**
784
+ * ConfigRuntime — runtime-plane value resolution with WebSocket updates.
785
+ *
786
+ * Holds a fully resolved local cache of config values for a specific
787
+ * environment. All value-access methods are synchronous (local reads);
788
+ * only {@link refresh} and {@link close} are async.
789
+ *
790
+ * A background WebSocket connection is maintained for real-time updates.
791
+ * If the WebSocket fails, the runtime operates in cache-only mode and
792
+ * reconnects automatically with exponential backoff.
793
+ */
794
+
795
+ /** @internal Options for constructing a ConfigRuntime. */
796
+ interface ConfigRuntimeOptions {
797
+ configKey: string;
798
+ configId: string;
799
+ environment: string;
800
+ chain: ChainConfig[];
801
+ apiKey: string;
802
+ baseUrl: string;
803
+ fetchChain: (() => Promise<ChainConfig[]>) | null;
804
+ sharedWs?: SharedWebSocket | null;
805
+ }
806
+ /**
807
+ * Runtime configuration handle for a specific environment.
808
+ *
809
+ * Obtained by calling {@link Config.connect}. All value-access methods
810
+ * are synchronous and served entirely from a local in-process cache.
811
+ * The cache is populated eagerly on construction and kept current via
812
+ * a background WebSocket connection.
813
+ */
814
+ declare class ConfigRuntime {
815
+ private _cache;
816
+ private _chain;
817
+ private _fetchCount;
818
+ private _lastFetchAt;
819
+ private _closed;
820
+ private _listeners;
821
+ private readonly _environment;
822
+ private readonly _fetchChain;
823
+ private _sharedWs;
824
+ /** @internal */
825
+ constructor(options: ConfigRuntimeOptions);
826
+ /**
827
+ * Return the resolved value for `key`, or `defaultValue` if absent.
828
+ *
829
+ * @param key - The config key to look up.
830
+ * @param defaultValue - Returned when the key is not present (default: null).
831
+ */
832
+ get(key: string, defaultValue?: unknown): unknown;
833
+ /**
834
+ * Return the value as a string, or `defaultValue` if absent or not a string.
835
+ */
836
+ getString(key: string, defaultValue?: string | null): string | null;
837
+ /**
838
+ * Return the value as a number, or `defaultValue` if absent or not a number.
839
+ */
840
+ getInt(key: string, defaultValue?: number | null): number | null;
841
+ /**
842
+ * Return the value as a boolean, or `defaultValue` if absent or not a boolean.
843
+ */
844
+ getBool(key: string, defaultValue?: boolean | null): boolean | null;
845
+ /**
846
+ * Return whether `key` is present in the resolved configuration.
847
+ */
848
+ exists(key: string): boolean;
849
+ /**
850
+ * Return a shallow copy of the full resolved configuration.
851
+ */
852
+ getAll(): Record<string, unknown>;
853
+ /**
854
+ * Register a listener that fires when a config value changes.
855
+ *
856
+ * @param callback - Called with a {@link ConfigChangeEvent} on each change.
857
+ * @param options.key - If provided, the listener fires only for this key.
858
+ * If omitted, the listener fires for all changes.
859
+ */
860
+ onChange(callback: (event: ConfigChangeEvent) => void, options?: {
861
+ key?: string;
862
+ }): void;
863
+ /**
864
+ * Return diagnostic statistics for this runtime.
865
+ */
866
+ stats(): ConfigStats;
867
+ /**
868
+ * Return the current WebSocket connection status.
869
+ */
870
+ connectionStatus(): ConnectionStatus;
871
+ /**
872
+ * Force a manual refresh of the cached configuration.
873
+ *
874
+ * Re-fetches the full config chain via HTTP, re-resolves values, updates
875
+ * the local cache, and fires listeners for any detected changes.
876
+ *
877
+ * @throws {Error} If no `fetchChain` function was provided on construction.
878
+ */
879
+ refresh(): Promise<void>;
880
+ /**
881
+ * Close the runtime connection.
882
+ *
883
+ * Unregisters from the shared WebSocket. Safe to call multiple times.
884
+ */
885
+ close(): Promise<void>;
886
+ /**
887
+ * Async dispose support for `await using` (TypeScript 5.2+).
888
+ */
889
+ [Symbol.asyncDispose](): Promise<void>;
890
+ private _handleConfigChanged;
891
+ private _handleConfigDeleted;
892
+ private _applyChanges;
893
+ private _diffAndFire;
894
+ private _fireListeners;
895
+ }
896
+
856
897
  /**
857
898
  * Structured SDK error types.
858
899
  *
@@ -884,9 +925,13 @@ declare class SmplNotFoundError extends SmplError {
884
925
  declare class SmplConflictError extends SmplError {
885
926
  constructor(message: string, statusCode?: number, responseBody?: string);
886
927
  }
928
+ /** Raised when a method requiring connect() is called before connecting. */
929
+ declare class SmplNotConnectedError extends SmplError {
930
+ constructor(message: string);
931
+ }
887
932
  /** Raised when the server rejects a request due to validation errors (HTTP 422). */
888
933
  declare class SmplValidationError extends SmplError {
889
934
  constructor(message: string, statusCode?: number, responseBody?: string);
890
935
  }
891
936
 
892
- export { BoolFlagHandle, Config, type ConfigChangeEvent, ConfigClient, ConfigRuntime, type ConfigStats, type ConnectOptions, type ConnectionStatus, Context, ContextType, type CreateConfigOptions, Flag, FlagChangeEvent, FlagStats, type FlagType, FlagsClient, type GetConfigOptions, JsonFlagHandle, NumberFlagHandle, Rule, SharedWebSocket, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotFoundError, SmplTimeoutError, SmplValidationError, StringFlagHandle };
937
+ export { BoolFlagHandle, Config, type ConfigChangeEvent, ConfigClient, ConfigRuntime, type ConfigStats, type ConnectOptions, type ConnectionStatus, Context, ContextType, type CreateConfigOptions, Flag, FlagChangeEvent, FlagStats, type FlagType, FlagsClient, type GetConfigOptions, JsonFlagHandle, NumberFlagHandle, Rule, SharedWebSocket, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotConnectedError, SmplNotFoundError, SmplTimeoutError, SmplValidationError, StringFlagHandle };