@flipt-io/flipt-client-js 0.0.2 → 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.
@@ -91,8 +91,7 @@ function handleError(f, args) {
91
91
 
92
92
  const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
93
93
 
94
- if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
95
-
94
+ if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }
96
95
  function getStringFromWasm0(ptr, len) {
97
96
  ptr = ptr >>> 0;
98
97
  return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
@@ -177,7 +176,7 @@ const EngineFinalization = (typeof FinalizationRegistry === 'undefined')
177
176
  ? { register: () => {}, unregister: () => {} }
178
177
  : new FinalizationRegistry(ptr => wasm.__wbg_engine_free(ptr >>> 0, 1));
179
178
 
180
- export class Engine {
179
+ class Engine {
181
180
 
182
181
  __destroy_into_raw() {
183
182
  const ptr = this.__wbg_ptr;
@@ -479,7 +478,6 @@ function __wbg_get_imports() {
479
478
  table.set(offset + 1, null);
480
479
  table.set(offset + 2, true);
481
480
  table.set(offset + 3, false);
482
- ;
483
481
  };
484
482
  imports.wbg.__wbindgen_is_function = function(arg0) {
485
483
  const ret = typeof(arg0) === 'function';
@@ -535,10 +533,6 @@ function __wbg_get_imports() {
535
533
  return imports;
536
534
  }
537
535
 
538
- function __wbg_init_memory(imports, memory) {
539
-
540
- }
541
-
542
536
  function __wbg_finalize_init(instance, module) {
543
537
  wasm = instance.exports;
544
538
  __wbg_init.__wbindgen_wasm_module = module;
@@ -550,40 +544,15 @@ function __wbg_finalize_init(instance, module) {
550
544
  return wasm;
551
545
  }
552
546
 
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
547
  async function __wbg_init(module_or_path) {
579
548
  if (wasm !== undefined) return wasm;
580
549
 
581
550
 
582
551
  if (typeof module_or_path !== 'undefined') {
583
552
  if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
584
- ({module_or_path} = module_or_path)
553
+ ({module_or_path} = module_or_path);
585
554
  } else {
586
- console.warn('using deprecated parameters for the initialization function; pass a single object instead')
555
+ console.warn('using deprecated parameters for the initialization function; pass a single object instead');
587
556
  }
588
557
  }
589
558
 
@@ -596,12 +565,283 @@ async function __wbg_init(module_or_path) {
596
565
  module_or_path = fetch(module_or_path);
597
566
  }
598
567
 
599
- __wbg_init_memory(imports);
600
-
601
568
  const { instance, module } = await __wbg_load(await module_or_path, imports);
602
569
 
603
570
  return __wbg_finalize_init(instance, module);
604
571
  }
605
572
 
606
- export { initSync };
607
- export default __wbg_init;
573
+ /**
574
+ * Defines the strategy to handle errors during the flags snapshot update calls.
575
+ */
576
+ var ErrorStrategy;
577
+ (function (ErrorStrategy) {
578
+ /** The client will throw an error if the flag state cannot be fetched. This is the default behavior. */
579
+ ErrorStrategy["Fail"] = "fail";
580
+ /** The client will maintain the last known good state and use that state for evaluation in case of an error. */
581
+ ErrorStrategy["Fallback"] = "fallback";
582
+ })(ErrorStrategy || (ErrorStrategy = {}));
583
+
584
+ function snakeToCamel(str) {
585
+ return str.replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''));
586
+ }
587
+ function camelToSnake(str) {
588
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
589
+ }
590
+ function deserialize(data) {
591
+ const result = {};
592
+ for (const key in data) {
593
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
594
+ const camelKey = snakeToCamel(key);
595
+ result[camelKey] = data[key];
596
+ }
597
+ }
598
+ return result;
599
+ }
600
+ function serialize(data) {
601
+ const result = {};
602
+ for (const key in data) {
603
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
604
+ const snakeKey = camelToSnake(key);
605
+ result[snakeKey] = data[key];
606
+ }
607
+ }
608
+ return result;
609
+ }
610
+
611
+ class BaseFliptClient {
612
+ constructor(engine, fetcher) {
613
+ this.engine = engine;
614
+ this.fetcher = fetcher;
615
+ }
616
+ /**
617
+ * Store etag from response for next requests
618
+ */
619
+ storeEtag(resp) {
620
+ const etag = resp.headers.get('etag');
621
+ if (etag) {
622
+ this.etag = etag;
623
+ }
624
+ }
625
+ /**
626
+ * Refresh the flags snapshot
627
+ * @returns true if snapshot changed
628
+ */
629
+ async refresh() {
630
+ try {
631
+ const opts = { etag: this.etag };
632
+ const resp = await this.fetcher(opts);
633
+ const etag = resp.headers.get('etag');
634
+ if (this.etag && this.etag === etag) {
635
+ return false;
636
+ }
637
+ this.storeEtag(resp);
638
+ const data = await resp.json();
639
+ this.engine.snapshot(data);
640
+ return true;
641
+ }
642
+ catch (error) {
643
+ if (this.errorStrategy === ErrorStrategy.Fail) {
644
+ throw error;
645
+ }
646
+ }
647
+ return false;
648
+ }
649
+ /**
650
+ * Evaluate a variant flag
651
+ */
652
+ evaluateVariant({ flagKey, entityId, context }) {
653
+ if (!flagKey || flagKey.trim() === '') {
654
+ throw new Error('flagKey cannot be empty');
655
+ }
656
+ if (!entityId || entityId.trim() === '') {
657
+ throw new Error('entityId cannot be empty');
658
+ }
659
+ const evaluationRequest = {
660
+ flagKey,
661
+ entityId,
662
+ context
663
+ };
664
+ const result = this.engine.evaluate_variant(serialize(evaluationRequest));
665
+ if (result === null) {
666
+ throw new Error('Failed to evaluate variant');
667
+ }
668
+ const variantResult = deserialize(result);
669
+ if (variantResult.status === 'failure') {
670
+ throw new Error(variantResult.errorMessage);
671
+ }
672
+ if (!variantResult.result) {
673
+ throw new Error('Failed to evaluate variant');
674
+ }
675
+ return deserialize(variantResult.result);
676
+ }
677
+ /**
678
+ * Evaluate a boolean flag
679
+ */
680
+ evaluateBoolean({ flagKey, entityId, context }) {
681
+ if (!flagKey || flagKey.trim() === '') {
682
+ throw new Error('flagKey cannot be empty');
683
+ }
684
+ if (!entityId || entityId.trim() === '') {
685
+ throw new Error('entityId cannot be empty');
686
+ }
687
+ const evaluationRequest = {
688
+ flagKey,
689
+ entityId,
690
+ context
691
+ };
692
+ const result = this.engine.evaluate_boolean(serialize(evaluationRequest));
693
+ if (result === null) {
694
+ throw new Error('Failed to evaluate boolean');
695
+ }
696
+ const booleanResult = deserialize(result);
697
+ if (booleanResult.status === 'failure') {
698
+ throw new Error(booleanResult.errorMessage);
699
+ }
700
+ if (!booleanResult.result) {
701
+ throw new Error('Failed to evaluate boolean');
702
+ }
703
+ return deserialize(booleanResult.result);
704
+ }
705
+ /**
706
+ * Evaluate a batch of flag requests
707
+ */
708
+ evaluateBatch(requests) {
709
+ const serializedRequests = requests.map(serialize);
710
+ const batchResult = this.engine.evaluate_batch(serializedRequests);
711
+ if (batchResult === null) {
712
+ throw new Error('Failed to evaluate batch');
713
+ }
714
+ if (batchResult.status === 'failure') {
715
+ throw new Error(batchResult.errorMessage);
716
+ }
717
+ if (!batchResult.result) {
718
+ throw new Error('Failed to evaluate batch');
719
+ }
720
+ const responses = batchResult.result.responses
721
+ .map((response) => {
722
+ if (response.type === 'BOOLEAN_EVALUATION_RESPONSE_TYPE') {
723
+ const booleanResponse = deserialize(
724
+ // @ts-ignore
725
+ response.boolean_evaluation_response);
726
+ return {
727
+ booleanEvaluationResponse: booleanResponse,
728
+ type: 'BOOLEAN_EVALUATION_RESPONSE_TYPE'
729
+ };
730
+ }
731
+ if (response.type === 'VARIANT_EVALUATION_RESPONSE_TYPE') {
732
+ const variantResponse = deserialize(
733
+ // @ts-ignore
734
+ response.variant_evaluation_response);
735
+ return {
736
+ variantEvaluationResponse: variantResponse,
737
+ type: 'VARIANT_EVALUATION_RESPONSE_TYPE'
738
+ };
739
+ }
740
+ if (response.type === 'ERROR_EVALUATION_RESPONSE_TYPE') {
741
+ const errorResponse = deserialize(
742
+ // @ts-ignore
743
+ response.error_evaluation_response);
744
+ return {
745
+ errorEvaluationResponse: errorResponse,
746
+ type: 'ERROR_EVALUATION_RESPONSE_TYPE'
747
+ };
748
+ }
749
+ return undefined;
750
+ })
751
+ .filter((response) => response !== undefined);
752
+ return {
753
+ responses,
754
+ requestDurationMillis: batchResult.result.requestDurationMillis
755
+ };
756
+ }
757
+ listFlags() {
758
+ const listFlagsResult = this.engine.list_flags();
759
+ if (listFlagsResult === null) {
760
+ throw new Error('Failed to list flags');
761
+ }
762
+ const flags = deserialize(listFlagsResult);
763
+ if (flags.status === 'failure') {
764
+ throw new Error(flags.errorMessage);
765
+ }
766
+ if (!flags.result) {
767
+ throw new Error('Failed to list flags');
768
+ }
769
+ return flags.result.map((deserialize));
770
+ }
771
+ }
772
+
773
+ class FliptClient extends BaseFliptClient {
774
+ /**
775
+ * Initialize the client
776
+ * @param options - optional client options
777
+ * @param wasmOptions - options for loading WASM
778
+ * @returns {Promise<FliptClient>}
779
+ */
780
+ static async init(options = {
781
+ namespace: 'default',
782
+ url: 'http://localhost:8080',
783
+ reference: '',
784
+ updateInterval: 120,
785
+ errorStrategy: ErrorStrategy.Fail
786
+ }, wasmOptions) {
787
+ if (!wasmOptions || !wasmOptions.wasm) {
788
+ throw new Error('WASM module must be provided in slim mode. Use the standard client or provide a wasm module.');
789
+ }
790
+ const namespace = options.namespace ?? 'default';
791
+ let url = options.url ?? 'http://localhost:8080';
792
+ url = url.replace(/\/$/, '');
793
+ url = `${url}/internal/v1/evaluation/snapshot/namespace/${namespace}`;
794
+ if (options.reference) {
795
+ url = `${url}?reference=${options.reference}`;
796
+ }
797
+ const headers = {
798
+ Accept: 'application/json',
799
+ 'x-flipt-accept-server-version': '1.47.0'
800
+ };
801
+ if (options.authentication) {
802
+ if ('clientToken' in options.authentication) {
803
+ headers['Authorization'] =
804
+ `Bearer ${options.authentication.clientToken}`;
805
+ }
806
+ else if ('jwtToken' in options.authentication) {
807
+ headers['Authorization'] = `JWT ${options.authentication.jwtToken}`;
808
+ }
809
+ }
810
+ let fetcher = options.fetcher;
811
+ if (!fetcher) {
812
+ fetcher = async (opts) => {
813
+ if (opts && opts.etag) {
814
+ headers['If-None-Match'] = opts.etag;
815
+ }
816
+ const resp = await fetch(url, {
817
+ method: 'GET',
818
+ headers
819
+ });
820
+ if (!resp.ok && resp.status !== 304) {
821
+ throw new Error(`Failed to fetch data: ${resp.statusText}`);
822
+ }
823
+ return resp;
824
+ };
825
+ }
826
+ // Initialize WASM engine with the provided WASM module
827
+ await __wbg_init(wasmOptions.wasm);
828
+ if (!fetcher) {
829
+ throw new Error('Failed to initialize fetcher');
830
+ }
831
+ // handle case if they pass in a custom fetcher that doesn't throw on non-2xx status codes
832
+ const resp = await fetcher();
833
+ if (!resp.ok) {
834
+ throw new Error(`Failed to fetch data: ${resp.statusText}`);
835
+ }
836
+ const data = await resp.json();
837
+ const engine = new Engine(namespace);
838
+ engine.snapshot(data);
839
+ // Create client instance
840
+ const client = new FliptClient(engine, fetcher);
841
+ client.storeEtag(resp);
842
+ client.errorStrategy = options.errorStrategy;
843
+ return client;
844
+ }
845
+ }
846
+
847
+ export { BaseFliptClient, ErrorStrategy, FliptClient };
@@ -1,7 +1,7 @@
1
- import { BaseFliptClient } from '../core/base';
2
- import { ClientOptions } from '../core/types';
3
- export * from '../core/types';
4
- export * from '../core/base';
1
+ import { BaseFliptClient } from '~/core/base';
2
+ import { ClientOptions } from '~/core/types';
3
+ export * from '~/core/types';
4
+ export * from '~/core/base';
5
5
  export declare class FliptClient extends BaseFliptClient {
6
6
  /**
7
7
  * Initialize the client
@@ -1,7 +1,7 @@
1
- import { BaseFliptClient } from '../core/base';
2
- import { ClientOptions } from '../core/types';
3
- export * from '../core/types';
4
- export * from '../core/base';
1
+ import { BaseFliptClient } from '~/core/base';
2
+ import { ClientOptions } from '~/core/types';
3
+ export * from '~/core/types';
4
+ export * from '~/core/base';
5
5
  export declare class FliptClient extends BaseFliptClient {
6
6
  private updateInterval?;
7
7
  /**
@@ -0,0 +1,31 @@
1
+ import { BaseFliptClient } from '~/core/base';
2
+ import { ClientOptions } from '~/core/types';
3
+ export * from '~/core/types';
4
+ export * from '~/core/base';
5
+ export interface WasmOptions {
6
+ /**
7
+ * The WASM module to use for evaluation.
8
+ * Can be provided as:
9
+ * - A URL string pointing to the WASM file
10
+ * - A filesystem path to the WASM file (in Node.js environments)
11
+ * - An ArrayBuffer or Uint8Array containing the WASM binary
12
+ * - A WebAssembly.Module object
13
+ *
14
+ * The simplest way to provide the WASM module is to import it directly from the package:
15
+ * ```
16
+ * import wasm from '@flipt-io/flipt-client-js/flipt.wasm';
17
+ *
18
+ * const client = await FliptClient.init({ ... }, { wasm });
19
+ * ```
20
+ */
21
+ wasm: ArrayBuffer | Uint8Array | WebAssembly.Module | string;
22
+ }
23
+ export declare class FliptClient extends BaseFliptClient {
24
+ /**
25
+ * Initialize the client
26
+ * @param options - optional client options
27
+ * @param wasmOptions - options for loading WASM
28
+ * @returns {Promise<FliptClient>}
29
+ */
30
+ static init(options?: ClientOptions, wasmOptions?: WasmOptions): Promise<FliptClient>;
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flipt-io/flipt-client-js",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Flipt Client Evaluation SDK for Web and Node.js",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.cjs",
@@ -20,6 +20,10 @@
20
20
  "default": {
21
21
  "import": "./dist/browser/index.mjs",
22
22
  "require": "./dist/browser/index.cjs"
23
+ },
24
+ "slim": {
25
+ "import": "./dist/slim/index.mjs",
26
+ "require": "./dist/slim/index.cjs"
23
27
  }
24
28
  },
25
29
  "./node": {
@@ -31,7 +35,13 @@
31
35
  "types": "./dist/types/index.d.ts",
32
36
  "import": "./dist/browser/index.mjs",
33
37
  "require": "./dist/browser/index.cjs"
34
- }
38
+ },
39
+ "./slim": {
40
+ "types": "./dist/types/slim/index.d.ts",
41
+ "import": "./dist/slim/index.mjs",
42
+ "require": "./dist/slim/index.cjs"
43
+ },
44
+ "./engine.wasm": "./dist/flipt_engine_wasm_js_bg.wasm"
35
45
  },
