@smplkit/sdk 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -362,6 +362,7 @@ var ConfigClient = class {
362
362
  _parent = null;
363
363
  _configCache = {};
364
364
  _connected = false;
365
+ _listeners = [];
365
366
  /** @internal */
366
367
  constructor(apiKey, timeout) {
367
368
  this._apiKey = apiKey;
@@ -497,6 +498,102 @@ var ConfigClient = class {
497
498
  }
498
499
  return itemKey in resolved ? resolved[itemKey] : defaultValue ?? null;
499
500
  }
501
+ /**
502
+ * Return a config value as a string, or `defaultValue` if absent or not a string.
503
+ *
504
+ * @throws {SmplNotConnectedError} If connect() has not been called.
505
+ */
506
+ getString(configKey, itemKey, defaultValue = null) {
507
+ const value = this.getValue(configKey, itemKey);
508
+ return typeof value === "string" ? value : defaultValue;
509
+ }
510
+ /**
511
+ * Return a config value as a number, or `defaultValue` if absent or not a number.
512
+ *
513
+ * @throws {SmplNotConnectedError} If connect() has not been called.
514
+ */
515
+ getInt(configKey, itemKey, defaultValue = null) {
516
+ const value = this.getValue(configKey, itemKey);
517
+ return typeof value === "number" ? value : defaultValue;
518
+ }
519
+ /**
520
+ * Return a config value as a boolean, or `defaultValue` if absent or not a boolean.
521
+ *
522
+ * @throws {SmplNotConnectedError} If connect() has not been called.
523
+ */
524
+ getBool(configKey, itemKey, defaultValue = null) {
525
+ const value = this.getValue(configKey, itemKey);
526
+ return typeof value === "boolean" ? value : defaultValue;
527
+ }
528
+ /**
529
+ * Re-fetch all configs, re-resolve values, and update the cache.
530
+ *
531
+ * Fires change listeners for any values that differ from the previous cache.
532
+ *
533
+ * @throws {SmplNotConnectedError} If connect() has not been called.
534
+ */
535
+ async refresh() {
536
+ if (!this._connected) {
537
+ throw new SmplNotConnectedError("SmplClient is not connected. Call client.connect() first.");
538
+ }
539
+ const environment = this._parent?._environment;
540
+ if (!environment) {
541
+ throw new SmplError("No environment set.");
542
+ }
543
+ const configs = await this.list();
544
+ const newCache = {};
545
+ for (const cfg of configs) {
546
+ const chain = await cfg._buildChain(this._http);
547
+ newCache[cfg.key] = resolveChain(chain, environment);
548
+ }
549
+ const oldCache = this._configCache;
550
+ this._configCache = newCache;
551
+ this._diffAndFire(oldCache, newCache, "manual");
552
+ }
553
+ /**
554
+ * Register a listener that fires when a config value changes (on refresh).
555
+ *
556
+ * @param callback - Called with a {@link ConfigChangeEvent} on each change.
557
+ * @param options.configKey - If provided, only fire for changes to this config.
558
+ * @param options.itemKey - If provided, only fire for changes to this item key.
559
+ */
560
+ onChange(callback, options) {
561
+ this._listeners.push({
562
+ callback,
563
+ configKey: options?.configKey ?? null,
564
+ itemKey: options?.itemKey ?? null
565
+ });
566
+ }
567
+ /** @internal */
568
+ _diffAndFire(oldCache, newCache, source) {
569
+ const allConfigKeys = /* @__PURE__ */ new Set([...Object.keys(oldCache), ...Object.keys(newCache)]);
570
+ for (const cfgKey of allConfigKeys) {
571
+ const oldItems = oldCache[cfgKey] ?? {};
572
+ const newItems = newCache[cfgKey] ?? {};
573
+ const allItemKeys = /* @__PURE__ */ new Set([...Object.keys(oldItems), ...Object.keys(newItems)]);
574
+ for (const iKey of allItemKeys) {
575
+ const oldVal = iKey in oldItems ? oldItems[iKey] : null;
576
+ const newVal = iKey in newItems ? newItems[iKey] : null;
577
+ if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
578
+ const event = {
579
+ configKey: cfgKey,
580
+ itemKey: iKey,
581
+ oldValue: oldVal,
582
+ newValue: newVal,
583
+ source
584
+ };
585
+ for (const listener of this._listeners) {
586
+ if (listener.configKey !== null && listener.configKey !== cfgKey) continue;
587
+ if (listener.itemKey !== null && listener.itemKey !== iKey) continue;
588
+ try {
589
+ listener.callback(event);
590
+ } catch {
591
+ }
592
+ }
593
+ }
594
+ }
595
+ }
596
+ }
500
597
  /**
501
598
  * Internal: PUT a full config update and return the updated model.
502
599
  *
@@ -1869,215 +1966,6 @@ var SmplClient = class {
1869
1966
  }
1870
1967
  };
1871
1968
 
1872
- // src/config/runtime.ts
1873
- var ConfigRuntime = class {
1874
- _cache;
1875
- _chain;
1876
- _fetchCount;
1877
- _lastFetchAt;
1878
- _closed = false;
1879
- _listeners = [];
1880
- _environment;
1881
- _fetchChain;
1882
- _sharedWs = null;
1883
- /** @internal */
1884
- constructor(options) {
1885
- this._environment = options.environment;
1886
- this._fetchChain = options.fetchChain;
1887
- this._chain = options.chain;
1888
- this._cache = resolveChain(options.chain, options.environment);
1889
- this._fetchCount = options.chain.length;
1890
- this._lastFetchAt = (/* @__PURE__ */ new Date()).toISOString();
1891
- if (options.sharedWs) {
1892
- this._sharedWs = options.sharedWs;
1893
- this._sharedWs.on("config_changed", this._handleConfigChanged);
1894
- this._sharedWs.on("config_deleted", this._handleConfigDeleted);
1895
- }
1896
- }
1897
- // ---- Value access (synchronous, local cache) ----
1898
- /**
1899
- * Return the resolved value for `key`, or `defaultValue` if absent.
1900
- *
1901
- * @param key - The config key to look up.
1902
- * @param defaultValue - Returned when the key is not present (default: null).
1903
- */
1904
- get(key, defaultValue = null) {
1905
- return key in this._cache ? this._cache[key] : defaultValue;
1906
- }
1907
- /**
1908
- * Return the value as a string, or `defaultValue` if absent or not a string.
1909
- */
1910
- getString(key, defaultValue = null) {
1911
- const value = this._cache[key];
1912
- return typeof value === "string" ? value : defaultValue;
1913
- }
1914
- /**
1915
- * Return the value as a number, or `defaultValue` if absent or not a number.
1916
- */
1917
- getInt(key, defaultValue = null) {
1918
- const value = this._cache[key];
1919
- return typeof value === "number" ? value : defaultValue;
1920
- }
1921
- /**
1922
- * Return the value as a boolean, or `defaultValue` if absent or not a boolean.
1923
- */
1924
- getBool(key, defaultValue = null) {
1925
- const value = this._cache[key];
1926
- return typeof value === "boolean" ? value : defaultValue;
1927
- }
1928
- /**
1929
- * Return whether `key` is present in the resolved configuration.
1930
- */
1931
- exists(key) {
1932
- return key in this._cache;
1933
- }
1934
- /**
1935
- * Return a shallow copy of the full resolved configuration.
1936
- */
1937
- getAll() {
1938
- return { ...this._cache };
1939
- }
1940
- // ---- Change listeners ----
1941
- /**
1942
- * Register a listener that fires when a config value changes.
1943
- *
1944
- * @param callback - Called with a {@link ConfigChangeEvent} on each change.
1945
- * @param options.key - If provided, the listener fires only for this key.
1946
- * If omitted, the listener fires for all changes.
1947
- */
1948
- onChange(callback, options) {
1949
- this._listeners.push({
1950
- callback,
1951
- key: options?.key ?? null
1952
- });
1953
- }
1954
- // ---- Diagnostics ----
1955
- /**
1956
- * Return diagnostic statistics for this runtime.
1957
- */
1958
- stats() {
1959
- return {
1960
- fetchCount: this._fetchCount,
1961
- lastFetchAt: this._lastFetchAt
1962
- };
1963
- }
1964
- /**
1965
- * Return the current WebSocket connection status.
1966
- */
1967
- connectionStatus() {
1968
- if (this._sharedWs) {
1969
- return this._sharedWs.connectionStatus;
1970
- }
1971
- return "disconnected";
1972
- }
1973
- // ---- Lifecycle ----
1974
- /**
1975
- * Force a manual refresh of the cached configuration.
1976
- *
1977
- * Re-fetches the full config chain via HTTP, re-resolves values, updates
1978
- * the local cache, and fires listeners for any detected changes.
1979
- *
1980
- * @throws {Error} If no `fetchChain` function was provided on construction.
1981
- */
1982
- async refresh() {
1983
- if (!this._fetchChain) {
1984
- throw new Error("No fetchChain function provided; cannot refresh.");
1985
- }
1986
- const newChain = await this._fetchChain();
1987
- const oldCache = this._cache;
1988
- this._chain = newChain;
1989
- this._cache = resolveChain(newChain, this._environment);
1990
- this._fetchCount += newChain.length;
1991
- this._lastFetchAt = (/* @__PURE__ */ new Date()).toISOString();
1992
- this._diffAndFire(oldCache, this._cache, "manual");
1993
- }
1994
- /**
1995
- * Close the runtime connection.
1996
- *
1997
- * Unregisters from the shared WebSocket. Safe to call multiple times.
1998
- */
1999
- async close() {
2000
- this._closed = true;
2001
- if (this._sharedWs !== null) {
2002
- this._sharedWs.off("config_changed", this._handleConfigChanged);
2003
- this._sharedWs.off("config_deleted", this._handleConfigDeleted);
2004
- this._sharedWs = null;
2005
- }
2006
- }
2007
- /**
2008
- * Async dispose support for `await using` (TypeScript 5.2+).
2009
- */
2010
- async [Symbol.asyncDispose]() {
2011
- await this.close();
2012
- }
2013
- // ---- Shared WebSocket event handlers ----
2014
- _handleConfigChanged = (data) => {
2015
- if (this._closed) return;
2016
- const configId = data.config_id;
2017
- const changes = data.changes;
2018
- if (configId && changes) {
2019
- this._applyChanges(configId, changes);
2020
- } else if (this._fetchChain) {
2021
- void this._fetchChain().then((newChain) => {
2022
- const oldCache = this._cache;
2023
- this._chain = newChain;
2024
- this._cache = resolveChain(newChain, this._environment);
2025
- this._fetchCount += newChain.length;
2026
- this._lastFetchAt = (/* @__PURE__ */ new Date()).toISOString();
2027
- this._diffAndFire(oldCache, this._cache, "websocket");
2028
- }).catch(() => {
2029
- });
2030
- }
2031
- };
2032
- _handleConfigDeleted = (_data) => {
2033
- this._closed = true;
2034
- void this.close();
2035
- };
2036
- _applyChanges(configId, changes) {
2037
- const chainEntry = this._chain.find((c) => c.id === configId);
2038
- if (!chainEntry) return;
2039
- for (const change of changes) {
2040
- const { key, new_value } = change;
2041
- const envEntry = chainEntry.environments[this._environment] !== void 0 && chainEntry.environments[this._environment] !== null ? chainEntry.environments[this._environment] : null;
2042
- const envValues = envEntry !== null && typeof envEntry === "object" ? envEntry.values ?? {} : null;
2043
- if (new_value === null || new_value === void 0) {
2044
- delete chainEntry.items[key];
2045
- if (envValues) delete envValues[key];
2046
- } else if (envValues && key in envValues) {
2047
- envValues[key] = new_value;
2048
- } else if (key in chainEntry.items) {
2049
- chainEntry.items[key] = new_value;
2050
- } else {
2051
- chainEntry.items[key] = new_value;
2052
- }
2053
- }
2054
- const oldCache = this._cache;
2055
- this._cache = resolveChain(this._chain, this._environment);
2056
- this._diffAndFire(oldCache, this._cache, "websocket");
2057
- }
2058
- _diffAndFire(oldCache, newCache, source) {
2059
- const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldCache), ...Object.keys(newCache)]);
2060
- for (const key of allKeys) {
2061
- const oldVal = key in oldCache ? oldCache[key] : null;
2062
- const newVal = key in newCache ? newCache[key] : null;
2063
- if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
2064
- const event = { key, oldValue: oldVal, newValue: newVal, source };
2065
- this._fireListeners(event);
2066
- }
2067
- }
2068
- }
2069
- _fireListeners(event) {
2070
- for (const listener of this._listeners) {
2071
- if (listener.key === null || listener.key === event.key) {
2072
- try {
2073
- listener.callback(event);
2074
- } catch {
2075
- }
2076
- }
2077
- }
2078
- }
2079
- };
2080
-
2081
1969
  // src/flags/types.ts
2082
1970
  var Context = class {
2083
1971
  type;
@@ -2146,7 +2034,6 @@ export {
2146
2034
  BoolFlagHandle,
2147
2035
  Config,
2148
2036
  ConfigClient,
2149
- ConfigRuntime,
2150
2037
  Context,
2151
2038
  ContextType,
2152
2039
  Flag,