@spotify-confidence/openfeature-server-provider-local 0.3.0 → 0.4.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0](https://github.com/spotify/confidence-resolver/compare/openfeature-provider-js-v0.3.0...openfeature-provider-js-v0.4.0) (2025-12-11)
4
+
5
+
6
+ ### Features
7
+
8
+ * **js:** flush assigned when reaching limit ([#145](https://github.com/spotify/confidence-resolver/issues/145)) ([09293ad](https://github.com/spotify/confidence-resolver/commit/09293adc5800f477620905f28539ad45d6a40997))
9
+ * WARN logs for errors in evaluations ([#192](https://github.com/spotify/confidence-resolver/issues/192)) ([7a9f157](https://github.com/spotify/confidence-resolver/commit/7a9f1571639e24ea366e643b5b966b1de6fe06bf))
10
+
3
11
  ## [0.3.0](https://github.com/spotify/confidence-resolver/compare/openfeature-provider-js-v0.2.0...openfeature-provider-js-v0.3.0) (2025-12-02)
4
12
 
5
13
 
@@ -245,6 +245,7 @@ interface LocalResolver {
245
245
  resolveWithSticky(request: ResolveWithStickyRequest): ResolveWithStickyResponse;
246
246
  setResolverState(request: SetResolverStateRequest): void;
247
247
  flushLogs(): Uint8Array;
248
+ flushAssigned(): Uint8Array;
248
249
  }
249
250
  //#endregion
250
251
  //#region src/ConfidenceServerProviderLocal.d.ts
@@ -286,6 +287,8 @@ declare class ConfidenceServerProviderLocal implements Provider {
286
287
  private extractValue;
287
288
  updateState(signal?: AbortSignal): Promise<void>;
288
289
  flush(signal?: AbortSignal): Promise<void>;
290
+ private flushAssigned;
291
+ private sendFlagLogs;
289
292
  private static convertReason;
290
293
  private static convertEvaluationContext;
291
294
  /** Resolves with an evaluation of a Boolean flag */
@@ -1207,10 +1207,10 @@ function isObject(value) {
1207
1207
  function isSet(value) {
1208
1208
  return value !== null && value !== void 0;
1209
1209
  }
1210
- const VERSION = "0.3.0";
1210
+ const VERSION = "0.4.0";
1211
1211
  const NOOP_LOG_FN = Object.assign(() => {}, { enabled: false });
1212
1212
  const debugBackend = loadDebug();
1213
- const logger$1 = new class LoggerImpl {
1213
+ const logger$2 = new class LoggerImpl {
1214
1214
  childLoggers = /* @__PURE__ */ new Map();
1215
1215
  debug = NOOP_LOG_FN;
1216
1216
  info = NOOP_LOG_FN;
@@ -1242,7 +1242,7 @@ const logger$1 = new class LoggerImpl {
1242
1242
  return child;
1243
1243
  }
1244
1244
  }("cnfd");
1245
- const getLogger = logger$1.getLogger.bind(logger$1);
1245
+ const getLogger = logger$2.getLogger.bind(logger$2);
1246
1246
  async function loadDebug() {
1247
1247
  try {
1248
1248
  const { default: debug } = await import("debug");
@@ -1272,7 +1272,7 @@ function scheduleWithFixedInterval(operation, intervalMs, opt = {}) {
1272
1272
  try {
1273
1273
  await operation(ac.signal);
1274
1274
  } catch (e) {
1275
- logger$1.warn("scheduleWithFixedInterval failure:", e);
1275
+ logger$2.warn("scheduleWithFixedInterval failure:", e);
1276
1276
  }
1277
1277
  concurrent--;
1278
1278
  if (Date.now() - lastRunTime > intervalMs && nextRunTimeoutId != 0) {
@@ -1331,7 +1331,7 @@ function promiseSignal(signal) {
1331
1331
  }, { once: true });
1332
1332
  });
1333
1333
  }
1334
- const logger$2 = logger$1.getLogger("fetch");
1334
+ const logger$3 = logger$2.getLogger("fetch");
1335
1335
  let Fetch;
1336
1336
  (function(_Fetch) {
1337
1337
  function create(middleware, sink = fetch) {
@@ -1402,13 +1402,13 @@ function withRetry(opts) {
1402
1402
  const onSuccess = async (resp) => {
1403
1403
  const { status, statusText } = resp;
1404
1404
  if (status !== 408 && status !== 429 && status < 500 || attempts >= maxAttempts) return resp;
1405
- logger$2.debug("withRetry %s failed attempt %d with %d %s", url, attempts - 1, status, statusText);
1405
+ logger$3.debug("withRetry %s failed attempt %d with %d %s", url, attempts - 1, status, statusText);
1406
1406
  const serverDelay = parseRetryAfter(resp.headers.get("Retry-After"), baseInterval, maxInterval);
1407
1407
  await abortableSleep(serverDelay ?? deadline - Date.now(), signal);
1408
1408
  return doTry();
1409
1409
  };
1410
1410
  const onError = async (error) => {
1411
- logger$2.debug("withRetry %s failed attempt %d with %s", url, attempts - 1, error);
1411
+ logger$3.debug("withRetry %s failed attempt %d with %s", url, attempts - 1, error);
1412
1412
  if (signal?.aborted || attempts >= maxAttempts) throw error;
1413
1413
  await abortableSleep(deadline - Date.now(), signal);
1414
1414
  return doTry();
@@ -1461,7 +1461,7 @@ function withRouter(routes) {
1461
1461
  return async (url, init = {}) => {
1462
1462
  const match = table.find(([pred]) => pred(url));
1463
1463
  if (!match) {
1464
- logger$2.info("withRouter no route matched %s, falling through", url);
1464
+ logger$3.info("withRouter no route matched %s, falling through", url);
1465
1465
  return next(url, init);
1466
1466
  }
1467
1467
  return match[1](url, init);
@@ -1471,13 +1471,13 @@ function withRouter(routes) {
1471
1471
  function withResponse(factory) {
1472
1472
  return (_next) => factory;
1473
1473
  }
1474
- const fetchLogger = logger$2;
1475
- function withLogging(logger$3 = fetchLogger) {
1474
+ const fetchLogger = logger$3;
1475
+ function withLogging(logger$4 = fetchLogger) {
1476
1476
  return (next) => async (url, init) => {
1477
1477
  const start = Date.now();
1478
1478
  const resp = await next(url, init);
1479
1479
  const duration = Date.now() - start;
1480
- logger$3.info("%s %s (%i) %dms", (init?.method ?? "get").toUpperCase(), url.split("?", 1)[0], resp.status, duration);
1480
+ logger$4.info("%s %s (%i) %dms", (init?.method ?? "get").toUpperCase(), url.split("?", 1)[0], resp.status, duration);
1481
1481
  return resp;
1482
1482
  };
1483
1483
  }
@@ -1505,6 +1505,7 @@ async function sha256Hex(input) {
1505
1505
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
1506
1506
  return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
1507
1507
  }
1508
+ const logger$1 = getLogger("provider");
1508
1509
  const DEFAULT_STATE_INTERVAL = 3e4;
1509
1510
  const DEFAULT_FLUSH_INTERVAL = 1e4;
1510
1511
  var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
@@ -1576,11 +1577,14 @@ var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
1576
1577
  failFastOnSticky: true
1577
1578
  };
1578
1579
  const response = await this.resolveWithStickyInternal(stickyRequest);
1579
- return this.extractValue(response.resolvedFlags[0], flagName, path, defaultValue);
1580
+ const result = this.extractValue(response.resolvedFlags[0], flagName, path, defaultValue);
1581
+ if (result.errorCode) logger$1.warn(`Flag evaluation for '${flagKey}' returned error code: ${result.errorCode}`);
1582
+ return result;
1580
1583
  }
1581
1584
  async resolveWithStickyInternal(request) {
1582
1585
  const response = this.resolver.resolveWithSticky(request);
1583
1586
  if (response.success && response.success.response) {
1587
+ this.flushAssigned();
1584
1588
  const { response: flagsResponse } = response.success;
1585
1589
  return flagsResponse;
1586
1590
  }
@@ -1644,17 +1648,28 @@ var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
1644
1648
  }
1645
1649
  async flush(signal) {
1646
1650
  const writeFlagLogRequest = this.resolver.flushLogs();
1647
- if (writeFlagLogRequest.length == 0) return;
1648
- const response = await this.fetch("https://resolver.confidence.dev/v1/clientFlagLogs:write", {
1649
- method: "post",
1650
- signal,
1651
- headers: {
1652
- "Content-Type": "application/x-protobuf",
1653
- Authorization: `ClientSecret ${this.options.flagClientSecret}`
1654
- },
1655
- body: writeFlagLogRequest
1656
- });
1657
- if (!response.ok) logger$1.error(`Failed to write flag logs: ${response.status} ${response.statusText} - ${await response.text()}`);
1651
+ if (writeFlagLogRequest.length > 0) await this.sendFlagLogs(writeFlagLogRequest, signal);
1652
+ }
1653
+ async flushAssigned() {
1654
+ const writeFlagLogRequest = this.resolver.flushAssigned();
1655
+ if (writeFlagLogRequest.length > 0) await this.sendFlagLogs(writeFlagLogRequest);
1656
+ }
1657
+ async sendFlagLogs(encodedWriteFlagLogRequest, signal = this.main.signal) {
1658
+ try {
1659
+ const response = await this.fetch("https://resolver.confidence.dev/v1/clientFlagLogs:write", {
1660
+ method: "post",
1661
+ signal,
1662
+ headers: {
1663
+ "Content-Type": "application/x-protobuf",
1664
+ Authorization: `ClientSecret ${this.options.flagClientSecret}`
1665
+ },
1666
+ body: encodedWriteFlagLogRequest
1667
+ });
1668
+ if (!response.ok) logger$1.error(`Failed to write flag logs: ${response.status} ${response.statusText} - ${await response.text()}`);
1669
+ } catch (err) {
1670
+ logger$1.warn("Failed to send flag logs", err);
1671
+ throw err;
1672
+ }
1658
1673
  }
1659
1674
  static convertReason(reason) {
1660
1675
  switch (reason) {
@@ -1711,7 +1726,8 @@ const EXPORT_FN_NAMES = [
1711
1726
  "wasm_msg_free",
1712
1727
  "wasm_msg_guest_resolve_with_sticky",
1713
1728
  "wasm_msg_guest_set_resolver_state",
1714
- "wasm_msg_guest_flush_logs"
1729
+ "wasm_msg_guest_bounded_flush_logs",
1730
+ "wasm_msg_guest_bounded_flush_assign"
1715
1731
  ];
1716
1732
  function verifyExports(exports) {
1717
1733
  for (const fnName of EXPORT_FN_NAMES) if (typeof exports[fnName] !== "function") throw new Error(`Expected Function export "${fnName}" found ${exports[fnName]}`);
@@ -1719,6 +1735,7 @@ function verifyExports(exports) {
1719
1735
  }
1720
1736
  var UnsafeWasmResolver = class {
1721
1737
  exports;
1738
+ flushCount = 0;
1722
1739
  constructor(module$1) {
1723
1740
  const { exports } = new WebAssembly.Instance(module$1, { wasm_msg: { wasm_msg_host_current_time: () => {
1724
1741
  const epochMillisecond = Date.now();
@@ -1743,7 +1760,13 @@ var UnsafeWasmResolver = class {
1743
1760
  this.consumeResponse(resPtr, Void);
1744
1761
  }
1745
1762
  flushLogs() {
1746
- const resPtr = this.exports.wasm_msg_guest_flush_logs(0);
1763
+ const resPtr = this.exports.wasm_msg_guest_bounded_flush_logs(0);
1764
+ const { data, error } = this.consume(resPtr, Response$1);
1765
+ if (error) throw new Error(error);
1766
+ return data;
1767
+ }
1768
+ flushAssigned() {
1769
+ const resPtr = this.exports.wasm_msg_guest_bounded_flush_assign(0);
1747
1770
  const { data, error } = this.consume(resPtr, Response$1);
1748
1771
  if (error) throw new Error(error);
1749
1772
  return data;
@@ -1831,6 +1854,9 @@ var WasmResolver = class {
1831
1854
  throw error;
1832
1855
  }
1833
1856
  }
1857
+ flushAssigned() {
1858
+ return this.delegate.flushAssigned();
1859
+ }
1834
1860
  };
1835
1861
  const wasmUrl = new URL("confidence_resolver.wasm", import.meta.url);
1836
1862
  const module = await WebAssembly.compileStreaming(fetch(wasmUrl));
@@ -245,6 +245,7 @@ interface LocalResolver {
245
245
  resolveWithSticky(request: ResolveWithStickyRequest): ResolveWithStickyResponse;
246
246
  setResolverState(request: SetResolverStateRequest): void;
247
247
  flushLogs(): Uint8Array;
248
+ flushAssigned(): Uint8Array;
248
249
  }
249
250
  //#endregion
250
251
  //#region src/ConfidenceServerProviderLocal.d.ts
@@ -286,6 +287,8 @@ declare class ConfidenceServerProviderLocal implements Provider {
286
287
  private extractValue;
287
288
  updateState(signal?: AbortSignal): Promise<void>;
288
289
  flush(signal?: AbortSignal): Promise<void>;
290
+ private flushAssigned;
291
+ private sendFlagLogs;
289
292
  private static convertReason;
290
293
  private static convertEvaluationContext;
291
294
  /** Resolves with an evaluation of a Boolean flag */
@@ -1210,10 +1210,10 @@ function isObject(value) {
1210
1210
  function isSet(value) {
1211
1211
  return value !== null && value !== void 0;
1212
1212
  }
1213
- const VERSION = "0.3.0";
1213
+ const VERSION = "0.4.0";
1214
1214
  const NOOP_LOG_FN = Object.assign(() => {}, { enabled: false });
1215
1215
  const debugBackend = loadDebug();
1216
- const logger$1 = new class LoggerImpl {
1216
+ const logger$2 = new class LoggerImpl {
1217
1217
  childLoggers = /* @__PURE__ */ new Map();
1218
1218
  debug = NOOP_LOG_FN;
1219
1219
  info = NOOP_LOG_FN;
@@ -1245,7 +1245,7 @@ const logger$1 = new class LoggerImpl {
1245
1245
  return child;
1246
1246
  }
1247
1247
  }("cnfd");
1248
- const getLogger = logger$1.getLogger.bind(logger$1);
1248
+ const getLogger = logger$2.getLogger.bind(logger$2);
1249
1249
  async function loadDebug() {
1250
1250
  try {
1251
1251
  const { default: debug } = await import("debug");
@@ -1275,7 +1275,7 @@ function scheduleWithFixedInterval(operation, intervalMs, opt = {}) {
1275
1275
  try {
1276
1276
  await operation(ac.signal);
1277
1277
  } catch (e) {
1278
- logger$1.warn("scheduleWithFixedInterval failure:", e);
1278
+ logger$2.warn("scheduleWithFixedInterval failure:", e);
1279
1279
  }
1280
1280
  concurrent--;
1281
1281
  if (Date.now() - lastRunTime > intervalMs && nextRunTimeoutId != 0) {
@@ -1334,7 +1334,7 @@ function promiseSignal(signal) {
1334
1334
  }, { once: true });
1335
1335
  });
1336
1336
  }
1337
- const logger$2 = logger$1.getLogger("fetch");
1337
+ const logger$3 = logger$2.getLogger("fetch");
1338
1338
  let Fetch;
1339
1339
  (function(_Fetch) {
1340
1340
  function create(middleware, sink = fetch) {
@@ -1405,13 +1405,13 @@ function withRetry(opts) {
1405
1405
  const onSuccess = async (resp) => {
1406
1406
  const { status, statusText } = resp;
1407
1407
  if (status !== 408 && status !== 429 && status < 500 || attempts >= maxAttempts) return resp;
1408
- logger$2.debug("withRetry %s failed attempt %d with %d %s", url, attempts - 1, status, statusText);
1408
+ logger$3.debug("withRetry %s failed attempt %d with %d %s", url, attempts - 1, status, statusText);
1409
1409
  const serverDelay = parseRetryAfter(resp.headers.get("Retry-After"), baseInterval, maxInterval);
1410
1410
  await abortableSleep(serverDelay ?? deadline - Date.now(), signal);
1411
1411
  return doTry();
1412
1412
  };
1413
1413
  const onError = async (error) => {
1414
- logger$2.debug("withRetry %s failed attempt %d with %s", url, attempts - 1, error);
1414
+ logger$3.debug("withRetry %s failed attempt %d with %s", url, attempts - 1, error);
1415
1415
  if (signal?.aborted || attempts >= maxAttempts) throw error;
1416
1416
  await abortableSleep(deadline - Date.now(), signal);
1417
1417
  return doTry();
@@ -1464,7 +1464,7 @@ function withRouter(routes) {
1464
1464
  return async (url, init = {}) => {
1465
1465
  const match = table.find(([pred]) => pred(url));
1466
1466
  if (!match) {
1467
- logger$2.info("withRouter no route matched %s, falling through", url);
1467
+ logger$3.info("withRouter no route matched %s, falling through", url);
1468
1468
  return next(url, init);
1469
1469
  }
1470
1470
  return match[1](url, init);
@@ -1474,13 +1474,13 @@ function withRouter(routes) {
1474
1474
  function withResponse(factory) {
1475
1475
  return (_next) => factory;
1476
1476
  }
1477
- const fetchLogger = logger$2;
1478
- function withLogging(logger$3 = fetchLogger) {
1477
+ const fetchLogger = logger$3;
1478
+ function withLogging(logger$4 = fetchLogger) {
1479
1479
  return (next) => async (url, init) => {
1480
1480
  const start = Date.now();
1481
1481
  const resp = await next(url, init);
1482
1482
  const duration = Date.now() - start;
1483
- logger$3.info("%s %s (%i) %dms", (init?.method ?? "get").toUpperCase(), url.split("?", 1)[0], resp.status, duration);
1483
+ logger$4.info("%s %s (%i) %dms", (init?.method ?? "get").toUpperCase(), url.split("?", 1)[0], resp.status, duration);
1484
1484
  return resp;
1485
1485
  };
1486
1486
  }
@@ -1508,6 +1508,7 @@ async function sha256Hex(input) {
1508
1508
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
1509
1509
  return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
1510
1510
  }
1511
+ const logger$1 = getLogger("provider");
1511
1512
  const DEFAULT_STATE_INTERVAL = 3e4;
1512
1513
  const DEFAULT_FLUSH_INTERVAL = 1e4;
1513
1514
  var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
@@ -1579,11 +1580,14 @@ var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
1579
1580
  failFastOnSticky: true
1580
1581
  };
1581
1582
  const response = await this.resolveWithStickyInternal(stickyRequest);
1582
- return this.extractValue(response.resolvedFlags[0], flagName, path, defaultValue);
1583
+ const result = this.extractValue(response.resolvedFlags[0], flagName, path, defaultValue);
1584
+ if (result.errorCode) logger$1.warn(`Flag evaluation for '${flagKey}' returned error code: ${result.errorCode}`);
1585
+ return result;
1583
1586
  }
1584
1587
  async resolveWithStickyInternal(request) {
1585
1588
  const response = this.resolver.resolveWithSticky(request);
1586
1589
  if (response.success && response.success.response) {
1590
+ this.flushAssigned();
1587
1591
  const { response: flagsResponse } = response.success;
1588
1592
  return flagsResponse;
1589
1593
  }
@@ -1647,17 +1651,28 @@ var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
1647
1651
  }
1648
1652
  async flush(signal) {
1649
1653
  const writeFlagLogRequest = this.resolver.flushLogs();
1650
- if (writeFlagLogRequest.length == 0) return;
1651
- const response = await this.fetch("https://resolver.confidence.dev/v1/clientFlagLogs:write", {
1652
- method: "post",
1653
- signal,
1654
- headers: {
1655
- "Content-Type": "application/x-protobuf",
1656
- Authorization: `ClientSecret ${this.options.flagClientSecret}`
1657
- },
1658
- body: writeFlagLogRequest
1659
- });
1660
- if (!response.ok) logger$1.error(`Failed to write flag logs: ${response.status} ${response.statusText} - ${await response.text()}`);
1654
+ if (writeFlagLogRequest.length > 0) await this.sendFlagLogs(writeFlagLogRequest, signal);
1655
+ }
1656
+ async flushAssigned() {
1657
+ const writeFlagLogRequest = this.resolver.flushAssigned();
1658
+ if (writeFlagLogRequest.length > 0) await this.sendFlagLogs(writeFlagLogRequest);
1659
+ }
1660
+ async sendFlagLogs(encodedWriteFlagLogRequest, signal = this.main.signal) {
1661
+ try {
1662
+ const response = await this.fetch("https://resolver.confidence.dev/v1/clientFlagLogs:write", {
1663
+ method: "post",
1664
+ signal,
1665
+ headers: {
1666
+ "Content-Type": "application/x-protobuf",
1667
+ Authorization: `ClientSecret ${this.options.flagClientSecret}`
1668
+ },
1669
+ body: encodedWriteFlagLogRequest
1670
+ });
1671
+ if (!response.ok) logger$1.error(`Failed to write flag logs: ${response.status} ${response.statusText} - ${await response.text()}`);
1672
+ } catch (err) {
1673
+ logger$1.warn("Failed to send flag logs", err);
1674
+ throw err;
1675
+ }
1661
1676
  }
1662
1677
  static convertReason(reason) {
1663
1678
  switch (reason) {
@@ -1714,7 +1729,8 @@ const EXPORT_FN_NAMES = [
1714
1729
  "wasm_msg_free",
1715
1730
  "wasm_msg_guest_resolve_with_sticky",
1716
1731
  "wasm_msg_guest_set_resolver_state",
1717
- "wasm_msg_guest_flush_logs"
1732
+ "wasm_msg_guest_bounded_flush_logs",
1733
+ "wasm_msg_guest_bounded_flush_assign"
1718
1734
  ];
1719
1735
  function verifyExports(exports) {
1720
1736
  for (const fnName of EXPORT_FN_NAMES) if (typeof exports[fnName] !== "function") throw new Error(`Expected Function export "${fnName}" found ${exports[fnName]}`);
@@ -1722,6 +1738,7 @@ function verifyExports(exports) {
1722
1738
  }
1723
1739
  var UnsafeWasmResolver = class {
1724
1740
  exports;
1741
+ flushCount = 0;
1725
1742
  constructor(module$1) {
1726
1743
  const { exports } = new WebAssembly.Instance(module$1, { wasm_msg: { wasm_msg_host_current_time: () => {
1727
1744
  const epochMillisecond = Date.now();
@@ -1746,7 +1763,13 @@ var UnsafeWasmResolver = class {
1746
1763
  this.consumeResponse(resPtr, Void);
1747
1764
  }
1748
1765
  flushLogs() {
1749
- const resPtr = this.exports.wasm_msg_guest_flush_logs(0);
1766
+ const resPtr = this.exports.wasm_msg_guest_bounded_flush_logs(0);
1767
+ const { data, error } = this.consume(resPtr, Response$1);
1768
+ if (error) throw new Error(error);
1769
+ return data;
1770
+ }
1771
+ flushAssigned() {
1772
+ const resPtr = this.exports.wasm_msg_guest_bounded_flush_assign(0);
1750
1773
  const { data, error } = this.consume(resPtr, Response$1);
1751
1774
  if (error) throw new Error(error);
1752
1775
  return data;
@@ -1834,6 +1857,9 @@ var WasmResolver = class {
1834
1857
  throw error;
1835
1858
  }
1836
1859
  }
1860
+ flushAssigned() {
1861
+ return this.delegate.flushAssigned();
1862
+ }
1837
1863
  };
1838
1864
  const wasmPath = __require.resolve("./confidence_resolver.wasm");
1839
1865
  const buffer = await fs.readFile(wasmPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spotify-confidence/openfeature-server-provider-local",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Spotify Confidence Open Feature provider",
5
5
  "type": "module",
6
6
  "files": [