@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.cjs CHANGED
@@ -33,7 +33,6 @@ __export(index_exports, {
33
33
  BoolFlagHandle: () => BoolFlagHandle,
34
34
  Config: () => Config,
35
35
  ConfigClient: () => ConfigClient,
36
- ConfigRuntime: () => ConfigRuntime,
37
36
  Context: () => Context,
38
37
  ContextType: () => ContextType,
39
38
  Flag: () => Flag,
@@ -420,6 +419,7 @@ var ConfigClient = class {
420
419
  _parent = null;
421
420
  _configCache = {};
422
421
  _connected = false;
422
+ _listeners = [];
423
423
  /** @internal */
424
424
  constructor(apiKey, timeout) {
425
425
  this._apiKey = apiKey;
@@ -555,6 +555,102 @@ var ConfigClient = class {
555
555
  }
556
556
  return itemKey in resolved ? resolved[itemKey] : defaultValue ?? null;
557
557
  }
558
+ /**
559
+ * Return a config value as a string, or `defaultValue` if absent or not a string.
560
+ *
561
+ * @throws {SmplNotConnectedError} If connect() has not been called.
562
+ */
563
+ getString(configKey, itemKey, defaultValue = null) {
564
+ const value = this.getValue(configKey, itemKey);
565
+ return typeof value === "string" ? value : defaultValue;
566
+ }
567
+ /**
568
+ * Return a config value as a number, or `defaultValue` if absent or not a number.
569
+ *
570
+ * @throws {SmplNotConnectedError} If connect() has not been called.
571
+ */
572
+ getInt(configKey, itemKey, defaultValue = null) {
573
+ const value = this.getValue(configKey, itemKey);
574
+ return typeof value === "number" ? value : defaultValue;
575
+ }
576
+ /**
577
+ * Return a config value as a boolean, or `defaultValue` if absent or not a boolean.
578
+ *
579
+ * @throws {SmplNotConnectedError} If connect() has not been called.
580
+ */
581
+ getBool(configKey, itemKey, defaultValue = null) {
582
+ const value = this.getValue(configKey, itemKey);
583
+ return typeof value === "boolean" ? value : defaultValue;
584
+ }
585
+ /**
586
+ * Re-fetch all configs, re-resolve values, and update the cache.
587
+ *
588
+ * Fires change listeners for any values that differ from the previous cache.
589
+ *
590
+ * @throws {SmplNotConnectedError} If connect() has not been called.
591
+ */
592
+ async refresh() {
593
+ if (!this._connected) {
594
+ throw new SmplNotConnectedError("SmplClient is not connected. Call client.connect() first.");
595
+ }
596
+ const environment = this._parent?._environment;
597
+ if (!environment) {
598
+ throw new SmplError("No environment set.");
599
+ }
600
+ const configs = await this.list();
601
+ const newCache = {};
602
+ for (const cfg of configs) {
603
+ const chain = await cfg._buildChain(this._http);
604
+ newCache[cfg.key] = resolveChain(chain, environment);
605
+ }
606
+ const oldCache = this._configCache;
607
+ this._configCache = newCache;
608
+ this._diffAndFire(oldCache, newCache, "manual");
609
+ }
610
+ /**
611
+ * Register a listener that fires when a config value changes (on refresh).
612
+ *
613
+ * @param callback - Called with a {@link ConfigChangeEvent} on each change.
614
+ * @param options.configKey - If provided, only fire for changes to this config.
615
+ * @param options.itemKey - If provided, only fire for changes to this item key.
616
+ */
617
+ onChange(callback, options) {
618
+ this._listeners.push({
619
+ callback,
620
+ configKey: options?.configKey ?? null,
621
+ itemKey: options?.itemKey ?? null
622
+ });
623
+ }
624
+ /** @internal */
625
+ _diffAndFire(oldCache, newCache, source) {
626
+ const allConfigKeys = /* @__PURE__ */ new Set([...Object.keys(oldCache), ...Object.keys(newCache)]);
627
+ for (const cfgKey of allConfigKeys) {
628
+ const oldItems = oldCache[cfgKey] ?? {};
629
+ const newItems = newCache[cfgKey] ?? {};
630
+ const allItemKeys = /* @__PURE__ */ new Set([...Object.keys(oldItems), ...Object.keys(newItems)]);
631
+ for (const iKey of allItemKeys) {
632
+ const oldVal = iKey in oldItems ? oldItems[iKey] : null;
633
+ const newVal = iKey in newItems ? newItems[iKey] : null;
634
+ if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
635
+ const event = {
636
+ configKey: cfgKey,
637
+ itemKey: iKey,
638
+ oldValue: oldVal,
639
+ newValue: newVal,
640
+ source
641
+ };
642
+ for (const listener of this._listeners) {
643
+ if (listener.configKey !== null && listener.configKey !== cfgKey) continue;
644
+ if (listener.itemKey !== null && listener.itemKey !== iKey) continue;
645
+ try {
646
+ listener.callback(event);
647
+ } catch {
648
+ }
649
+ }
650
+ }
651
+ }
652
+ }
653
+ }
558
654
  /**
559
655
  * Internal: PUT a full config update and return the updated model.
560
656
  *
@@ -1927,215 +2023,6 @@ var SmplClient = class {
1927
2023
  }
1928
2024
  };
1929
2025
 
1930
- // src/config/runtime.ts
1931
- var ConfigRuntime = class {
1932
- _cache;
1933
- _chain;
1934
- _fetchCount;
1935
- _lastFetchAt;
1936
- _closed = false;
1937
- _listeners = [];
1938
- _environment;
1939
- _fetchChain;
1940
- _sharedWs = null;
1941
- /** @internal */
1942
- constructor(options) {
1943
- this._environment = options.environment;
1944
- this._fetchChain = options.fetchChain;
1945
- this._chain = options.chain;
1946
- this._cache = resolveChain(options.chain, options.environment);
1947
- this._fetchCount = options.chain.length;
1948
- this._lastFetchAt = (/* @__PURE__ */ new Date()).toISOString();
1949
- if (options.sharedWs) {
1950
- this._sharedWs = options.sharedWs;
1951
- this._sharedWs.on("config_changed", this._handleConfigChanged);
1952
- this._sharedWs.on("config_deleted", this._handleConfigDeleted);
1953
- }
1954
- }
1955
- // ---- Value access (synchronous, local cache) ----
1956
- /**
1957
- * Return the resolved value for `key`, or `defaultValue` if absent.
1958
- *
1959
- * @param key - The config key to look up.
1960
- * @param defaultValue - Returned when the key is not present (default: null).
1961
- */
1962
- get(key, defaultValue = null) {
1963
- return key in this._cache ? this._cache[key] : defaultValue;
1964
- }
1965
- /**
1966
- * Return the value as a string, or `defaultValue` if absent or not a string.
1967
- */
1968
- getString(key, defaultValue = null) {
1969
- const value = this._cache[key];
1970
- return typeof value === "string" ? value : defaultValue;
1971
- }
1972
- /**
1973
- * Return the value as a number, or `defaultValue` if absent or not a number.
1974
- */
1975
- getInt(key, defaultValue = null) {
1976
- const value = this._cache[key];
1977
- return typeof value === "number" ? value : defaultValue;
1978
- }
1979
- /**
1980
- * Return the value as a boolean, or `defaultValue` if absent or not a boolean.
1981
- */
1982
- getBool(key, defaultValue = null) {
1983
- const value = this._cache[key];
1984
- return typeof value === "boolean" ? value : defaultValue;
1985
- }
1986
- /**
1987
- * Return whether `key` is present in the resolved configuration.
1988
- */
1989
- exists(key) {
1990
- return key in this._cache;
1991
- }
1992
- /**
1993
- * Return a shallow copy of the full resolved configuration.
1994
- */
1995
- getAll() {
1996
- return { ...this._cache };
1997
- }
1998
- // ---- Change listeners ----
1999
- /**
2000
- * Register a listener that fires when a config value changes.
2001
- *
2002
- * @param callback - Called with a {@link ConfigChangeEvent} on each change.
2003
- * @param options.key - If provided, the listener fires only for this key.
2004
- * If omitted, the listener fires for all changes.
2005
- */
2006
- onChange(callback, options) {
2007
- this._listeners.push({
2008
- callback,
2009
- key: options?.key ?? null
2010
- });
2011
- }
2012
- // ---- Diagnostics ----
2013
- /**
2014
- * Return diagnostic statistics for this runtime.
2015
- */
2016
- stats() {
2017
- return {
2018
- fetchCount: this._fetchCount,
2019
- lastFetchAt: this._lastFetchAt
2020
- };
2021
- }
2022
- /**
2023
- * Return the current WebSocket connection status.
2024
- */
2025
- connectionStatus() {
2026
- if (this._sharedWs) {
2027
- return this._sharedWs.connectionStatus;
2028
- }
2029
- return "disconnected";
2030
- }
2031
- // ---- Lifecycle ----
2032
- /**
2033
- * Force a manual refresh of the cached configuration.
2034
- *
2035
- * Re-fetches the full config chain via HTTP, re-resolves values, updates
2036
- * the local cache, and fires listeners for any detected changes.
2037
- *
2038
- * @throws {Error} If no `fetchChain` function was provided on construction.
2039
- */
2040
- async refresh() {
2041
- if (!this._fetchChain) {
2042
- throw new Error("No fetchChain function provided; cannot refresh.");
2043
- }
2044
- const newChain = await this._fetchChain();
2045
- const oldCache = this._cache;
2046
- this._chain = newChain;
2047
- this._cache = resolveChain(newChain, this._environment);
2048
- this._fetchCount += newChain.length;
2049
- this._lastFetchAt = (/* @__PURE__ */ new Date()).toISOString();
2050
- this._diffAndFire(oldCache, this._cache, "manual");
2051
- }
2052
- /**
2053
- * Close the runtime connection.
2054
- *
2055
- * Unregisters from the shared WebSocket. Safe to call multiple times.
2056
- */
2057
- async close() {
2058
- this._closed = true;
2059
- if (this._sharedWs !== null) {
2060
- this._sharedWs.off("config_changed", this._handleConfigChanged);
2061
- this._sharedWs.off("config_deleted", this._handleConfigDeleted);
2062
- this._sharedWs = null;
2063
- }
2064
- }
2065
- /**
2066
- * Async dispose support for `await using` (TypeScript 5.2+).
2067
- */
2068
- async [Symbol.asyncDispose]() {
2069
- await this.close();
2070
- }
2071
- // ---- Shared WebSocket event handlers ----
2072
- _handleConfigChanged = (data) => {
2073
- if (this._closed) return;
2074
- const configId = data.config_id;
2075
- const changes = data.changes;
2076
- if (configId && changes) {
2077
- this._applyChanges(configId, changes);
2078
- } else if (this._fetchChain) {
2079
- void this._fetchChain().then((newChain) => {
2080
- const oldCache = this._cache;
2081
- this._chain = newChain;
2082
- this._cache = resolveChain(newChain, this._environment);
2083
- this._fetchCount += newChain.length;
2084
- this._lastFetchAt = (/* @__PURE__ */ new Date()).toISOString();
2085
- this._diffAndFire(oldCache, this._cache, "websocket");
2086
- }).catch(() => {
2087
- });
2088
- }
2089
- };
2090
- _handleConfigDeleted = (_data) => {
2091
- this._closed = true;
2092
- void this.close();
2093
- };
2094
- _applyChanges(configId, changes) {
2095
- const chainEntry = this._chain.find((c) => c.id === configId);
2096
- if (!chainEntry) return;
2097
- for (const change of changes) {
2098
- const { key, new_value } = change;
2099
- const envEntry = chainEntry.environments[this._environment] !== void 0 && chainEntry.environments[this._environment] !== null ? chainEntry.environments[this._environment] : null;
2100
- const envValues = envEntry !== null && typeof envEntry === "object" ? envEntry.values ?? {} : null;
2101
- if (new_value === null || new_value === void 0) {
2102
- delete chainEntry.items[key];
2103
- if (envValues) delete envValues[key];
2104
- } else if (envValues && key in envValues) {
2105
- envValues[key] = new_value;
2106
- } else if (key in chainEntry.items) {
2107
- chainEntry.items[key] = new_value;
2108
- } else {
2109
- chainEntry.items[key] = new_value;
2110
- }
2111
- }
2112
- const oldCache = this._cache;
2113
- this._cache = resolveChain(this._chain, this._environment);
2114
- this._diffAndFire(oldCache, this._cache, "websocket");
2115
- }
2116
- _diffAndFire(oldCache, newCache, source) {
2117
- const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldCache), ...Object.keys(newCache)]);
2118
- for (const key of allKeys) {
2119
- const oldVal = key in oldCache ? oldCache[key] : null;
2120
- const newVal = key in newCache ? newCache[key] : null;
2121
- if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
2122
- const event = { key, oldValue: oldVal, newValue: newVal, source };
2123
- this._fireListeners(event);
2124
- }
2125
- }
2126
- }
2127
- _fireListeners(event) {
2128
- for (const listener of this._listeners) {
2129
- if (listener.key === null || listener.key === event.key) {
2130
- try {
2131
- listener.callback(event);
2132
- } catch {
2133
- }
2134
- }
2135
- }
2136
- }
2137
- };
2138
-
2139
2026
  // src/flags/types.ts
2140
2027
  var Context = class {
2141
2028
  type;
@@ -2205,7 +2092,6 @@ var Rule = class {
2205
2092
  BoolFlagHandle,
2206
2093
  Config,
2207
2094
  ConfigClient,
2208
- ConfigRuntime,
2209
2095
  Context,
2210
2096
  ContextType,
2211
2097
  Flag,