@flipt-io/flipt-client-js 0.0.1 → 0.0.3

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.
@@ -1,3 +1,6 @@
1
+ 'use strict';
2
+
3
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
1
4
  let wasm;
2
5
 
3
6
  let WASM_VECTOR_LEN = 0;
@@ -91,8 +94,7 @@ function handleError(f, args) {
91
94
 
92
95
  const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
93
96
 
94
- if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
95
-
97
+ if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }
96
98
  function getStringFromWasm0(ptr, len) {
97
99
  ptr = ptr >>> 0;
98
100
  return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
@@ -177,7 +179,7 @@ const EngineFinalization = (typeof FinalizationRegistry === 'undefined')
177
179
  ? { register: () => {}, unregister: () => {} }
178
180
  : new FinalizationRegistry(ptr => wasm.__wbg_engine_free(ptr >>> 0, 1));
179
181
 
180
- export class Engine {
182
+ class Engine {
181
183
 
182
184
  __destroy_into_raw() {
183
185
  const ptr = this.__wbg_ptr;
@@ -479,7 +481,6 @@ function __wbg_get_imports() {
479
481
  table.set(offset + 1, null);
480
482
  table.set(offset + 2, true);
481
483
  table.set(offset + 3, false);
482
- ;
483
484
  };
484
485
  imports.wbg.__wbindgen_is_function = function(arg0) {
485
486
  const ret = typeof(arg0) === 'function';
@@ -535,10 +536,6 @@ function __wbg_get_imports() {
535
536
  return imports;
536
537
  }
537
538
 
538
- function __wbg_init_memory(imports, memory) {
539
-
540
- }
541
-
542
539
  function __wbg_finalize_init(instance, module) {
543
540
  wasm = instance.exports;
544
541
  __wbg_init.__wbindgen_wasm_module = module;
@@ -550,45 +547,20 @@ function __wbg_finalize_init(instance, module) {
550
547
  return wasm;
551
548
  }
552
549
 
553
- function initSync(module) {
554
- if (wasm !== undefined) return wasm;
555
-
556
-
557
- if (typeof module !== 'undefined') {
558
- if (Object.getPrototypeOf(module) === Object.prototype) {
559
- ({module} = module)
560
- } else {
561
- console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
562
- }
563
- }
564
-
565
- const imports = __wbg_get_imports();
566
-
567
- __wbg_init_memory(imports);
568
-
569
- if (!(module instanceof WebAssembly.Module)) {
570
- module = new WebAssembly.Module(module);
571
- }
572
-
573
- const instance = new WebAssembly.Instance(module, imports);
574
-
575
- return __wbg_finalize_init(instance, module);
576
- }
577
-
578
550
  async function __wbg_init(module_or_path) {
579
551
  if (wasm !== undefined) return wasm;
580
552
 
581
553
 
582
554
  if (typeof module_or_path !== 'undefined') {
583
555
  if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
584
- ({module_or_path} = module_or_path)
556
+ ({module_or_path} = module_or_path);
585
557
  } else {
586
- console.warn('using deprecated parameters for the initialization function; pass a single object instead')
558
+ console.warn('using deprecated parameters for the initialization function; pass a single object instead');
587
559
  }
588
560
  }
589
561
 
590
562
  if (typeof module_or_path === 'undefined') {
591
- module_or_path = new URL('flipt_engine_wasm_js_bg.wasm', import.meta.url);
563
+ module_or_path = new URL('flipt_engine_wasm_js_bg.wasm', (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
592
564
  }
593
565
  const imports = __wbg_get_imports();
594
566
 
@@ -596,12 +568,284 @@ async function __wbg_init(module_or_path) {
596
568
  module_or_path = fetch(module_or_path);
597
569
  }
598
570
 
599
- __wbg_init_memory(imports);
600
-
601
571
  const { instance, module } = await __wbg_load(await module_or_path, imports);
602
572
 
603
573
  return __wbg_finalize_init(instance, module);
604
574
  }
605
575
 
606
- export { initSync };
607
- export default __wbg_init;
576
+ /**
577
+ * Defines the strategy to handle errors during the flags snapshot update calls.
578
+ */
579
+ exports.ErrorStrategy = void 0;
580
+ (function (ErrorStrategy) {
581
+ /** The client will throw an error if the flag state cannot be fetched. This is the default behavior. */
582
+ ErrorStrategy["Fail"] = "fail";
583
+ /** The client will maintain the last known good state and use that state for evaluation in case of an error. */
584
+ ErrorStrategy["Fallback"] = "fallback";
585
+ })(exports.ErrorStrategy || (exports.ErrorStrategy = {}));
586
+
587
+ function snakeToCamel(str) {
588
+ return str.replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''));
589
+ }
590
+ function camelToSnake(str) {
591
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
592
+ }
593
+ function deserialize(data) {
594
+ const result = {};
595
+ for (const key in data) {
596
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
597
+ const camelKey = snakeToCamel(key);
598
+ result[camelKey] = data[key];
599
+ }
600
+ }
601
+ return result;
602
+ }
603
+ function serialize(data) {
604
+ const result = {};
605
+ for (const key in data) {
606
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
607
+ const snakeKey = camelToSnake(key);
608
+ result[snakeKey] = data[key];
609
+ }
610
+ }
611
+ return result;
612
+ }
613
+
614
+ class BaseFliptClient {
615
+ constructor(engine, fetcher) {
616
+ this.engine = engine;
617
+ this.fetcher = fetcher;
618
+ }
619
+ /**
620
+ * Store etag from response for next requests
621
+ */
622
+ storeEtag(resp) {
623
+ const etag = resp.headers.get('etag');
624
+ if (etag) {
625
+ this.etag = etag;
626
+ }
627
+ }
628
+ /**
629
+ * Refresh the flags snapshot
630
+ * @returns true if snapshot changed
631
+ */
632
+ async refresh() {
633
+ try {
634
+ const opts = { etag: this.etag };
635
+ const resp = await this.fetcher(opts);
636
+ const etag = resp.headers.get('etag');
637
+ if (this.etag && this.etag === etag) {
638
+ return false;
639
+ }
640
+ this.storeEtag(resp);
641
+ const data = await resp.json();
642
+ this.engine.snapshot(data);
643
+ return true;
644
+ }
645
+ catch (error) {
646
+ if (this.errorStrategy === exports.ErrorStrategy.Fail) {
647
+ throw error;
648
+ }
649
+ }
650
+ return false;
651
+ }
652
+ /**
653
+ * Evaluate a variant flag
654
+ */
655
+ evaluateVariant({ flagKey, entityId, context }) {
656
+ if (!flagKey || flagKey.trim() === '') {
657
+ throw new Error('flagKey cannot be empty');
658
+ }
659
+ if (!entityId || entityId.trim() === '') {
660
+ throw new Error('entityId cannot be empty');
661
+ }
662
+ const evaluationRequest = {
663
+ flagKey,
664
+ entityId,
665
+ context
666
+ };
667
+ const result = this.engine.evaluate_variant(serialize(evaluationRequest));
668
+ if (result === null) {
669
+ throw new Error('Failed to evaluate variant');
670
+ }
671
+ const variantResult = deserialize(result);
672
+ if (variantResult.status === 'failure') {
673
+ throw new Error(variantResult.errorMessage);
674
+ }
675
+ if (!variantResult.result) {
676
+ throw new Error('Failed to evaluate variant');
677
+ }
678
+ return deserialize(variantResult.result);
679
+ }
680
+ /**
681
+ * Evaluate a boolean flag
682
+ */
683
+ evaluateBoolean({ flagKey, entityId, context }) {
684
+ if (!flagKey || flagKey.trim() === '') {
685
+ throw new Error('flagKey cannot be empty');
686
+ }
687
+ if (!entityId || entityId.trim() === '') {
688
+ throw new Error('entityId cannot be empty');
689
+ }
690
+ const evaluationRequest = {
691
+ flagKey,
692
+ entityId,
693
+ context
694
+ };
695
+ const result = this.engine.evaluate_boolean(serialize(evaluationRequest));
696
+ if (result === null) {
697
+ throw new Error('Failed to evaluate boolean');
698
+ }
699
+ const booleanResult = deserialize(result);
700
+ if (booleanResult.status === 'failure') {
701
+ throw new Error(booleanResult.errorMessage);
702
+ }
703
+ if (!booleanResult.result) {
704
+ throw new Error('Failed to evaluate boolean');
705
+ }
706
+ return deserialize(booleanResult.result);
707
+ }
708
+ /**
709
+ * Evaluate a batch of flag requests
710
+ */
711
+ evaluateBatch(requests) {
712
+ const serializedRequests = requests.map(serialize);
713
+ const batchResult = this.engine.evaluate_batch(serializedRequests);
714
+ if (batchResult === null) {
715
+ throw new Error('Failed to evaluate batch');
716
+ }
717
+ if (batchResult.status === 'failure') {
718
+ throw new Error(batchResult.errorMessage);
719
+ }
720
+ if (!batchResult.result) {
721
+ throw new Error('Failed to evaluate batch');
722
+ }
723
+ const responses = batchResult.result.responses
724
+ .map((response) => {
725
+ if (response.type === 'BOOLEAN_EVALUATION_RESPONSE_TYPE') {
726
+ const booleanResponse = deserialize(
727
+ // @ts-ignore
728
+ response.boolean_evaluation_response);
729
+ return {
730
+ booleanEvaluationResponse: booleanResponse,
731
+ type: 'BOOLEAN_EVALUATION_RESPONSE_TYPE'
732
+ };
733
+ }
734
+ if (response.type === 'VARIANT_EVALUATION_RESPONSE_TYPE') {
735
+ const variantResponse = deserialize(
736
+ // @ts-ignore
737
+ response.variant_evaluation_response);
738
+ return {
739
+ variantEvaluationResponse: variantResponse,
740
+ type: 'VARIANT_EVALUATION_RESPONSE_TYPE'
741
+ };
742
+ }
743
+ if (response.type === 'ERROR_EVALUATION_RESPONSE_TYPE') {
744
+ const errorResponse = deserialize(
745
+ // @ts-ignore
746
+ response.error_evaluation_response);
747
+ return {
748
+ errorEvaluationResponse: errorResponse,
749
+ type: 'ERROR_EVALUATION_RESPONSE_TYPE'
750
+ };
751
+ }
752
+ return undefined;
753
+ })
754
+ .filter((response) => response !== undefined);
755
+ return {
756
+ responses,
757
+ requestDurationMillis: batchResult.result.requestDurationMillis
758
+ };
759
+ }
760
+ listFlags() {
761
+ const listFlagsResult = this.engine.list_flags();
762
+ if (listFlagsResult === null) {
763
+ throw new Error('Failed to list flags');
764
+ }
765
+ const flags = deserialize(listFlagsResult);
766
+ if (flags.status === 'failure') {
767
+ throw new Error(flags.errorMessage);
768
+ }
769
+ if (!flags.result) {
770
+ throw new Error('Failed to list flags');
771
+ }
772
+ return flags.result.map((deserialize));
773
+ }
774
+ }
775
+
776
+ class FliptClient extends BaseFliptClient {
777
+ /**
778
+ * Initialize the client
779
+ * @param options - optional client options
780
+ * @param wasmOptions - options for loading WASM
781
+ * @returns {Promise<FliptClient>}
782
+ */
783
+ static async init(options = {
784
+ namespace: 'default',
785
+ url: 'http://localhost:8080',
786
+ reference: '',
787
+ updateInterval: 120,
788
+ errorStrategy: exports.ErrorStrategy.Fail
789
+ }, wasmOptions) {
790
+ if (!wasmOptions || !wasmOptions.wasm) {
791
+ throw new Error('WASM module must be provided in slim mode. Use the standard client or provide a wasm module.');
792
+ }
793
+ const namespace = options.namespace ?? 'default';
794
+ let url = options.url ?? 'http://localhost:8080';
795
+ url = url.replace(/\/$/, '');
796
+ url = `${url}/internal/v1/evaluation/snapshot/namespace/${namespace}`;
797
+ if (options.reference) {
798
+ url = `${url}?reference=${options.reference}`;
799
+ }
800
+ const headers = {
801
+ Accept: 'application/json',
802
+ 'x-flipt-accept-server-version': '1.47.0'
803
+ };
804
+ if (options.authentication) {
805
+ if ('clientToken' in options.authentication) {
806
+ headers['Authorization'] =
807
+ `Bearer ${options.authentication.clientToken}`;
808
+ }
809
+ else if ('jwtToken' in options.authentication) {
810
+ headers['Authorization'] = `JWT ${options.authentication.jwtToken}`;
811
+ }
812
+ }
813
+ let fetcher = options.fetcher;
814
+ if (!fetcher) {
815
+ fetcher = async (opts) => {
816
+ if (opts && opts.etag) {
817
+ headers['If-None-Match'] = opts.etag;
818
+ }
819
+ const resp = await fetch(url, {
820
+ method: 'GET',
821
+ headers
822
+ });
823
+ if (!resp.ok && resp.status !== 304) {
824
+ throw new Error(`Failed to fetch data: ${resp.statusText}`);
825
+ }
826
+ return resp;
827
+ };
828
+ }
829
+ // Initialize WASM engine with the provided WASM module
830
+ await __wbg_init(wasmOptions.wasm);
831
+ if (!fetcher) {
832
+ throw new Error('Failed to initialize fetcher');
833
+ }
834
+ // handle case if they pass in a custom fetcher that doesn't throw on non-2xx status codes
835
+ const resp = await fetcher();
836
+ if (!resp.ok) {
837
+ throw new Error(`Failed to fetch data: ${resp.statusText}`);
838
+ }
839
+ const data = await resp.json();
840
+ const engine = new Engine(namespace);
841
+ engine.snapshot(data);
842
+ // Create client instance
843
+ const client = new FliptClient(engine, fetcher);
844
+ client.storeEtag(resp);
845
+ client.errorStrategy = options.errorStrategy;
846
+ return client;
847
+ }
848
+ }
849
+
850
+ exports.BaseFliptClient = BaseFliptClient;
851
+ exports.FliptClient = FliptClient;