@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.cjs +355 -302
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +244 -199
- package/dist/index.d.ts +244 -199
- package/dist/index.js +355 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2VYY5OMH.js +0 -241
- package/dist/chunk-2VYY5OMH.js.map +0 -1
- package/dist/runtime-MIIY5ZNG.js +0 -7
- package/dist/runtime-MIIY5ZNG.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ConfigRuntime
|
|
3
|
-
} from "./chunk-2VYY5OMH.js";
|
|
4
|
-
|
|
5
1
|
// src/config/client.ts
|
|
6
2
|
import createClient from "openapi-fetch";
|
|
7
3
|
|
|
@@ -47,6 +43,13 @@ var SmplConflictError = class extends SmplError {
|
|
|
47
43
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
48
44
|
}
|
|
49
45
|
};
|
|
46
|
+
var SmplNotConnectedError = class extends SmplError {
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "SmplNotConnectedError";
|
|
50
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
50
53
|
var SmplValidationError = class extends SmplError {
|
|
51
54
|
constructor(message, statusCode, responseBody) {
|
|
52
55
|
super(message, statusCode ?? 422, responseBody);
|
|
@@ -55,6 +58,34 @@ var SmplValidationError = class extends SmplError {
|
|
|
55
58
|
}
|
|
56
59
|
};
|
|
57
60
|
|
|
61
|
+
// src/config/resolve.ts
|
|
62
|
+
function deepMerge(base, override) {
|
|
63
|
+
const result = { ...base };
|
|
64
|
+
for (const [key, value] of Object.entries(override)) {
|
|
65
|
+
if (key in result && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key]) && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
66
|
+
result[key] = deepMerge(
|
|
67
|
+
result[key],
|
|
68
|
+
value
|
|
69
|
+
);
|
|
70
|
+
} else {
|
|
71
|
+
result[key] = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
function resolveChain(chain, environment) {
|
|
77
|
+
let accumulated = {};
|
|
78
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
79
|
+
const config = chain[i];
|
|
80
|
+
const baseValues = config.items ?? {};
|
|
81
|
+
const envEntry = (config.environments ?? {})[environment];
|
|
82
|
+
const envValues = envEntry !== null && envEntry !== void 0 && typeof envEntry === "object" && !Array.isArray(envEntry) ? envEntry.values ?? {} : {};
|
|
83
|
+
const configResolved = deepMerge(baseValues, envValues);
|
|
84
|
+
accumulated = deepMerge(accumulated, configResolved);
|
|
85
|
+
}
|
|
86
|
+
return accumulated;
|
|
87
|
+
}
|
|
88
|
+
|
|
58
89
|
// src/config/types.ts
|
|
59
90
|
var Config = class {
|
|
60
91
|
/** UUID of the config. */
|
|
@@ -181,46 +212,6 @@ var Config = class {
|
|
|
181
212
|
await this.setValues(existing, environment);
|
|
182
213
|
}
|
|
183
214
|
}
|
|
184
|
-
/**
|
|
185
|
-
* Connect to this config for runtime value resolution.
|
|
186
|
-
*
|
|
187
|
-
* Eagerly fetches this config and its full parent chain, resolves values
|
|
188
|
-
* for the given environment via deep merge, and returns a
|
|
189
|
-
* {@link ConfigRuntime} with a fully populated local cache.
|
|
190
|
-
*
|
|
191
|
-
* A background WebSocket connection is started for real-time updates.
|
|
192
|
-
* If the WebSocket fails to connect, the runtime operates in cache-only
|
|
193
|
-
* mode and reconnects automatically.
|
|
194
|
-
*
|
|
195
|
-
* Supports both `await` and `await using` (TypeScript 5.2+)::
|
|
196
|
-
*
|
|
197
|
-
* ```typescript
|
|
198
|
-
* // Simple await
|
|
199
|
-
* const runtime = await config.connect("production");
|
|
200
|
-
* try { ... } finally { await runtime.close(); }
|
|
201
|
-
*
|
|
202
|
-
* // await using (auto-close)
|
|
203
|
-
* await using runtime = await config.connect("production");
|
|
204
|
-
* ```
|
|
205
|
-
*
|
|
206
|
-
* @param environment - The environment to resolve for (e.g. `"production"`).
|
|
207
|
-
* @param options.timeout - Milliseconds to wait for the initial fetch.
|
|
208
|
-
*/
|
|
209
|
-
async connect(environment, options) {
|
|
210
|
-
const { ConfigRuntime: ConfigRuntime2 } = await import("./runtime-MIIY5ZNG.js");
|
|
211
|
-
const timeout = options?.timeout ?? 3e4;
|
|
212
|
-
const chain = await this._buildChain(timeout);
|
|
213
|
-
return new ConfigRuntime2({
|
|
214
|
-
configKey: this.key,
|
|
215
|
-
configId: this.id,
|
|
216
|
-
environment,
|
|
217
|
-
chain,
|
|
218
|
-
apiKey: this._client._apiKey,
|
|
219
|
-
baseUrl: this._client._baseUrl,
|
|
220
|
-
fetchChain: () => this._buildChain(timeout),
|
|
221
|
-
sharedWs: this._client._getSharedWs ? this._client._getSharedWs() : null
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
215
|
/**
|
|
225
216
|
* Walk the parent chain and return config data objects, child-to-root.
|
|
226
217
|
* @internal
|
|
@@ -367,6 +358,10 @@ var ConfigClient = class {
|
|
|
367
358
|
_http;
|
|
368
359
|
/** @internal — returns the shared WebSocket for real-time updates. */
|
|
369
360
|
_getSharedWs;
|
|
361
|
+
/** @internal — set by SmplClient after construction. */
|
|
362
|
+
_parent = null;
|
|
363
|
+
_configCache = {};
|
|
364
|
+
_connected = false;
|
|
370
365
|
/** @internal */
|
|
371
366
|
constructor(apiKey, timeout) {
|
|
372
367
|
this._apiKey = apiKey;
|
|
@@ -464,6 +459,44 @@ var ConfigClient = class {
|
|
|
464
459
|
wrapFetchError(err);
|
|
465
460
|
}
|
|
466
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* Fetch all configs, resolve values for the environment, and cache.
|
|
464
|
+
* @internal — called by SmplClient.connect().
|
|
465
|
+
*/
|
|
466
|
+
async _connectInternal(environment) {
|
|
467
|
+
const configs = await this.list();
|
|
468
|
+
const cache = {};
|
|
469
|
+
for (const cfg of configs) {
|
|
470
|
+
const chain = await cfg._buildChain(this._http);
|
|
471
|
+
cache[cfg.key] = resolveChain(chain, environment);
|
|
472
|
+
}
|
|
473
|
+
this._configCache = cache;
|
|
474
|
+
this._connected = true;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Read a resolved config value (prescriptive access).
|
|
478
|
+
*
|
|
479
|
+
* Requires {@link SmplClient.connect} to have been called.
|
|
480
|
+
*
|
|
481
|
+
* @param configKey - The config key to look up.
|
|
482
|
+
* @param itemKey - Optional specific item key. If omitted, returns all values.
|
|
483
|
+
* @param defaultValue - Default value if the key is missing.
|
|
484
|
+
*
|
|
485
|
+
* @throws {SmplNotConnectedError} If connect() has not been called.
|
|
486
|
+
*/
|
|
487
|
+
getValue(configKey, itemKey, defaultValue) {
|
|
488
|
+
if (!this._connected) {
|
|
489
|
+
throw new SmplNotConnectedError("SmplClient is not connected. Call client.connect() first.");
|
|
490
|
+
}
|
|
491
|
+
const resolved = this._configCache[configKey];
|
|
492
|
+
if (resolved === void 0) {
|
|
493
|
+
return defaultValue ?? null;
|
|
494
|
+
}
|
|
495
|
+
if (itemKey === void 0) {
|
|
496
|
+
return { ...resolved };
|
|
497
|
+
}
|
|
498
|
+
return itemKey in resolved ? resolved[itemKey] : defaultValue ?? null;
|
|
499
|
+
}
|
|
467
500
|
/**
|
|
468
501
|
* Internal: PUT a full config update and return the updated model.
|
|
469
502
|
*
|
|
@@ -1051,6 +1084,8 @@ var FlagsClient = class {
|
|
|
1051
1084
|
// Shared WebSocket (set during connect)
|
|
1052
1085
|
_wsManager = null;
|
|
1053
1086
|
_ensureWs;
|
|
1087
|
+
/** @internal — set by SmplClient after construction. */
|
|
1088
|
+
_parent = null;
|
|
1054
1089
|
/** @internal */
|
|
1055
1090
|
constructor(apiKey, ensureWs, timeout) {
|
|
1056
1091
|
this._apiKey = apiKey;
|
|
@@ -1289,8 +1324,9 @@ var FlagsClient = class {
|
|
|
1289
1324
|
/**
|
|
1290
1325
|
* Connect to an environment: fetch flag definitions, register on
|
|
1291
1326
|
* shared WebSocket, enable local evaluation.
|
|
1327
|
+
* @internal — called by SmplClient.connect().
|
|
1292
1328
|
*/
|
|
1293
|
-
async
|
|
1329
|
+
async _connectInternal(environment) {
|
|
1294
1330
|
this._environment = environment;
|
|
1295
1331
|
await this._fetchAllFlags();
|
|
1296
1332
|
this._connected = true;
|
|
@@ -1377,6 +1413,9 @@ var FlagsClient = class {
|
|
|
1377
1413
|
*/
|
|
1378
1414
|
async evaluate(key, options) {
|
|
1379
1415
|
const evalDict = contextsToEvalDict(options.context);
|
|
1416
|
+
if (this._parent?._service && !("service" in evalDict)) {
|
|
1417
|
+
evalDict["service"] = { key: this._parent._service };
|
|
1418
|
+
}
|
|
1380
1419
|
let flagDef = null;
|
|
1381
1420
|
if (this._connected && key in this._flagStore) {
|
|
1382
1421
|
flagDef = this._flagStore[key];
|
|
@@ -1400,7 +1439,7 @@ var FlagsClient = class {
|
|
|
1400
1439
|
/** @internal */
|
|
1401
1440
|
_evaluateHandle(key, defaultValue, context) {
|
|
1402
1441
|
if (!this._connected) {
|
|
1403
|
-
|
|
1442
|
+
throw new SmplNotConnectedError("SmplClient is not connected. Call client.connect() first.");
|
|
1404
1443
|
}
|
|
1405
1444
|
let evalDict;
|
|
1406
1445
|
if (context !== null) {
|
|
@@ -1415,6 +1454,9 @@ var FlagsClient = class {
|
|
|
1415
1454
|
} else {
|
|
1416
1455
|
evalDict = {};
|
|
1417
1456
|
}
|
|
1457
|
+
if (this._parent?._service && !("service" in evalDict)) {
|
|
1458
|
+
evalDict["service"] = { key: this._parent._service };
|
|
1459
|
+
}
|
|
1418
1460
|
const ctxHash = hashContext(evalDict);
|
|
1419
1461
|
const cacheKey = `${key}:${ctxHash}`;
|
|
1420
1462
|
const [hit, cachedValue] = this._cache.get(cacheKey);
|
|
@@ -1741,6 +1783,7 @@ function resolveApiKey(explicit) {
|
|
|
1741
1783
|
|
|
1742
1784
|
// src/client.ts
|
|
1743
1785
|
var APP_BASE_URL2 = "https://app.smplkit.com";
|
|
1786
|
+
var NO_ENVIRONMENT_MESSAGE = "No environment provided. Set one of:\n 1. Pass environment to the constructor\n 2. Set the SMPLKIT_ENVIRONMENT environment variable";
|
|
1744
1787
|
var SmplClient = class {
|
|
1745
1788
|
/** Client for config management-plane operations. */
|
|
1746
1789
|
config;
|
|
@@ -1748,12 +1791,66 @@ var SmplClient = class {
|
|
|
1748
1791
|
flags;
|
|
1749
1792
|
_wsManager = null;
|
|
1750
1793
|
_apiKey;
|
|
1794
|
+
/** @internal */
|
|
1795
|
+
_environment;
|
|
1796
|
+
/** @internal */
|
|
1797
|
+
_service;
|
|
1798
|
+
_connected = false;
|
|
1799
|
+
_timeout;
|
|
1751
1800
|
constructor(options = {}) {
|
|
1752
1801
|
const apiKey = resolveApiKey(options.apiKey);
|
|
1753
1802
|
this._apiKey = apiKey;
|
|
1754
|
-
|
|
1755
|
-
|
|
1803
|
+
const environment = options.environment || process.env.SMPLKIT_ENVIRONMENT;
|
|
1804
|
+
if (!environment) {
|
|
1805
|
+
throw new SmplError(NO_ENVIRONMENT_MESSAGE);
|
|
1806
|
+
}
|
|
1807
|
+
this._environment = environment;
|
|
1808
|
+
this._service = options.service || process.env.SMPLKIT_SERVICE || null;
|
|
1809
|
+
this._timeout = options.timeout ?? 3e4;
|
|
1810
|
+
this.config = new ConfigClient(apiKey, this._timeout);
|
|
1811
|
+
this.flags = new FlagsClient(apiKey, () => this._ensureWs(), this._timeout);
|
|
1756
1812
|
this.config._getSharedWs = () => this._ensureWs();
|
|
1813
|
+
this.flags._parent = this;
|
|
1814
|
+
this.config._parent = this;
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Connect to the smplkit platform.
|
|
1818
|
+
*
|
|
1819
|
+
* Fetches initial flag and config data, opens the shared WebSocket,
|
|
1820
|
+
* and registers the service as a context instance (if provided).
|
|
1821
|
+
*
|
|
1822
|
+
* This method is idempotent — calling it multiple times is safe.
|
|
1823
|
+
*/
|
|
1824
|
+
async connect() {
|
|
1825
|
+
if (this._connected) return;
|
|
1826
|
+
if (this._service) {
|
|
1827
|
+
await this._registerServiceContext();
|
|
1828
|
+
}
|
|
1829
|
+
await this.flags._connectInternal(this._environment);
|
|
1830
|
+
await this.config._connectInternal(this._environment);
|
|
1831
|
+
this._connected = true;
|
|
1832
|
+
}
|
|
1833
|
+
/** @internal */
|
|
1834
|
+
async _registerServiceContext() {
|
|
1835
|
+
try {
|
|
1836
|
+
await fetch(`${APP_BASE_URL2}/api/v1/contexts/bulk`, {
|
|
1837
|
+
method: "PUT",
|
|
1838
|
+
headers: {
|
|
1839
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
1840
|
+
"Content-Type": "application/json"
|
|
1841
|
+
},
|
|
1842
|
+
body: JSON.stringify({
|
|
1843
|
+
contexts: [
|
|
1844
|
+
{
|
|
1845
|
+
type: "service",
|
|
1846
|
+
key: this._service,
|
|
1847
|
+
attributes: { name: this._service }
|
|
1848
|
+
}
|
|
1849
|
+
]
|
|
1850
|
+
})
|
|
1851
|
+
});
|
|
1852
|
+
} catch {
|
|
1853
|
+
}
|
|
1757
1854
|
}
|
|
1758
1855
|
/** Lazily create and start the shared WebSocket. @internal */
|
|
1759
1856
|
_ensureWs() {
|
|
@@ -1772,6 +1869,215 @@ var SmplClient = class {
|
|
|
1772
1869
|
}
|
|
1773
1870
|
};
|
|
1774
1871
|
|
|
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
|
+
|
|
1775
2081
|
// src/flags/types.ts
|
|
1776
2082
|
var Context = class {
|
|
1777
2083
|
type;
|
|
@@ -1855,6 +2161,7 @@ export {
|
|
|
1855
2161
|
SmplConflictError,
|
|
1856
2162
|
SmplConnectionError,
|
|
1857
2163
|
SmplError,
|
|
2164
|
+
SmplNotConnectedError,
|
|
1858
2165
|
SmplNotFoundError,
|
|
1859
2166
|
SmplTimeoutError,
|
|
1860
2167
|
SmplValidationError,
|