36
46
  "typesVersions": {
37
47
  "*": {
@@ -43,13 +53,14 @@
43
53
  ],
44
54
  "browser": [
45
55
  "./dist/types/index.d.ts"
56
+ ],
57
+ "slim": [
58
+ "./dist/types/slim/index.d.ts"
46
59
  ]
47
60
  }
48
61
  },
49
62
  "files": [
50
- "dist/browser",
51
- "dist/node",
52
- "dist/types",
63
+ "dist",
53
64
  "README.md"
54
65
  ],
55
66
  "scripts": {
@@ -65,18 +76,16 @@
65
76
  "devDependencies": {
66
77
  "@rollup/plugin-typescript": "^12.1.0",
67
78
  "@rollup/plugin-wasm": "^6.2.2",
68
- "@types/glob": "^8.1.0",
69
79
  "@types/jest": "^29.5.12",
70
80
  "@types/node": "^22.13.14",
71
81
  "@types/node-fetch": "^2.6.11",
72
82
  "@typescript-eslint/eslint-plugin": "^5.47.0",
73
83
  "@typescript-eslint/parser": "^5.47.0",
74
84
  "eslint": "^8.30.0",
75
- "glob": "^8.1.0",
76
85
  "jest": "^29.7.0",
77
86
  "node-fetch": "^3.3.0",
78
87
  "prettier": "^3.1.0",
79
- "rimraf": "^4.1.2",
88
+ "rimraf": "^6.0.1",
80
89
  "rollup": "^4.14.0",
81
90
  "ts-jest": "^29.0.3",
82
91
  "tslib": "^2.6.2",
@@ -1,53 +0,0 @@
1
- /* tslint:disable */
2
- /* eslint-disable */
3
- export class Engine {
4
- free(): void;
5
- constructor(namespace: string);
6
- snapshot(data: any): void;
7
- evaluate_boolean(request: any): any;
8
- evaluate_variant(request: any): any;
9
- evaluate_batch(request: any): any;
10
- list_flags(): any;
11
- }
12
-
13
- export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
14
-
15
- export interface InitOutput {
16
- readonly memory: WebAssembly.Memory;
17
- readonly __wbg_engine_free: (a: number, b: number) => void;
18
- readonly engine_new: (a: number, b: number) => number;
19
- readonly engine_snapshot: (a: number, b: any) => [number, number];
20
- readonly engine_evaluate_boolean: (a: number, b: any) => [number, number, number];
21
- readonly engine_evaluate_variant: (a: number, b: any) => [number, number, number];
22
- readonly engine_evaluate_batch: (a: number, b: any) => [number, number, number];
23
- readonly engine_list_flags: (a: number) => [number, number, number];
24
- readonly __wbindgen_malloc: (a: number, b: number) => number;
25
- readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
26
- readonly __wbindgen_exn_store: (a: number) => void;
27
- readonly __externref_table_alloc: () => number;
28
- readonly __wbindgen_export_4: WebAssembly.Table;
29
- readonly __wbindgen_free: (a: number, b: number, c: number) => void;
30
- readonly __externref_table_dealloc: (a: number) => void;
31
- readonly __wbindgen_start: () => void;
32
- }
33
-
34
- export type SyncInitInput = BufferSource | WebAssembly.Module;
35
- /**
36
- * Instantiates the given `module`, which can either be bytes or
37
- * a precompiled `WebAssembly.Module`.
38
- *
39
- * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
40
- *
41
- * @returns {InitOutput}
42
- */
43
- export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
44
-
45
- /**
46
- * If `module_or_path` is {RequestInfo} or {URL}, makes a request and
47
- * for everything else, calls `WebAssembly.instantiate` directly.
48
- *
49
- * @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
50
- *
51
- * @returns {Promise<InitOutput>}
52
- */
53
- export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
@@ -1,18 +0,0 @@
1
- /* tslint:disable */
2
- /* eslint-disable */
3
- export const memory: WebAssembly.Memory;
4
- export const __wbg_engine_free: (a: number, b: number) => void;
5
- export const engine_new: (a: number, b: number) => number;
6
- export const engine_snapshot: (a: number, b: any) => [number, number];
7
- export const engine_evaluate_boolean: (a: number, b: any) => [number, number, number];
8
- export const engine_evaluate_variant: (a: number, b: any) => [number, number, number];
9
- export const engine_evaluate_batch: (a: number, b: any) => [number, number, number];
10
- export const engine_list_flags: (a: number) => [number, number, number];
11
- export const __wbindgen_malloc: (a: number, b: number) => number;
12
- export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
13
- export const __wbindgen_exn_store: (a: number) => void;
14
- export const __externref_table_alloc: () => number;
15
- export const __wbindgen_export_4: WebAssembly.Table;
16
- export const __wbindgen_free: (a: number, b: number, c: number) => void;
17
- export const __externref_table_dealloc: (a: number) => void;
18
- export const __wbindgen_start: () => void;