@spotify-confidence/openfeature-server-provider-local 0.1.0 → 0.2.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,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.0](https://github.com/spotify/confidence-resolver/compare/openfeature-provider-js-v0.1.1...openfeature-provider-js-v0.2.0) (2025-11-24)
4
+
5
+
6
+ ### Features
7
+
8
+ * send js sdk info in resolve request ([#161](https://github.com/spotify/confidence-resolver/issues/161)) ([5cbc7d9](https://github.com/spotify/confidence-resolver/commit/5cbc7d9e2ada26c52298d74faaba50ec6cb4c3e7))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * rust-guest bumped from 0.1.9 to 0.1.10
16
+
17
+ ## [0.1.1](https://github.com/spotify/confidence-resolver-rust/compare/openfeature-provider-js-v0.1.0...openfeature-provider-js-v0.1.1) (2025-11-03)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * handle panics ([#76](https://github.com/spotify/confidence-resolver-rust/issues/76)) ([1ea86ea](https://github.com/spotify/confidence-resolver-rust/commit/1ea86eaa3e64aea5a64086534fe94a155828ef80))
23
+
3
24
  ## 0.1.0 (2025-10-24)
4
25
 
5
26
 
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
- ## @spotify-confidence/openfeature-server-provider-local
1
+ # @spotify-confidence/openfeature-server-provider-local
2
2
 
3
3
  OpenFeature provider for the Spotify Confidence resolver (local mode, powered by WebAssembly). It periodically fetches resolver state, evaluates flags locally, and flushes evaluation logs to the Confidence backend.
4
4
 
5
- ### Features
5
+ ## Features
6
6
  - Local flag evaluation via WASM (no per-eval network calls)
7
7
  - Automatic state refresh and batched flag log flushing
8
8
  - Pluggable `fetch` with retries, timeouts and routing
9
9
  - Optional logging using `debug`
10
10
 
11
- ### Requirements
11
+ ## Requirements
12
12
  - Node.js 18+ (built-in `fetch`) or provide a compatible `fetch`
13
13
  - WebAssembly support (Node 18+/modern browsers)
14
14
 
@@ -78,6 +78,11 @@ The provider periodically:
78
78
 
79
79
  ---
80
80
 
81
+ ## Migration from online resolver
82
+ If you're currently using the ["online resolver"](https://github.com/spotify/confidence-sdk-js/tree/main/packages/openfeature-server-provider) and wants to improve the resolve latency, [migration](MIGRATION.md) is easy!
83
+
84
+ ---
85
+
81
86
  ## Sticky Assignments
82
87
 
83
88
  Confidence supports "sticky" flag assignments to ensure users receive consistent variant assignments even when their context changes or flag configurations are updated. This SDK falls back to a cloud resolve in these cases.
@@ -182,3 +187,11 @@ The package exports a browser ESM build that compiles the WASM via streaming and
182
187
  ## License
183
188
 
184
189
  See the root `LICENSE`.
190
+
191
+ ## Formatting
192
+
193
+ Code is formatted using prettier, you can format all files by running
194
+
195
+ ```sh
196
+ yarn format
197
+ ```
Binary file
@@ -2,6 +2,32 @@ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
2
2
  import { EvaluationContext, JsonValue, Provider, ProviderMetadata, ProviderStatus, ResolutionDetails } from "@openfeature/server-sdk";
3
3
 
4
4
  //#region src/proto/api.d.ts
5
+ declare enum SdkId {
6
+ SDK_ID_UNSPECIFIED = 0,
7
+ SDK_ID_JAVA_PROVIDER = 1,
8
+ SDK_ID_KOTLIN_PROVIDER = 2,
9
+ SDK_ID_SWIFT_PROVIDER = 3,
10
+ SDK_ID_JS_WEB_PROVIDER = 4,
11
+ SDK_ID_JS_SERVER_PROVIDER = 5,
12
+ SDK_ID_PYTHON_PROVIDER = 6,
13
+ SDK_ID_GO_PROVIDER = 7,
14
+ SDK_ID_RUBY_PROVIDER = 8,
15
+ SDK_ID_RUST_PROVIDER = 9,
16
+ SDK_ID_JAVA_CONFIDENCE = 10,
17
+ SDK_ID_KOTLIN_CONFIDENCE = 11,
18
+ SDK_ID_SWIFT_CONFIDENCE = 12,
19
+ SDK_ID_JS_CONFIDENCE = 13,
20
+ SDK_ID_PYTHON_CONFIDENCE = 14,
21
+ SDK_ID_GO_CONFIDENCE = 15,
22
+ SDK_ID_RUST_CONFIDENCE = 16,
23
+ SDK_ID_FLUTTER_IOS_CONFIDENCE = 17,
24
+ SDK_ID_FLUTTER_ANDROID_CONFIDENCE = 18,
25
+ SDK_ID_DOTNET_CONFIDENCE = 19,
26
+ SDK_ID_GO_LOCAL_PROVIDER = 20,
27
+ SDK_ID_JAVA_LOCAL_PROVIDER = 21,
28
+ SDK_ID_JS_LOCAL_SERVER_PROVIDER = 22,
29
+ UNRECOGNIZED = -1,
30
+ }
5
31
  declare enum ResolveReason {
6
32
  /** RESOLVE_REASON_UNSPECIFIED - Unspecified enum. */
7
33
  RESOLVE_REASON_UNSPECIFIED = 0,
@@ -24,6 +50,11 @@ declare enum ResolveReason {
24
50
  RESOLVE_REASON_ERROR = 6,
25
51
  UNRECOGNIZED = -1,
26
52
  }
53
+ interface Sdk {
54
+ id?: SdkId | undefined;
55
+ customId?: string | undefined;
56
+ version: string;
57
+ }
27
58
  interface ResolveFlagsRequest {
28
59
  /**
29
60
  * If non-empty, the specific flags are resolved, otherwise all flags
@@ -50,6 +81,8 @@ interface ResolveFlagsRequest {
50
81
  * `apply` should likely be set to false.
51
82
  */
52
83
  apply: boolean;
84
+ /** Information about the SDK used to initiate the request. */
85
+ sdk?: Sdk | undefined;
53
86
  }
54
87
  interface ResolveFlagsResponse {
55
88
  /**
@@ -143,6 +176,7 @@ interface ResolveWithStickyResponse_MaterializationUpdate {
143
176
  rule: string;
144
177
  variant: string;
145
178
  }
179
+ declare const Sdk: MessageFns<Sdk>;
146
180
  declare const ResolveFlagsRequest: MessageFns<ResolveFlagsRequest>;
147
181
  declare const ResolveFlagsResponse: MessageFns<ResolveFlagsResponse>;
148
182
  declare const ResolvedFlag: MessageFns<ResolvedFlag>;
@@ -315,6 +315,115 @@ function isObject$1(value) {
315
315
  function isSet$3(value) {
316
316
  return value !== null && value !== void 0;
317
317
  }
318
+ let SdkId = /* @__PURE__ */ function(SdkId$1) {
319
+ SdkId$1[SdkId$1["SDK_ID_UNSPECIFIED"] = 0] = "SDK_ID_UNSPECIFIED";
320
+ SdkId$1[SdkId$1["SDK_ID_JAVA_PROVIDER"] = 1] = "SDK_ID_JAVA_PROVIDER";
321
+ SdkId$1[SdkId$1["SDK_ID_KOTLIN_PROVIDER"] = 2] = "SDK_ID_KOTLIN_PROVIDER";
322
+ SdkId$1[SdkId$1["SDK_ID_SWIFT_PROVIDER"] = 3] = "SDK_ID_SWIFT_PROVIDER";
323
+ SdkId$1[SdkId$1["SDK_ID_JS_WEB_PROVIDER"] = 4] = "SDK_ID_JS_WEB_PROVIDER";
324
+ SdkId$1[SdkId$1["SDK_ID_JS_SERVER_PROVIDER"] = 5] = "SDK_ID_JS_SERVER_PROVIDER";
325
+ SdkId$1[SdkId$1["SDK_ID_PYTHON_PROVIDER"] = 6] = "SDK_ID_PYTHON_PROVIDER";
326
+ SdkId$1[SdkId$1["SDK_ID_GO_PROVIDER"] = 7] = "SDK_ID_GO_PROVIDER";
327
+ SdkId$1[SdkId$1["SDK_ID_RUBY_PROVIDER"] = 8] = "SDK_ID_RUBY_PROVIDER";
328
+ SdkId$1[SdkId$1["SDK_ID_RUST_PROVIDER"] = 9] = "SDK_ID_RUST_PROVIDER";
329
+ SdkId$1[SdkId$1["SDK_ID_JAVA_CONFIDENCE"] = 10] = "SDK_ID_JAVA_CONFIDENCE";
330
+ SdkId$1[SdkId$1["SDK_ID_KOTLIN_CONFIDENCE"] = 11] = "SDK_ID_KOTLIN_CONFIDENCE";
331
+ SdkId$1[SdkId$1["SDK_ID_SWIFT_CONFIDENCE"] = 12] = "SDK_ID_SWIFT_CONFIDENCE";
332
+ SdkId$1[SdkId$1["SDK_ID_JS_CONFIDENCE"] = 13] = "SDK_ID_JS_CONFIDENCE";
333
+ SdkId$1[SdkId$1["SDK_ID_PYTHON_CONFIDENCE"] = 14] = "SDK_ID_PYTHON_CONFIDENCE";
334
+ SdkId$1[SdkId$1["SDK_ID_GO_CONFIDENCE"] = 15] = "SDK_ID_GO_CONFIDENCE";
335
+ SdkId$1[SdkId$1["SDK_ID_RUST_CONFIDENCE"] = 16] = "SDK_ID_RUST_CONFIDENCE";
336
+ SdkId$1[SdkId$1["SDK_ID_FLUTTER_IOS_CONFIDENCE"] = 17] = "SDK_ID_FLUTTER_IOS_CONFIDENCE";
337
+ SdkId$1[SdkId$1["SDK_ID_FLUTTER_ANDROID_CONFIDENCE"] = 18] = "SDK_ID_FLUTTER_ANDROID_CONFIDENCE";
338
+ SdkId$1[SdkId$1["SDK_ID_DOTNET_CONFIDENCE"] = 19] = "SDK_ID_DOTNET_CONFIDENCE";
339
+ SdkId$1[SdkId$1["SDK_ID_GO_LOCAL_PROVIDER"] = 20] = "SDK_ID_GO_LOCAL_PROVIDER";
340
+ SdkId$1[SdkId$1["SDK_ID_JAVA_LOCAL_PROVIDER"] = 21] = "SDK_ID_JAVA_LOCAL_PROVIDER";
341
+ SdkId$1[SdkId$1["SDK_ID_JS_LOCAL_SERVER_PROVIDER"] = 22] = "SDK_ID_JS_LOCAL_SERVER_PROVIDER";
342
+ SdkId$1[SdkId$1["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
343
+ return SdkId$1;
344
+ }({});
345
+ function sdkIdFromJSON(object) {
346
+ switch (object) {
347
+ case 0:
348
+ case "SDK_ID_UNSPECIFIED": return SdkId.SDK_ID_UNSPECIFIED;
349
+ case 1:
350
+ case "SDK_ID_JAVA_PROVIDER": return SdkId.SDK_ID_JAVA_PROVIDER;
351
+ case 2:
352
+ case "SDK_ID_KOTLIN_PROVIDER": return SdkId.SDK_ID_KOTLIN_PROVIDER;
353
+ case 3:
354
+ case "SDK_ID_SWIFT_PROVIDER": return SdkId.SDK_ID_SWIFT_PROVIDER;
355
+ case 4:
356
+ case "SDK_ID_JS_WEB_PROVIDER": return SdkId.SDK_ID_JS_WEB_PROVIDER;
357
+ case 5:
358
+ case "SDK_ID_JS_SERVER_PROVIDER": return SdkId.SDK_ID_JS_SERVER_PROVIDER;
359
+ case 6:
360
+ case "SDK_ID_PYTHON_PROVIDER": return SdkId.SDK_ID_PYTHON_PROVIDER;
361
+ case 7:
362
+ case "SDK_ID_GO_PROVIDER": return SdkId.SDK_ID_GO_PROVIDER;
363
+ case 8:
364
+ case "SDK_ID_RUBY_PROVIDER": return SdkId.SDK_ID_RUBY_PROVIDER;
365
+ case 9:
366
+ case "SDK_ID_RUST_PROVIDER": return SdkId.SDK_ID_RUST_PROVIDER;
367
+ case 10:
368
+ case "SDK_ID_JAVA_CONFIDENCE": return SdkId.SDK_ID_JAVA_CONFIDENCE;
369
+ case 11:
370
+ case "SDK_ID_KOTLIN_CONFIDENCE": return SdkId.SDK_ID_KOTLIN_CONFIDENCE;
371
+ case 12:
372
+ case "SDK_ID_SWIFT_CONFIDENCE": return SdkId.SDK_ID_SWIFT_CONFIDENCE;
373
+ case 13:
374
+ case "SDK_ID_JS_CONFIDENCE": return SdkId.SDK_ID_JS_CONFIDENCE;
375
+ case 14:
376
+ case "SDK_ID_PYTHON_CONFIDENCE": return SdkId.SDK_ID_PYTHON_CONFIDENCE;
377
+ case 15:
378
+ case "SDK_ID_GO_CONFIDENCE": return SdkId.SDK_ID_GO_CONFIDENCE;
379
+ case 16:
380
+ case "SDK_ID_RUST_CONFIDENCE": return SdkId.SDK_ID_RUST_CONFIDENCE;
381
+ case 17:
382
+ case "SDK_ID_FLUTTER_IOS_CONFIDENCE": return SdkId.SDK_ID_FLUTTER_IOS_CONFIDENCE;
383
+ case 18:
384
+ case "SDK_ID_FLUTTER_ANDROID_CONFIDENCE": return SdkId.SDK_ID_FLUTTER_ANDROID_CONFIDENCE;
385
+ case 19:
386
+ case "SDK_ID_DOTNET_CONFIDENCE": return SdkId.SDK_ID_DOTNET_CONFIDENCE;
387
+ case 20:
388
+ case "SDK_ID_GO_LOCAL_PROVIDER": return SdkId.SDK_ID_GO_LOCAL_PROVIDER;
389
+ case 21:
390
+ case "SDK_ID_JAVA_LOCAL_PROVIDER": return SdkId.SDK_ID_JAVA_LOCAL_PROVIDER;
391
+ case 22:
392
+ case "SDK_ID_JS_LOCAL_SERVER_PROVIDER": return SdkId.SDK_ID_JS_LOCAL_SERVER_PROVIDER;
393
+ case -1:
394
+ case "UNRECOGNIZED":
395
+ default: return SdkId.UNRECOGNIZED;
396
+ }
397
+ }
398
+ function sdkIdToJSON(object) {
399
+ switch (object) {
400
+ case SdkId.SDK_ID_UNSPECIFIED: return "SDK_ID_UNSPECIFIED";
401
+ case SdkId.SDK_ID_JAVA_PROVIDER: return "SDK_ID_JAVA_PROVIDER";
402
+ case SdkId.SDK_ID_KOTLIN_PROVIDER: return "SDK_ID_KOTLIN_PROVIDER";
403
+ case SdkId.SDK_ID_SWIFT_PROVIDER: return "SDK_ID_SWIFT_PROVIDER";
404
+ case SdkId.SDK_ID_JS_WEB_PROVIDER: return "SDK_ID_JS_WEB_PROVIDER";
405
+ case SdkId.SDK_ID_JS_SERVER_PROVIDER: return "SDK_ID_JS_SERVER_PROVIDER";
406
+ case SdkId.SDK_ID_PYTHON_PROVIDER: return "SDK_ID_PYTHON_PROVIDER";
407
+ case SdkId.SDK_ID_GO_PROVIDER: return "SDK_ID_GO_PROVIDER";
408
+ case SdkId.SDK_ID_RUBY_PROVIDER: return "SDK_ID_RUBY_PROVIDER";
409
+ case SdkId.SDK_ID_RUST_PROVIDER: return "SDK_ID_RUST_PROVIDER";
410
+ case SdkId.SDK_ID_JAVA_CONFIDENCE: return "SDK_ID_JAVA_CONFIDENCE";
411
+ case SdkId.SDK_ID_KOTLIN_CONFIDENCE: return "SDK_ID_KOTLIN_CONFIDENCE";
412
+ case SdkId.SDK_ID_SWIFT_CONFIDENCE: return "SDK_ID_SWIFT_CONFIDENCE";
413
+ case SdkId.SDK_ID_JS_CONFIDENCE: return "SDK_ID_JS_CONFIDENCE";
414
+ case SdkId.SDK_ID_PYTHON_CONFIDENCE: return "SDK_ID_PYTHON_CONFIDENCE";
415
+ case SdkId.SDK_ID_GO_CONFIDENCE: return "SDK_ID_GO_CONFIDENCE";
416
+ case SdkId.SDK_ID_RUST_CONFIDENCE: return "SDK_ID_RUST_CONFIDENCE";
417
+ case SdkId.SDK_ID_FLUTTER_IOS_CONFIDENCE: return "SDK_ID_FLUTTER_IOS_CONFIDENCE";
418
+ case SdkId.SDK_ID_FLUTTER_ANDROID_CONFIDENCE: return "SDK_ID_FLUTTER_ANDROID_CONFIDENCE";
419
+ case SdkId.SDK_ID_DOTNET_CONFIDENCE: return "SDK_ID_DOTNET_CONFIDENCE";
420
+ case SdkId.SDK_ID_GO_LOCAL_PROVIDER: return "SDK_ID_GO_LOCAL_PROVIDER";
421
+ case SdkId.SDK_ID_JAVA_LOCAL_PROVIDER: return "SDK_ID_JAVA_LOCAL_PROVIDER";
422
+ case SdkId.SDK_ID_JS_LOCAL_SERVER_PROVIDER: return "SDK_ID_JS_LOCAL_SERVER_PROVIDER";
423
+ case SdkId.UNRECOGNIZED:
424
+ default: return "UNRECOGNIZED";
425
+ }
426
+ }
318
427
  let ResolveReason = /* @__PURE__ */ function(ResolveReason$1) {
319
428
  ResolveReason$1[ResolveReason$1["RESOLVE_REASON_UNSPECIFIED"] = 0] = "RESOLVE_REASON_UNSPECIFIED";
320
429
  ResolveReason$1[ResolveReason$1["RESOLVE_REASON_MATCH"] = 1] = "RESOLVE_REASON_MATCH";
@@ -360,12 +469,77 @@ function resolveReasonToJSON(object) {
360
469
  default: return "UNRECOGNIZED";
361
470
  }
362
471
  }
472
+ function createBaseSdk() {
473
+ return {
474
+ id: void 0,
475
+ customId: void 0,
476
+ version: ""
477
+ };
478
+ }
479
+ const Sdk = {
480
+ encode(message, writer = new BinaryWriter()) {
481
+ if (message.id !== void 0) writer.uint32(8).int32(message.id);
482
+ if (message.customId !== void 0) writer.uint32(18).string(message.customId);
483
+ if (message.version !== "") writer.uint32(26).string(message.version);
484
+ return writer;
485
+ },
486
+ decode(input, length) {
487
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
488
+ const end = length === void 0 ? reader.len : reader.pos + length;
489
+ const message = createBaseSdk();
490
+ while (reader.pos < end) {
491
+ const tag = reader.uint32();
492
+ switch (tag >>> 3) {
493
+ case 1:
494
+ if (tag !== 8) break;
495
+ message.id = reader.int32();
496
+ continue;
497
+ case 2:
498
+ if (tag !== 18) break;
499
+ message.customId = reader.string();
500
+ continue;
501
+ case 3:
502
+ if (tag !== 26) break;
503
+ message.version = reader.string();
504
+ continue;
505
+ }
506
+ if ((tag & 7) === 4 || tag === 0) break;
507
+ reader.skip(tag & 7);
508
+ }
509
+ return message;
510
+ },
511
+ fromJSON(object) {
512
+ return {
513
+ id: isSet$2(object.id) ? sdkIdFromJSON(object.id) : void 0,
514
+ customId: isSet$2(object.customId) ? globalThis.String(object.customId) : void 0,
515
+ version: isSet$2(object.version) ? globalThis.String(object.version) : ""
516
+ };
517
+ },
518
+ toJSON(message) {
519
+ const obj = {};
520
+ if (message.id !== void 0) obj.id = sdkIdToJSON(message.id);
521
+ if (message.customId !== void 0) obj.customId = message.customId;
522
+ if (message.version !== "") obj.version = message.version;
523
+ return obj;
524
+ },
525
+ create(base) {
526
+ return Sdk.fromPartial(base ?? {});
527
+ },
528
+ fromPartial(object) {
529
+ const message = createBaseSdk();
530
+ message.id = object.id ?? void 0;
531
+ message.customId = object.customId ?? void 0;
532
+ message.version = object.version ?? "";
533
+ return message;
534
+ }
535
+ };
363
536
  function createBaseResolveFlagsRequest() {
364
537
  return {
365
538
  flags: [],
366
539
  evaluationContext: void 0,
367
540
  clientSecret: "",
368
- apply: false
541
+ apply: false,
542
+ sdk: void 0
369
543
  };
370
544
  }
371
545
  const ResolveFlagsRequest = {
@@ -374,6 +548,7 @@ const ResolveFlagsRequest = {
374
548
  if (message.evaluationContext !== void 0) Struct.encode(Struct.wrap(message.evaluationContext), writer.uint32(18).fork()).join();
375
549
  if (message.clientSecret !== "") writer.uint32(26).string(message.clientSecret);
376
550
  if (message.apply !== false) writer.uint32(32).bool(message.apply);
551
+ if (message.sdk !== void 0) Sdk.encode(message.sdk, writer.uint32(42).fork()).join();
377
552
  return writer;
378
553
  },
379
554
  decode(input, length) {
@@ -399,6 +574,10 @@ const ResolveFlagsRequest = {
399
574
  if (tag !== 32) break;
400
575
  message.apply = reader.bool();
401
576
  continue;
577
+ case 5:
578
+ if (tag !== 42) break;
579
+ message.sdk = Sdk.decode(reader, reader.uint32());
580
+ continue;
402
581
  }
403
582
  if ((tag & 7) === 4 || tag === 0) break;
404
583
  reader.skip(tag & 7);
@@ -410,7 +589,8 @@ const ResolveFlagsRequest = {
410
589
  flags: globalThis.Array.isArray(object?.flags) ? object.flags.map((e) => globalThis.String(e)) : [],
411
590
  evaluationContext: isObject(object.evaluationContext) ? object.evaluationContext : void 0,
412
591
  clientSecret: isSet$2(object.clientSecret) ? globalThis.String(object.clientSecret) : "",
413
- apply: isSet$2(object.apply) ? globalThis.Boolean(object.apply) : false
592
+ apply: isSet$2(object.apply) ? globalThis.Boolean(object.apply) : false,
593
+ sdk: isSet$2(object.sdk) ? Sdk.fromJSON(object.sdk) : void 0
414
594
  };
415
595
  },
416
596
  toJSON(message) {
@@ -419,6 +599,7 @@ const ResolveFlagsRequest = {
419
599
  if (message.evaluationContext !== void 0) obj.evaluationContext = message.evaluationContext;
420
600
  if (message.clientSecret !== "") obj.clientSecret = message.clientSecret;
421
601
  if (message.apply !== false) obj.apply = message.apply;
602
+ if (message.sdk !== void 0) obj.sdk = Sdk.toJSON(message.sdk);
422
603
  return obj;
423
604
  },
424
605
  create(base) {
@@ -430,6 +611,7 @@ const ResolveFlagsRequest = {
430
611
  message.evaluationContext = object.evaluationContext ?? void 0;
431
612
  message.clientSecret = object.clientSecret ?? "";
432
613
  message.apply = object.apply ?? false;
614
+ message.sdk = object.sdk !== void 0 && object.sdk !== null ? Sdk.fromPartial(object.sdk) : void 0;
433
615
  return message;
434
616
  }
435
617
  };
@@ -1328,9 +1510,10 @@ function isObject(value) {
1328
1510
  function isSet$2(value) {
1329
1511
  return value !== null && value !== void 0;
1330
1512
  }
1513
+ const VERSION = "0.2.0";
1331
1514
  const NOOP_LOG_FN = () => {};
1332
1515
  const debugBackend = loadDebug();
1333
- const logger = new class LoggerImpl {
1516
+ const logger$1 = new class LoggerImpl {
1334
1517
  childLoggers = /* @__PURE__ */ new Map();
1335
1518
  debug = NOOP_LOG_FN;
1336
1519
  info = NOOP_LOG_FN;
@@ -1362,6 +1545,7 @@ const logger = new class LoggerImpl {
1362
1545
  return child;
1363
1546
  }
1364
1547
  }("cnfd");
1548
+ const getLogger = logger$1.getLogger.bind(logger$1);
1365
1549
  async function loadDebug() {
1366
1550
  try {
1367
1551
  const { default: debug } = await import("debug");
@@ -1391,7 +1575,7 @@ function scheduleWithFixedInterval(operation, intervalMs, opt = {}) {
1391
1575
  try {
1392
1576
  await operation(ac.signal);
1393
1577
  } catch (e) {
1394
- logger.warn("scheduleWithFixedInterval failure:", e);
1578
+ logger$1.warn("scheduleWithFixedInterval failure:", e);
1395
1579
  }
1396
1580
  concurrent--;
1397
1581
  if (Date.now() - lastRunTime > intervalMs && nextRunTimeoutId != 0) {
@@ -1453,7 +1637,7 @@ function promiseSignal(signal) {
1453
1637
  function abortablePromise(promise, signal) {
1454
1638
  return signal ? Promise.race([promise, promiseSignal(signal)]) : promise;
1455
1639
  }
1456
- const logger$1 = logger.getLogger("fetch");
1640
+ const logger$2 = logger$1.getLogger("fetch");
1457
1641
  let Fetch;
1458
1642
  (function(_Fetch) {
1459
1643
  function create(middleware, sink = fetch) {
@@ -1524,13 +1708,13 @@ function withRetry(opts) {
1524
1708
  const onSuccess = async (resp) => {
1525
1709
  const { status, statusText } = resp;
1526
1710
  if (status !== 408 && status !== 429 && status < 500 || attempts >= maxAttempts) return resp;
1527
- logger$1.debug("withRetry %s failed attempt %d with %d %s", url, attempts - 1, status, statusText);
1711
+ logger$2.debug("withRetry %s failed attempt %d with %d %s", url, attempts - 1, status, statusText);
1528
1712
  const serverDelay = parseRetryAfter(resp.headers.get("Retry-After"), baseInterval, maxInterval);
1529
1713
  await abortableSleep(serverDelay ?? deadline - Date.now(), signal);
1530
1714
  return doTry();
1531
1715
  };
1532
1716
  const onError = async (error) => {
1533
- logger$1.debug("withRetry %s failed attempt %d with %s", url, attempts - 1, error);
1717
+ logger$2.debug("withRetry %s failed attempt %d with %s", url, attempts - 1, error);
1534
1718
  if (signal?.aborted || attempts >= maxAttempts) throw error;
1535
1719
  await abortableSleep(deadline - Date.now(), signal);
1536
1720
  return doTry();
@@ -1556,10 +1740,10 @@ function withAuth(tokenProvider, signal) {
1556
1740
  clearTimeout(renewTimeout);
1557
1741
  });
1558
1742
  const renewToken = () => {
1559
- logger$1.debug("withAuth renewing token");
1743
+ logger$2.debug("withAuth renewing token");
1560
1744
  clearTimeout(renewTimeout);
1561
1745
  current = tokenProvider().then(([token, expiry]) => {
1562
- logger$1.debug("withAuth renew success %s", expiry && expiry.valueOf() - Date.now());
1746
+ logger$2.debug("withAuth renew success %s", expiry && expiry.valueOf() - Date.now());
1563
1747
  if (expiry) {
1564
1748
  const ttl = expiry.valueOf() - Date.now();
1565
1749
  renewTimeout = portableSetTimeout(renewToken, .8 * ttl);
@@ -1631,7 +1815,7 @@ function withRouter(routes) {
1631
1815
  return async (url, init = {}) => {
1632
1816
  const match = table.find(([pred]) => pred(url));
1633
1817
  if (!match) {
1634
- logger$1.info("withRouter no route matched %s, falling through", url);
1818
+ logger$2.info("withRouter no route matched %s, falling through", url);
1635
1819
  return next(url, init);
1636
1820
  }
1637
1821
  return match[1](url, init);
@@ -1641,13 +1825,13 @@ function withRouter(routes) {
1641
1825
  function withResponse(factory) {
1642
1826
  return (_next) => factory;
1643
1827
  }
1644
- const fetchLogger = logger$1;
1645
- function withLogging(logger$2 = fetchLogger) {
1828
+ const fetchLogger = logger$2;
1829
+ function withLogging(logger$3 = fetchLogger) {
1646
1830
  return (next) => async (url, init) => {
1647
1831
  const start = Date.now();
1648
1832
  const resp = await next(url, init);
1649
1833
  const duration = Date.now() - start;
1650
- logger$2.info("%s %s (%i) %dms", (init?.method ?? "get").toUpperCase(), url.split("?", 1)[0], resp.status, duration);
1834
+ logger$3.info("%s %s (%i) %dms", (init?.method ?? "get").toUpperCase(), url.split("?", 1)[0], resp.status, duration);
1651
1835
  return resp;
1652
1836
  };
1653
1837
  }
@@ -1699,7 +1883,7 @@ var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
1699
1883
  baseInterval: 500,
1700
1884
  maxInterval: DEFAULT_STATE_INTERVAL
1701
1885
  }), withStallTimeout(500)],
1702
- "https://flags.confidence.dev/*|https://resolver.confidence.dev/*": [withConfidenceAuth, withRouter({
1886
+ "https://resolver.confidence.dev/*": [withConfidenceAuth, withRouter({
1703
1887
  "*/v1/resolverState:resolverStateUri": [withFastRetry],
1704
1888
  "*/v1/flags:resolve": [withRetry({
1705
1889
  maxAttempts: 3,
@@ -1742,7 +1926,11 @@ var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
1742
1926
  flags: [`flags/${flagName}`],
1743
1927
  evaluationContext: ConfidenceServerProviderLocal.convertEvaluationContext(context),
1744
1928
  apply: true,
1745
- clientSecret: this.options.flagClientSecret
1929
+ clientSecret: this.options.flagClientSecret,
1930
+ sdk: {
1931
+ id: SdkId.SDK_ID_JS_LOCAL_SERVER_PROVIDER,
1932
+ version: VERSION
1933
+ }
1746
1934
  },
1747
1935
  materializationsPerUnit: {},
1748
1936
  failFastOnSticky: true
@@ -1827,7 +2015,7 @@ var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
1827
2015
  });
1828
2016
  }
1829
2017
  async fetchResolveStateUri(signal) {
1830
- const resp = await this.fetch("https://flags.confidence.dev/v1/resolverState:resolverStateUri", { signal });
2018
+ const resp = await this.fetch("https://resolver.confidence.dev/v1/resolverState:resolverStateUri", { signal });
1831
2019
  if (!resp.ok) throw new Error("Failed to get resolve state url");
1832
2020
  return resp.json();
1833
2021
  }
@@ -2108,11 +2296,22 @@ function longToNumber(int64) {
2108
2296
  function isSet(value) {
2109
2297
  return value !== null && value !== void 0;
2110
2298
  }
2111
- var WasmResolver = class WasmResolver {
2299
+ const logger = getLogger("wasm-resolver");
2300
+ const EXPORT_FN_NAMES = [
2301
+ "wasm_msg_alloc",
2302
+ "wasm_msg_free",
2303
+ "wasm_msg_guest_resolve_with_sticky",
2304
+ "wasm_msg_guest_set_resolver_state",
2305
+ "wasm_msg_guest_flush_logs"
2306
+ ];
2307
+ function verifyExports(exports) {
2308
+ for (const fnName of EXPORT_FN_NAMES) if (typeof exports[fnName] !== "function") throw new Error(`Expected Function export "${fnName}" found ${exports[fnName]}`);
2309
+ if (!(exports.memory instanceof WebAssembly.Memory)) throw new Error(`Expected WebAssembly.Memory export "memory", found ${exports.memory}`);
2310
+ }
2311
+ var UnsafeWasmResolver = class {
2112
2312
  exports;
2113
- imports;
2114
- constructor() {
2115
- this.imports = { wasm_msg: { wasm_msg_host_current_time: () => {
2313
+ constructor(module$1) {
2314
+ const { exports } = new WebAssembly.Instance(module$1, { wasm_msg: { wasm_msg_host_current_time: () => {
2116
2315
  const epochMillisecond = Date.now();
2117
2316
  const seconds = Math.floor(epochMillisecond / 1e3);
2118
2317
  const nanos = (epochMillisecond - 1e3 * seconds) * 1e6;
@@ -2120,7 +2319,9 @@ var WasmResolver = class WasmResolver {
2120
2319
  seconds,
2121
2320
  nanos
2122
2321
  }, Timestamp);
2123
- } } };
2322
+ } } });
2323
+ verifyExports(exports);
2324
+ this.exports = exports;
2124
2325
  }
2125
2326
  resolveWithSticky(request) {
2126
2327
  const reqPtr = this.transferRequest(request, ResolveWithStickyRequest);
@@ -2166,15 +2367,65 @@ var WasmResolver = class WasmResolver {
2166
2367
  free(ptr) {
2167
2368
  this.exports.wasm_msg_free(ptr);
2168
2369
  }
2169
- static async load(module$1) {
2170
- const wasmResolver = new WasmResolver();
2171
- wasmResolver.exports = (await WebAssembly.instantiate(module$1, wasmResolver.imports)).exports;
2172
- return wasmResolver;
2370
+ };
2371
+ const DEFAULT_DELEGATE_FACTORY = (module$1) => new UnsafeWasmResolver(module$1);
2372
+ var WasmResolver = class {
2373
+ delegate;
2374
+ currentState;
2375
+ bufferedLogs = [];
2376
+ constructor(module$1, delegateFactory = DEFAULT_DELEGATE_FACTORY) {
2377
+ this.module = module$1;
2378
+ this.delegateFactory = delegateFactory;
2379
+ this.delegate = delegateFactory(module$1);
2380
+ }
2381
+ reloadInstance(error) {
2382
+ logger.error("Failure calling into wasm:", error);
2383
+ try {
2384
+ this.bufferedLogs.push(this.delegate.flushLogs());
2385
+ } catch (_) {
2386
+ logger.error("Failed to flushLogs on error");
2387
+ }
2388
+ this.delegate = this.delegateFactory(this.module);
2389
+ if (this.currentState) this.delegate.setResolverState(this.currentState);
2390
+ }
2391
+ resolveWithSticky(request) {
2392
+ try {
2393
+ return this.delegate.resolveWithSticky(request);
2394
+ } catch (error) {
2395
+ if (error instanceof WebAssembly.RuntimeError) this.reloadInstance(error);
2396
+ throw error;
2397
+ }
2398
+ }
2399
+ setResolverState(request) {
2400
+ this.currentState = request;
2401
+ try {
2402
+ this.delegate.setResolverState(request);
2403
+ } catch (error) {
2404
+ if (error instanceof WebAssembly.RuntimeError) this.reloadInstance(error);
2405
+ throw error;
2406
+ }
2407
+ }
2408
+ flushLogs() {
2409
+ try {
2410
+ this.bufferedLogs.push(this.delegate.flushLogs());
2411
+ const len = this.bufferedLogs.reduce((sum, chunk) => sum + chunk.length, 0);
2412
+ const buffer = new Uint8Array(len);
2413
+ let offset = 0;
2414
+ for (const chunk of this.bufferedLogs) {
2415
+ buffer.set(chunk, offset);
2416
+ offset += chunk.length;
2417
+ }
2418
+ this.bufferedLogs.length = 0;
2419
+ return buffer;
2420
+ } catch (error) {
2421
+ if (error instanceof WebAssembly.RuntimeError) this.reloadInstance(error);
2422
+ throw error;
2423
+ }
2173
2424
  }
2174
2425
  };
2175
2426
  const wasmUrl = new URL("confidence_resolver.wasm", import.meta.url);
2176
2427
  const module = await WebAssembly.compileStreaming(fetch(wasmUrl));
2177
- const resolver = await WasmResolver.load(module);
2428
+ const resolver = new WasmResolver(module);
2178
2429
  function createConfidenceServerProvider(options) {
2179
2430
  return new ConfidenceServerProviderLocal(resolver, options);
2180
2431
  }
@@ -2,6 +2,32 @@ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
2
2
  import { EvaluationContext, JsonValue, Provider, ProviderMetadata, ProviderStatus, ResolutionDetails } from "@openfeature/server-sdk";
3
3
 
4
4
  //#region src/proto/api.d.ts
5
+ declare enum SdkId {
6
+ SDK_ID_UNSPECIFIED = 0,
7
+ SDK_ID_JAVA_PROVIDER = 1,
8
+ SDK_ID_KOTLIN_PROVIDER = 2,
9
+ SDK_ID_SWIFT_PROVIDER = 3,
10
+ SDK_ID_JS_WEB_PROVIDER = 4,
11
+ SDK_ID_JS_SERVER_PROVIDER = 5,
12
+ SDK_ID_PYTHON_PROVIDER = 6,
13
+ SDK_ID_GO_PROVIDER = 7,
14
+ SDK_ID_RUBY_PROVIDER = 8,
15
+ SDK_ID_RUST_PROVIDER = 9,
16
+ SDK_ID_JAVA_CONFIDENCE = 10,
17
+ SDK_ID_KOTLIN_CONFIDENCE = 11,
18
+ SDK_ID_SWIFT_CONFIDENCE = 12,
19
+ SDK_ID_JS_CONFIDENCE = 13,
20
+ SDK_ID_PYTHON_CONFIDENCE = 14,
21
+ SDK_ID_GO_CONFIDENCE = 15,
22
+ SDK_ID_RUST_CONFIDENCE = 16,
23
+ SDK_ID_FLUTTER_IOS_CONFIDENCE = 17,
24
+ SDK_ID_FLUTTER_ANDROID_CONFIDENCE = 18,
25
+ SDK_ID_DOTNET_CONFIDENCE = 19,
26
+ SDK_ID_GO_LOCAL_PROVIDER = 20,
27
+ SDK_ID_JAVA_LOCAL_PROVIDER = 21,
28
+ SDK_ID_JS_LOCAL_SERVER_PROVIDER = 22,
29
+ UNRECOGNIZED = -1,
30
+ }
5
31
  declare enum ResolveReason {
6
32
  /** RESOLVE_REASON_UNSPECIFIED - Unspecified enum. */
7
33
  RESOLVE_REASON_UNSPECIFIED = 0,
@@ -24,6 +50,11 @@ declare enum ResolveReason {
24
50
  RESOLVE_REASON_ERROR = 6,
25
51
  UNRECOGNIZED = -1,
26
52
  }
53
+ interface Sdk {
54
+ id?: SdkId | undefined;
55
+ customId?: string | undefined;
56
+ version: string;
57
+ }
27
58
  interface ResolveFlagsRequest {
28
59
  /**
29
60
  * If non-empty, the specific flags are resolved, otherwise all flags
@@ -50,6 +81,8 @@ interface ResolveFlagsRequest {
50
81
  * `apply` should likely be set to false.
51
82
  */
52
83
  apply: boolean;
84
+ /** Information about the SDK used to initiate the request. */
85
+ sdk?: Sdk | undefined;
53
86
  }
54
87
  interface ResolveFlagsResponse {
55
88
  /**
@@ -143,6 +176,7 @@ interface ResolveWithStickyResponse_MaterializationUpdate {
143
176
  rule: string;
144
177
  variant: string;
145
178
  }
179
+ declare const Sdk: MessageFns<Sdk>;
146
180
  declare const ResolveFlagsRequest: MessageFns<ResolveFlagsRequest>;
147
181
  declare const ResolveFlagsResponse: MessageFns<ResolveFlagsResponse>;
148
182
  declare const ResolvedFlag: MessageFns<ResolvedFlag>;
@@ -318,6 +318,115 @@ function isObject$1(value) {
318
318
  function isSet$3(value) {
319
319
  return value !== null && value !== void 0;
320
320
  }
321
+ let SdkId = /* @__PURE__ */ function(SdkId$1) {
322
+ SdkId$1[SdkId$1["SDK_ID_UNSPECIFIED"] = 0] = "SDK_ID_UNSPECIFIED";
323
+ SdkId$1[SdkId$1["SDK_ID_JAVA_PROVIDER"] = 1] = "SDK_ID_JAVA_PROVIDER";
324
+ SdkId$1[SdkId$1["SDK_ID_KOTLIN_PROVIDER"] = 2] = "SDK_ID_KOTLIN_PROVIDER";
325
+ SdkId$1[SdkId$1["SDK_ID_SWIFT_PROVIDER"] = 3] = "SDK_ID_SWIFT_PROVIDER";
326
+ SdkId$1[SdkId$1["SDK_ID_JS_WEB_PROVIDER"] = 4] = "SDK_ID_JS_WEB_PROVIDER";
327
+ SdkId$1[SdkId$1["SDK_ID_JS_SERVER_PROVIDER"] = 5] = "SDK_ID_JS_SERVER_PROVIDER";
328
+ SdkId$1[SdkId$1["SDK_ID_PYTHON_PROVIDER"] = 6] = "SDK_ID_PYTHON_PROVIDER";
329
+ SdkId$1[SdkId$1["SDK_ID_GO_PROVIDER"] = 7] = "SDK_ID_GO_PROVIDER";
330
+ SdkId$1[SdkId$1["SDK_ID_RUBY_PROVIDER"] = 8] = "SDK_ID_RUBY_PROVIDER";
331
+ SdkId$1[SdkId$1["SDK_ID_RUST_PROVIDER"] = 9] = "SDK_ID_RUST_PROVIDER";
332
+ SdkId$1[SdkId$1["SDK_ID_JAVA_CONFIDENCE"] = 10] = "SDK_ID_JAVA_CONFIDENCE";
333
+ SdkId$1[SdkId$1["SDK_ID_KOTLIN_CONFIDENCE"] = 11] = "SDK_ID_KOTLIN_CONFIDENCE";
334
+ SdkId$1[SdkId$1["SDK_ID_SWIFT_CONFIDENCE"] = 12] = "SDK_ID_SWIFT_CONFIDENCE";
335
+ SdkId$1[SdkId$1["SDK_ID_JS_CONFIDENCE"] = 13] = "SDK_ID_JS_CONFIDENCE";
336
+ SdkId$1[SdkId$1["SDK_ID_PYTHON_CONFIDENCE"] = 14] = "SDK_ID_PYTHON_CONFIDENCE";
337
+ SdkId$1[SdkId$1["SDK_ID_GO_CONFIDENCE"] = 15] = "SDK_ID_GO_CONFIDENCE";
338
+ SdkId$1[SdkId$1["SDK_ID_RUST_CONFIDENCE"] = 16] = "SDK_ID_RUST_CONFIDENCE";
339
+ SdkId$1[SdkId$1["SDK_ID_FLUTTER_IOS_CONFIDENCE"] = 17] = "SDK_ID_FLUTTER_IOS_CONFIDENCE";
340
+ SdkId$1[SdkId$1["SDK_ID_FLUTTER_ANDROID_CONFIDENCE"] = 18] = "SDK_ID_FLUTTER_ANDROID_CONFIDENCE";
341
+ SdkId$1[SdkId$1["SDK_ID_DOTNET_CONFIDENCE"] = 19] = "SDK_ID_DOTNET_CONFIDENCE";
342
+ SdkId$1[SdkId$1["SDK_ID_GO_LOCAL_PROVIDER"] = 20] = "SDK_ID_GO_LOCAL_PROVIDER";
343
+ SdkId$1[SdkId$1["SDK_ID_JAVA_LOCAL_PROVIDER"] = 21] = "SDK_ID_JAVA_LOCAL_PROVIDER";
344
+ SdkId$1[SdkId$1["SDK_ID_JS_LOCAL_SERVER_PROVIDER"] = 22] = "SDK_ID_JS_LOCAL_SERVER_PROVIDER";
345
+ SdkId$1[SdkId$1["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
346
+ return SdkId$1;
347
+ }({});
348
+ function sdkIdFromJSON(object) {
349
+ switch (object) {
350
+ case 0:
351
+ case "SDK_ID_UNSPECIFIED": return SdkId.SDK_ID_UNSPECIFIED;
352
+ case 1:
353
+ case "SDK_ID_JAVA_PROVIDER": return SdkId.SDK_ID_JAVA_PROVIDER;
354
+ case 2:
355
+ case "SDK_ID_KOTLIN_PROVIDER": return SdkId.SDK_ID_KOTLIN_PROVIDER;
356
+ case 3:
357
+ case "SDK_ID_SWIFT_PROVIDER": return SdkId.SDK_ID_SWIFT_PROVIDER;
358
+ case 4:
359
+ case "SDK_ID_JS_WEB_PROVIDER": return SdkId.SDK_ID_JS_WEB_PROVIDER;
360
+ case 5:
361
+ case "SDK_ID_JS_SERVER_PROVIDER": return SdkId.SDK_ID_JS_SERVER_PROVIDER;
362
+ case 6:
363
+ case "SDK_ID_PYTHON_PROVIDER": return SdkId.SDK_ID_PYTHON_PROVIDER;
364
+ case 7:
365
+ case "SDK_ID_GO_PROVIDER": return SdkId.SDK_ID_GO_PROVIDER;
366
+ case 8:
367
+ case "SDK_ID_RUBY_PROVIDER": return SdkId.SDK_ID_RUBY_PROVIDER;
368
+ case 9:
369
+ case "SDK_ID_RUST_PROVIDER": return SdkId.SDK_ID_RUST_PROVIDER;
370
+ case 10:
371
+ case "SDK_ID_JAVA_CONFIDENCE": return SdkId.SDK_ID_JAVA_CONFIDENCE;
372
+ case 11:
373
+ case "SDK_ID_KOTLIN_CONFIDENCE": return SdkId.SDK_ID_KOTLIN_CONFIDENCE;
374
+ case 12:
375
+ case "SDK_ID_SWIFT_CONFIDENCE": return SdkId.SDK_ID_SWIFT_CONFIDENCE;
376
+ case 13:
377
+ case "SDK_ID_JS_CONFIDENCE": return SdkId.SDK_ID_JS_CONFIDENCE;
378
+ case 14:
379
+ case "SDK_ID_PYTHON_CONFIDENCE": return SdkId.SDK_ID_PYTHON_CONFIDENCE;
380
+ case 15:
381
+ case "SDK_ID_GO_CONFIDENCE": return SdkId.SDK_ID_GO_CONFIDENCE;
382
+ case 16:
383
+ case "SDK_ID_RUST_CONFIDENCE": return SdkId.SDK_ID_RUST_CONFIDENCE;
384
+ case 17:
385
+ case "SDK_ID_FLUTTER_IOS_CONFIDENCE": return SdkId.SDK_ID_FLUTTER_IOS_CONFIDENCE;
386
+ case 18:
387
+ case "SDK_ID_FLUTTER_ANDROID_CONFIDENCE": return SdkId.SDK_ID_FLUTTER_ANDROID_CONFIDENCE;
388
+ case 19:
389
+ case "SDK_ID_DOTNET_CONFIDENCE": return SdkId.SDK_ID_DOTNET_CONFIDENCE;
390
+ case 20:
391
+ case "SDK_ID_GO_LOCAL_PROVIDER": return SdkId.SDK_ID_GO_LOCAL_PROVIDER;
392
+ case 21:
393
+ case "SDK_ID_JAVA_LOCAL_PROVIDER": return SdkId.SDK_ID_JAVA_LOCAL_PROVIDER;
394
+ case 22:
395
+ case "SDK_ID_JS_LOCAL_SERVER_PROVIDER": return SdkId.SDK_ID_JS_LOCAL_SERVER_PROVIDER;
396
+ case -1:
397
+ case "UNRECOGNIZED":
398
+ default: return SdkId.UNRECOGNIZED;
399
+ }
400
+ }
401
+ function sdkIdToJSON(object) {
402
+ switch (object) {
403
+ case SdkId.SDK_ID_UNSPECIFIED: return "SDK_ID_UNSPECIFIED";
404
+ case SdkId.SDK_ID_JAVA_PROVIDER: return "SDK_ID_JAVA_PROVIDER";
405
+ case SdkId.SDK_ID_KOTLIN_PROVIDER: return "SDK_ID_KOTLIN_PROVIDER";
406
+ case SdkId.SDK_ID_SWIFT_PROVIDER: return "SDK_ID_SWIFT_PROVIDER";
407
+ case SdkId.SDK_ID_JS_WEB_PROVIDER: return "SDK_ID_JS_WEB_PROVIDER";
408
+ case SdkId.SDK_ID_JS_SERVER_PROVIDER: return "SDK_ID_JS_SERVER_PROVIDER";
409
+ case SdkId.SDK_ID_PYTHON_PROVIDER: return "SDK_ID_PYTHON_PROVIDER";
410
+ case SdkId.SDK_ID_GO_PROVIDER: return "SDK_ID_GO_PROVIDER";
411
+ case SdkId.SDK_ID_RUBY_PROVIDER: return "SDK_ID_RUBY_PROVIDER";
412
+ case SdkId.SDK_ID_RUST_PROVIDER: return "SDK_ID_RUST_PROVIDER";
413
+ case SdkId.SDK_ID_JAVA_CONFIDENCE: return "SDK_ID_JAVA_CONFIDENCE";
414
+ case SdkId.SDK_ID_KOTLIN_CONFIDENCE: return "SDK_ID_KOTLIN_CONFIDENCE";
415
+ case SdkId.SDK_ID_SWIFT_CONFIDENCE: return "SDK_ID_SWIFT_CONFIDENCE";
416
+ case SdkId.SDK_ID_JS_CONFIDENCE: return "SDK_ID_JS_CONFIDENCE";
417
+ case SdkId.SDK_ID_PYTHON_CONFIDENCE: return "SDK_ID_PYTHON_CONFIDENCE";
418
+ case SdkId.SDK_ID_GO_CONFIDENCE: return "SDK_ID_GO_CONFIDENCE";
419
+ case SdkId.SDK_ID_RUST_CONFIDENCE: return "SDK_ID_RUST_CONFIDENCE";
420
+ case SdkId.SDK_ID_FLUTTER_IOS_CONFIDENCE: return "SDK_ID_FLUTTER_IOS_CONFIDENCE";
421
+ case SdkId.SDK_ID_FLUTTER_ANDROID_CONFIDENCE: return "SDK_ID_FLUTTER_ANDROID_CONFIDENCE";
422
+ case SdkId.SDK_ID_DOTNET_CONFIDENCE: return "SDK_ID_DOTNET_CONFIDENCE";
423
+ case SdkId.SDK_ID_GO_LOCAL_PROVIDER: return "SDK_ID_GO_LOCAL_PROVIDER";
424
+ case SdkId.SDK_ID_JAVA_LOCAL_PROVIDER: return "SDK_ID_JAVA_LOCAL_PROVIDER";
425
+ case SdkId.SDK_ID_JS_LOCAL_SERVER_PROVIDER: return "SDK_ID_JS_LOCAL_SERVER_PROVIDER";
426
+ case SdkId.UNRECOGNIZED:
427
+ default: return "UNRECOGNIZED";
428
+ }
429
+ }
321
430
  let ResolveReason = /* @__PURE__ */ function(ResolveReason$1) {
322
431
  ResolveReason$1[ResolveReason$1["RESOLVE_REASON_UNSPECIFIED"] = 0] = "RESOLVE_REASON_UNSPECIFIED";
323
432
  ResolveReason$1[ResolveReason$1["RESOLVE_REASON_MATCH"] = 1] = "RESOLVE_REASON_MATCH";
@@ -363,12 +472,77 @@ function resolveReasonToJSON(object) {
363
472
  default: return "UNRECOGNIZED";
364
473
  }
365
474
  }
475
+ function createBaseSdk() {
476
+ return {
477
+ id: void 0,
478
+ customId: void 0,
479
+ version: ""
480
+ };
481
+ }
482
+ const Sdk = {
483
+ encode(message, writer = new BinaryWriter()) {
484
+ if (message.id !== void 0) writer.uint32(8).int32(message.id);
485
+ if (message.customId !== void 0) writer.uint32(18).string(message.customId);
486
+ if (message.version !== "") writer.uint32(26).string(message.version);
487
+ return writer;
488
+ },
489
+ decode(input, length) {
490
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
491
+ const end = length === void 0 ? reader.len : reader.pos + length;
492
+ const message = createBaseSdk();
493
+ while (reader.pos < end) {
494
+ const tag = reader.uint32();
495
+ switch (tag >>> 3) {
496
+ case 1:
497
+ if (tag !== 8) break;
498
+ message.id = reader.int32();
499
+ continue;
500
+ case 2:
501
+ if (tag !== 18) break;
502
+ message.customId = reader.string();
503
+ continue;
504
+ case 3:
505
+ if (tag !== 26) break;
506
+ message.version = reader.string();
507
+ continue;
508
+ }
509
+ if ((tag & 7) === 4 || tag === 0) break;
510
+ reader.skip(tag & 7);
511
+ }
512
+ return message;
513
+ },
514
+ fromJSON(object) {
515
+ return {
516
+ id: isSet$2(object.id) ? sdkIdFromJSON(object.id) : void 0,
517
+ customId: isSet$2(object.customId) ? globalThis.String(object.customId) : void 0,
518
+ version: isSet$2(object.version) ? globalThis.String(object.version) : ""
519
+ };
520
+ },
521
+ toJSON(message) {
522
+ const obj = {};
523
+ if (message.id !== void 0) obj.id = sdkIdToJSON(message.id);
524
+ if (message.customId !== void 0) obj.customId = message.customId;
525
+ if (message.version !== "") obj.version = message.version;
526
+ return obj;
527
+ },
528
+ create(base) {
529
+ return Sdk.fromPartial(base ?? {});
530
+ },
531
+ fromPartial(object) {
532
+ const message = createBaseSdk();
533
+ message.id = object.id ?? void 0;
534
+ message.customId = object.customId ?? void 0;
535
+ message.version = object.version ?? "";
536
+ return message;
537
+ }
538
+ };
366
539
  function createBaseResolveFlagsRequest() {
367
540
  return {
368
541
  flags: [],
369
542
  evaluationContext: void 0,
370
543
  clientSecret: "",
371
- apply: false
544
+ apply: false,
545
+ sdk: void 0
372
546
  };
373
547
  }
374
548
  const ResolveFlagsRequest = {
@@ -377,6 +551,7 @@ const ResolveFlagsRequest = {
377
551
  if (message.evaluationContext !== void 0) Struct.encode(Struct.wrap(message.evaluationContext), writer.uint32(18).fork()).join();
378
552
  if (message.clientSecret !== "") writer.uint32(26).string(message.clientSecret);
379
553
  if (message.apply !== false) writer.uint32(32).bool(message.apply);
554
+ if (message.sdk !== void 0) Sdk.encode(message.sdk, writer.uint32(42).fork()).join();
380
555
  return writer;
381
556
  },
382
557
  decode(input, length) {
@@ -402,6 +577,10 @@ const ResolveFlagsRequest = {
402
577
  if (tag !== 32) break;
403
578
  message.apply = reader.bool();
404
579
  continue;
580
+ case 5:
581
+ if (tag !== 42) break;
582
+ message.sdk = Sdk.decode(reader, reader.uint32());
583
+ continue;
405
584
  }
406
585
  if ((tag & 7) === 4 || tag === 0) break;
407
586
  reader.skip(tag & 7);
@@ -413,7 +592,8 @@ const ResolveFlagsRequest = {
413
592
  flags: globalThis.Array.isArray(object?.flags) ? object.flags.map((e) => globalThis.String(e)) : [],
414
593
  evaluationContext: isObject(object.evaluationContext) ? object.evaluationContext : void 0,
415
594
  clientSecret: isSet$2(object.clientSecret) ? globalThis.String(object.clientSecret) : "",
416
- apply: isSet$2(object.apply) ? globalThis.Boolean(object.apply) : false
595
+ apply: isSet$2(object.apply) ? globalThis.Boolean(object.apply) : false,
596
+ sdk: isSet$2(object.sdk) ? Sdk.fromJSON(object.sdk) : void 0
417
597
  };
418
598
  },
419
599
  toJSON(message) {
@@ -422,6 +602,7 @@ const ResolveFlagsRequest = {
422
602
  if (message.evaluationContext !== void 0) obj.evaluationContext = message.evaluationContext;
423
603
  if (message.clientSecret !== "") obj.clientSecret = message.clientSecret;
424
604
  if (message.apply !== false) obj.apply = message.apply;
605
+ if (message.sdk !== void 0) obj.sdk = Sdk.toJSON(message.sdk);
425
606
  return obj;
426
607
  },
427
608
  create(base) {
@@ -433,6 +614,7 @@ const ResolveFlagsRequest = {
433
614
  message.evaluationContext = object.evaluationContext ?? void 0;
434
615
  message.clientSecret = object.clientSecret ?? "";
435
616
  message.apply = object.apply ?? false;
617
+ message.sdk = object.sdk !== void 0 && object.sdk !== null ? Sdk.fromPartial(object.sdk) : void 0;
436
618
  return message;
437
619
  }
438
620
  };
@@ -1331,9 +1513,10 @@ function isObject(value) {
1331
1513
  function isSet$2(value) {
1332
1514
  return value !== null && value !== void 0;
1333
1515
  }
1516
+ const VERSION = "0.2.0";
1334
1517
  const NOOP_LOG_FN = () => {};
1335
1518
  const debugBackend = loadDebug();
1336
- const logger = new class LoggerImpl {
1519
+ const logger$1 = new class LoggerImpl {
1337
1520
  childLoggers = /* @__PURE__ */ new Map();
1338
1521
  debug = NOOP_LOG_FN;
1339
1522
  info = NOOP_LOG_FN;
@@ -1365,6 +1548,7 @@ const logger = new class LoggerImpl {
1365
1548
  return child;
1366
1549
  }
1367
1550
  }("cnfd");
1551
+ const getLogger = logger$1.getLogger.bind(logger$1);
1368
1552
  async function loadDebug() {
1369
1553
  try {
1370
1554
  const { default: debug } = await import("debug");
@@ -1394,7 +1578,7 @@ function scheduleWithFixedInterval(operation, intervalMs, opt = {}) {
1394
1578
  try {
1395
1579
  await operation(ac.signal);
1396
1580
  } catch (e) {
1397
- logger.warn("scheduleWithFixedInterval failure:", e);
1581
+ logger$1.warn("scheduleWithFixedInterval failure:", e);
1398
1582
  }
1399
1583
  concurrent--;
1400
1584
  if (Date.now() - lastRunTime > intervalMs && nextRunTimeoutId != 0) {
@@ -1456,7 +1640,7 @@ function promiseSignal(signal) {
1456
1640
  function abortablePromise(promise, signal) {
1457
1641
  return signal ? Promise.race([promise, promiseSignal(signal)]) : promise;
1458
1642
  }
1459
- const logger$1 = logger.getLogger("fetch");
1643
+ const logger$2 = logger$1.getLogger("fetch");
1460
1644
  let Fetch;
1461
1645
  (function(_Fetch) {
1462
1646
  function create(middleware, sink = fetch) {
@@ -1527,13 +1711,13 @@ function withRetry(opts) {
1527
1711
  const onSuccess = async (resp) => {
1528
1712
  const { status, statusText } = resp;
1529
1713
  if (status !== 408 && status !== 429 && status < 500 || attempts >= maxAttempts) return resp;
1530
- logger$1.debug("withRetry %s failed attempt %d with %d %s", url, attempts - 1, status, statusText);
1714
+ logger$2.debug("withRetry %s failed attempt %d with %d %s", url, attempts - 1, status, statusText);
1531
1715
  const serverDelay = parseRetryAfter(resp.headers.get("Retry-After"), baseInterval, maxInterval);
1532
1716
  await abortableSleep(serverDelay ?? deadline - Date.now(), signal);
1533
1717
  return doTry();
1534
1718
  };
1535
1719
  const onError = async (error) => {
1536
- logger$1.debug("withRetry %s failed attempt %d with %s", url, attempts - 1, error);
1720
+ logger$2.debug("withRetry %s failed attempt %d with %s", url, attempts - 1, error);
1537
1721
  if (signal?.aborted || attempts >= maxAttempts) throw error;
1538
1722
  await abortableSleep(deadline - Date.now(), signal);
1539
1723
  return doTry();
@@ -1559,10 +1743,10 @@ function withAuth(tokenProvider, signal) {
1559
1743
  clearTimeout(renewTimeout);
1560
1744
  });
1561
1745
  const renewToken = () => {
1562
- logger$1.debug("withAuth renewing token");
1746
+ logger$2.debug("withAuth renewing token");
1563
1747
  clearTimeout(renewTimeout);
1564
1748
  current = tokenProvider().then(([token, expiry]) => {
1565
- logger$1.debug("withAuth renew success %s", expiry && expiry.valueOf() - Date.now());
1749
+ logger$2.debug("withAuth renew success %s", expiry && expiry.valueOf() - Date.now());
1566
1750
  if (expiry) {
1567
1751
  const ttl = expiry.valueOf() - Date.now();
1568
1752
  renewTimeout = portableSetTimeout(renewToken, .8 * ttl);
@@ -1634,7 +1818,7 @@ function withRouter(routes) {
1634
1818
  return async (url, init = {}) => {
1635
1819
  const match = table.find(([pred]) => pred(url));
1636
1820
  if (!match) {
1637
- logger$1.info("withRouter no route matched %s, falling through", url);
1821
+ logger$2.info("withRouter no route matched %s, falling through", url);
1638
1822
  return next(url, init);
1639
1823
  }
1640
1824
  return match[1](url, init);
@@ -1644,13 +1828,13 @@ function withRouter(routes) {
1644
1828
  function withResponse(factory) {
1645
1829
  return (_next) => factory;
1646
1830
  }
1647
- const fetchLogger = logger$1;
1648
- function withLogging(logger$2 = fetchLogger) {
1831
+ const fetchLogger = logger$2;
1832
+ function withLogging(logger$3 = fetchLogger) {
1649
1833
  return (next) => async (url, init) => {
1650
1834
  const start = Date.now();
1651
1835
  const resp = await next(url, init);
1652
1836
  const duration = Date.now() - start;
1653
- logger$2.info("%s %s (%i) %dms", (init?.method ?? "get").toUpperCase(), url.split("?", 1)[0], resp.status, duration);
1837
+ logger$3.info("%s %s (%i) %dms", (init?.method ?? "get").toUpperCase(), url.split("?", 1)[0], resp.status, duration);
1654
1838
  return resp;
1655
1839
  };
1656
1840
  }
@@ -1702,7 +1886,7 @@ var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
1702
1886
  baseInterval: 500,
1703
1887
  maxInterval: DEFAULT_STATE_INTERVAL
1704
1888
  }), withStallTimeout(500)],
1705
- "https://flags.confidence.dev/*|https://resolver.confidence.dev/*": [withConfidenceAuth, withRouter({
1889
+ "https://resolver.confidence.dev/*": [withConfidenceAuth, withRouter({
1706
1890
  "*/v1/resolverState:resolverStateUri": [withFastRetry],
1707
1891
  "*/v1/flags:resolve": [withRetry({
1708
1892
  maxAttempts: 3,
@@ -1745,7 +1929,11 @@ var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
1745
1929
  flags: [`flags/${flagName}`],
1746
1930
  evaluationContext: ConfidenceServerProviderLocal.convertEvaluationContext(context),
1747
1931
  apply: true,
1748
- clientSecret: this.options.flagClientSecret
1932
+ clientSecret: this.options.flagClientSecret,
1933
+ sdk: {
1934
+ id: SdkId.SDK_ID_JS_LOCAL_SERVER_PROVIDER,
1935
+ version: VERSION
1936
+ }
1749
1937
  },
1750
1938
  materializationsPerUnit: {},
1751
1939
  failFastOnSticky: true
@@ -1830,7 +2018,7 @@ var ConfidenceServerProviderLocal = class ConfidenceServerProviderLocal {
1830
2018
  });
1831
2019
  }
1832
2020
  async fetchResolveStateUri(signal) {
1833
- const resp = await this.fetch("https://flags.confidence.dev/v1/resolverState:resolverStateUri", { signal });
2021
+ const resp = await this.fetch("https://resolver.confidence.dev/v1/resolverState:resolverStateUri", { signal });
1834
2022
  if (!resp.ok) throw new Error("Failed to get resolve state url");
1835
2023
  return resp.json();
1836
2024
  }
@@ -2111,11 +2299,22 @@ function longToNumber(int64) {
2111
2299
  function isSet(value) {
2112
2300
  return value !== null && value !== void 0;
2113
2301
  }
2114
- var WasmResolver = class WasmResolver {
2302
+ const logger = getLogger("wasm-resolver");
2303
+ const EXPORT_FN_NAMES = [
2304
+ "wasm_msg_alloc",
2305
+ "wasm_msg_free",
2306
+ "wasm_msg_guest_resolve_with_sticky",
2307
+ "wasm_msg_guest_set_resolver_state",
2308
+ "wasm_msg_guest_flush_logs"
2309
+ ];
2310
+ function verifyExports(exports) {
2311
+ for (const fnName of EXPORT_FN_NAMES) if (typeof exports[fnName] !== "function") throw new Error(`Expected Function export "${fnName}" found ${exports[fnName]}`);
2312
+ if (!(exports.memory instanceof WebAssembly.Memory)) throw new Error(`Expected WebAssembly.Memory export "memory", found ${exports.memory}`);
2313
+ }
2314
+ var UnsafeWasmResolver = class {
2115
2315
  exports;
2116
- imports;
2117
- constructor() {
2118
- this.imports = { wasm_msg: { wasm_msg_host_current_time: () => {
2316
+ constructor(module$1) {
2317
+ const { exports } = new WebAssembly.Instance(module$1, { wasm_msg: { wasm_msg_host_current_time: () => {
2119
2318
  const epochMillisecond = Date.now();
2120
2319
  const seconds = Math.floor(epochMillisecond / 1e3);
2121
2320
  const nanos = (epochMillisecond - 1e3 * seconds) * 1e6;
@@ -2123,7 +2322,9 @@ var WasmResolver = class WasmResolver {
2123
2322
  seconds,
2124
2323
  nanos
2125
2324
  }, Timestamp);
2126
- } } };
2325
+ } } });
2326
+ verifyExports(exports);
2327
+ this.exports = exports;
2127
2328
  }
2128
2329
  resolveWithSticky(request) {
2129
2330
  const reqPtr = this.transferRequest(request, ResolveWithStickyRequest);
@@ -2169,16 +2370,66 @@ var WasmResolver = class WasmResolver {
2169
2370
  free(ptr) {
2170
2371
  this.exports.wasm_msg_free(ptr);
2171
2372
  }
2172
- static async load(module$1) {
2173
- const wasmResolver = new WasmResolver();
2174
- wasmResolver.exports = (await WebAssembly.instantiate(module$1, wasmResolver.imports)).exports;
2175
- return wasmResolver;
2373
+ };
2374
+ const DEFAULT_DELEGATE_FACTORY = (module$1) => new UnsafeWasmResolver(module$1);
2375
+ var WasmResolver = class {
2376
+ delegate;
2377
+ currentState;
2378
+ bufferedLogs = [];
2379
+ constructor(module$1, delegateFactory = DEFAULT_DELEGATE_FACTORY) {
2380
+ this.module = module$1;
2381
+ this.delegateFactory = delegateFactory;
2382
+ this.delegate = delegateFactory(module$1);
2383
+ }
2384
+ reloadInstance(error) {
2385
+ logger.error("Failure calling into wasm:", error);
2386
+ try {
2387
+ this.bufferedLogs.push(this.delegate.flushLogs());
2388
+ } catch (_) {
2389
+ logger.error("Failed to flushLogs on error");
2390
+ }
2391
+ this.delegate = this.delegateFactory(this.module);
2392
+ if (this.currentState) this.delegate.setResolverState(this.currentState);
2393
+ }
2394
+ resolveWithSticky(request) {
2395
+ try {
2396
+ return this.delegate.resolveWithSticky(request);
2397
+ } catch (error) {
2398
+ if (error instanceof WebAssembly.RuntimeError) this.reloadInstance(error);
2399
+ throw error;
2400
+ }
2401
+ }
2402
+ setResolverState(request) {
2403
+ this.currentState = request;
2404
+ try {
2405
+ this.delegate.setResolverState(request);
2406
+ } catch (error) {
2407
+ if (error instanceof WebAssembly.RuntimeError) this.reloadInstance(error);
2408
+ throw error;
2409
+ }
2410
+ }
2411
+ flushLogs() {
2412
+ try {
2413
+ this.bufferedLogs.push(this.delegate.flushLogs());
2414
+ const len = this.bufferedLogs.reduce((sum, chunk) => sum + chunk.length, 0);
2415
+ const buffer$1 = new Uint8Array(len);
2416
+ let offset = 0;
2417
+ for (const chunk of this.bufferedLogs) {
2418
+ buffer$1.set(chunk, offset);
2419
+ offset += chunk.length;
2420
+ }
2421
+ this.bufferedLogs.length = 0;
2422
+ return buffer$1;
2423
+ } catch (error) {
2424
+ if (error instanceof WebAssembly.RuntimeError) this.reloadInstance(error);
2425
+ throw error;
2426
+ }
2176
2427
  }
2177
2428
  };
2178
2429
  const wasmPath = __require.resolve("./confidence_resolver.wasm");
2179
2430
  const buffer = await fs.readFile(wasmPath);
2180
2431
  const module = await WebAssembly.compile(buffer);
2181
- const resolver = await WasmResolver.load(module);
2432
+ const resolver = new WasmResolver(module);
2182
2433
  function createConfidenceServerProvider(options) {
2183
2434
  return new ConfidenceServerProviderLocal(resolver, options);
2184
2435
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spotify-confidence/openfeature-server-provider-local",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Spotify Confidence Open Feature provider",
5
5
  "type": "module",
6
6
  "files": [
@@ -32,8 +32,10 @@
32
32
  "scripts": {
33
33
  "build": "tsdown",
34
34
  "dev": "tsdown --watch",
35
+ "format": "prettier --config prettier.config.cjs -w .",
36
+ "format:check": "prettier --config prettier.config.cjs -c .",
35
37
  "test": "vitest",
36
- "proto:gen": "rm -rf src/proto && mkdir -p src/proto && protoc --plugin=node_modules/.bin/protoc-gen-ts_proto --ts_proto_opt useOptionals=messages --ts_proto_opt esModuleInterop=true --ts_proto_out src/proto -Iproto api.proto messages.proto"
38
+ "proto:gen": "rm -rf src/proto && mkdir -p src/proto && protoc --plugin=node_modules/.bin/protoc-gen-ts_proto --ts_proto_opt useOptionals=messages --ts_proto_opt esModuleInterop=true --ts_proto_out src/proto -Iproto api.proto messages.proto test-only.proto"
37
39
  },
38
40
  "dependencies": {
39
41
  "@bufbuild/protobuf": "^2.9.0"
@@ -41,11 +43,13 @@
41
43
  "devDependencies": {
42
44
  "@openfeature/core": "^1.9.0",
43
45
  "@openfeature/server-sdk": "^1.19.0",
46
+ "@spotify/prettier-config": "^15.0.0",
44
47
  "@types/debug": "^4",
45
48
  "@types/node": "^24.0.1",
46
49
  "@vitest/coverage-v8": "^3.2.4",
47
50
  "debug": "^4.4.3",
48
51
  "dotenv": "^17.2.2",
52
+ "prettier": "^2.8.8",
49
53
  "rolldown": "1.0.0-beta.38",
50
54
  "ts-proto": "^2.7.3",
51
55
  "tsdown": "latest",
@@ -62,7 +66,7 @@
62
66
  "packageManager": "yarn@4.6.0",
63
67
  "repository": {
64
68
  "type": "git",
65
- "url": "https://github.com/spotify/confidence-resolver-rust",
69
+ "url": "https://github.com/spotify/confidence-resolver",
66
70
  "directory": "openfeature-provider/js"
67
71
  },
68
72
  "license": "Apache-2.0",
@@ -76,7 +80,7 @@
76
80
  ],
77
81
  "author": "Spotify",
78
82
  "bugs": {
79
- "url": "https://github.com/spotify/confidence-resolver-rust/issues"
83
+ "url": "https://github.com/spotify/confidence-resolver/issues"
80
84
  },
81
- "homepage": "https://github.com/spotify/confidence-resolver-rust?tab=readme-ov-file#confidence-rust-flags-resolver"
85
+ "homepage": "https://github.com/spotify/confidence-resolver?tab=readme-ov-file#confidence-rust-flags-resolver"
82
86
  }