@smplkit/sdk 1.1.9 → 1.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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ConfigRuntime
3
- } from "./chunk-RF6LYU4V.js";
3
+ } from "./chunk-2VYY5OMH.js";
4
4
 
5
5
  // src/config/client.ts
6
6
  import createClient from "openapi-fetch";
@@ -207,7 +207,7 @@ var Config = class {
207
207
  * @param options.timeout - Milliseconds to wait for the initial fetch.
208
208
  */
209
209
  async connect(environment, options) {
210
- const { ConfigRuntime: ConfigRuntime2 } = await import("./runtime-FT745HBO.js");
210
+ const { ConfigRuntime: ConfigRuntime2 } = await import("./runtime-MIIY5ZNG.js");
211
211
  const timeout = options?.timeout ?? 3e4;
212
212
  const chain = await this._buildChain(timeout);
213
213
  return new ConfigRuntime2({
@@ -217,7 +217,8 @@ var Config = class {
217
217
  chain,
218
218
  apiKey: this._client._apiKey,
219
219
  baseUrl: this._client._baseUrl,
220
- fetchChain: () => this._buildChain(timeout)
220
+ fetchChain: () => this._buildChain(timeout),
221
+ sharedWs: this._client._getSharedWs ? this._client._getSharedWs() : null
221
222
  });
222
223
  }
223
224
  /**
@@ -364,6 +365,8 @@ var ConfigClient = class {
364
365
  _baseUrl = BASE_URL;
365
366
  /** @internal */
366
367
  _http;
368
+ /** @internal — returns the shared WebSocket for real-time updates. */
369
+ _getSharedWs;
367
370
  /** @internal */
368
371
  constructor(apiKey, timeout) {
369
372
  this._apiKey = apiKey;
@@ -528,6 +531,1181 @@ var ConfigClient = class {
528
531
  }
529
532
  };
530
533
 
534
+ // src/flags/client.ts
535
+ import createClient2 from "openapi-fetch";
536
+
537
+ // src/auth.ts
538
+ function buildAuthHeader(apiKey) {
539
+ return `Bearer ${apiKey}`;
540
+ }
541
+
542
+ // src/transport.ts
543
+ var SDK_VERSION = "0.0.0";
544
+ var DEFAULT_TIMEOUT_MS = 3e4;
545
+ var Transport = class {
546
+ apiKey;
547
+ timeout;
548
+ constructor(options) {
549
+ this.apiKey = options.apiKey;
550
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
551
+ }
552
+ /**
553
+ * Send a GET request.
554
+ *
555
+ * @param url - Fully-qualified URL (e.g. `https://config.smplkit.com/api/v1/configs`).
556
+ * @param params - Optional query parameters.
557
+ * @returns Parsed JSON response body.
558
+ */
559
+ async get(url, params) {
560
+ return this.request("GET", url, void 0, params);
561
+ }
562
+ /**
563
+ * Send a POST request with a JSON body.
564
+ *
565
+ * @param url - Fully-qualified URL.
566
+ * @param body - JSON-serializable request body.
567
+ * @returns Parsed JSON response body.
568
+ */
569
+ async post(url, body) {
570
+ return this.request("POST", url, body);
571
+ }
572
+ /**
573
+ * Send a PUT request with a JSON body.
574
+ *
575
+ * @param url - Fully-qualified URL.
576
+ * @param body - JSON-serializable request body.
577
+ * @returns Parsed JSON response body.
578
+ */
579
+ async put(url, body) {
580
+ return this.request("PUT", url, body);
581
+ }
582
+ /**
583
+ * Send a DELETE request.
584
+ *
585
+ * @param url - Fully-qualified URL.
586
+ * @returns Parsed JSON response body (empty object for 204 responses).
587
+ */
588
+ async delete(url) {
589
+ return this.request("DELETE", url);
590
+ }
591
+ /**
592
+ * Core request method. Handles headers, timeouts, and error mapping.
593
+ */
594
+ async request(method, url, body, params) {
595
+ if (params) {
596
+ const searchParams = new URLSearchParams(params);
597
+ url += `?${searchParams.toString()}`;
598
+ }
599
+ const headers = {
600
+ Authorization: buildAuthHeader(this.apiKey),
601
+ "User-Agent": `smplkit-typescript-sdk/${SDK_VERSION}`,
602
+ Accept: "application/vnd.api+json"
603
+ };
604
+ if (body !== void 0) {
605
+ headers["Content-Type"] = "application/vnd.api+json";
606
+ }
607
+ const controller = new AbortController();
608
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
609
+ let response;
610
+ try {
611
+ response = await fetch(url, {
612
+ method,
613
+ headers,
614
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
615
+ signal: controller.signal
616
+ });
617
+ } catch (error) {
618
+ clearTimeout(timeoutId);
619
+ if (error instanceof DOMException && error.name === "AbortError") {
620
+ throw new SmplTimeoutError(`Request timed out after ${this.timeout}ms`);
621
+ }
622
+ if (error instanceof TypeError) {
623
+ throw new SmplConnectionError(`Network error: ${error.message}`);
624
+ }
625
+ throw new SmplConnectionError(
626
+ `Request failed: ${error instanceof Error ? error.message : String(error)}`
627
+ );
628
+ } finally {
629
+ clearTimeout(timeoutId);
630
+ }
631
+ if (response.status === 204) {
632
+ return {};
633
+ }
634
+ const responseText = await response.text();
635
+ if (!response.ok) {
636
+ this.throwForStatus(response.status, responseText);
637
+ }
638
+ try {
639
+ return JSON.parse(responseText);
640
+ } catch {
641
+ throw new SmplError(`Invalid JSON response: ${responseText}`, response.status, responseText);
642
+ }
643
+ }
644
+ /**
645
+ * Map HTTP error status codes to typed SDK exceptions.
646
+ *
647
+ * @throws {SmplNotFoundError} On 404.
648
+ * @throws {SmplConflictError} On 409.
649
+ * @throws {SmplValidationError} On 422.
650
+ * @throws {SmplError} On any other non-2xx status.
651
+ */
652
+ throwForStatus(status, body) {
653
+ switch (status) {
654
+ case 404:
655
+ throw new SmplNotFoundError(body, 404, body);
656
+ case 409:
657
+ throw new SmplConflictError(body, 409, body);
658
+ case 422:
659
+ throw new SmplValidationError(body, 422, body);
660
+ default:
661
+ throw new SmplError(`HTTP ${status}: ${body}`, status, body);
662
+ }
663
+ }
664
+ };
665
+
666
+ // src/flags/models.ts
667
+ var Flag = class {
668
+ /** UUID of the flag. */
669
+ id;
670
+ /** Unique key within the account. */
671
+ key;
672
+ /** Human-readable display name. */
673
+ name;
674
+ /** Value type: BOOLEAN, STRING, NUMERIC, or JSON. */
675
+ type;
676
+ /** Flag-level default value. */
677
+ default;
678
+ /** Closed set of possible values. */
679
+ values;
680
+ /** Optional description. */
681
+ description;
682
+ /** Per-environment configuration. */
683
+ environments;
684
+ /** When the flag was created. */
685
+ createdAt;
686
+ /** When the flag was last updated. */
687
+ updatedAt;
688
+ /** @internal */
689
+ _client;
690
+ /** @internal */
691
+ constructor(client, fields) {
692
+ this._client = client;
693
+ this.id = fields.id;
694
+ this.key = fields.key;
695
+ this.name = fields.name;
696
+ this.type = fields.type;
697
+ this.default = fields.default;
698
+ this.values = fields.values;
699
+ this.description = fields.description;
700
+ this.environments = fields.environments;
701
+ this.createdAt = fields.createdAt;
702
+ this.updatedAt = fields.updatedAt;
703
+ }
704
+ /**
705
+ * Update this flag's attributes on the server.
706
+ *
707
+ * Only provided fields are changed; others retain their current values.
708
+ */
709
+ async update(options) {
710
+ const updated = await this._client._updateFlag({
711
+ flag: this,
712
+ environments: options.environments,
713
+ values: options.values,
714
+ default: options.default,
715
+ description: options.description,
716
+ name: options.name
717
+ });
718
+ this._apply(updated);
719
+ }
720
+ /**
721
+ * Add a rule to a specific environment.
722
+ *
723
+ * The built rule must include an `environment` key (set via
724
+ * `Rule(...).environment("env_key")`). Re-fetches current state
725
+ * first to avoid stale data.
726
+ */
727
+ async addRule(builtRule) {
728
+ const envKey = builtRule.environment;
729
+ if (!envKey) {
730
+ throw new Error(
731
+ `Built rule must include 'environment' key. Use new Rule(...).environment("env_key").when(...).serve(...).build()`
732
+ );
733
+ }
734
+ const current = await this._client.get(this.id);
735
+ this._apply(current);
736
+ const envs = { ...this.environments };
737
+ const envData = { ...envs[envKey] ?? { enabled: true, rules: [] } };
738
+ const rules = [...envData.rules ?? []];
739
+ const { environment: _env, ...ruleCopy } = builtRule;
740
+ rules.push(ruleCopy);
741
+ envData.rules = rules;
742
+ envs[envKey] = envData;
743
+ const updated = await this._client._updateFlag({
744
+ flag: this,
745
+ environments: envs
746
+ });
747
+ this._apply(updated);
748
+ }
749
+ /** @internal */
750
+ _apply(other) {
751
+ this.id = other.id;
752
+ this.key = other.key;
753
+ this.name = other.name;
754
+ this.type = other.type;
755
+ this.default = other.default;
756
+ this.values = other.values;
757
+ this.description = other.description;
758
+ this.environments = other.environments;
759
+ this.createdAt = other.createdAt;
760
+ this.updatedAt = other.updatedAt;
761
+ }
762
+ toString() {
763
+ return `Flag(key=${this.key}, type=${this.type}, default=${this.default})`;
764
+ }
765
+ };
766
+ var ContextType = class {
767
+ /** UUID. */
768
+ id;
769
+ /** Unique key within the account. */
770
+ key;
771
+ /** Human-readable display name. */
772
+ name;
773
+ /** Known attributes. */
774
+ attributes;
775
+ constructor(fields) {
776
+ this.id = fields.id;
777
+ this.key = fields.key;
778
+ this.name = fields.name;
779
+ this.attributes = fields.attributes;
780
+ }
781
+ toString() {
782
+ return `ContextType(key=${this.key}, name=${this.name})`;
783
+ }
784
+ };
785
+
786
+ // src/flags/client.ts
787
+ import jsonLogic from "json-logic-js";
788
+ var FLAGS_BASE_URL = "https://flags.smplkit.com";
789
+ var APP_BASE_URL = "https://app.smplkit.com";
790
+ var CACHE_MAX_SIZE = 1e4;
791
+ var CONTEXT_REGISTRATION_LRU_SIZE = 1e4;
792
+ var CONTEXT_BATCH_FLUSH_SIZE = 100;
793
+ async function checkError2(response, context) {
794
+ const body = await response.text().catch(() => "");
795
+ switch (response.status) {
796
+ case 404:
797
+ throw new SmplNotFoundError(body || context, 404, body);
798
+ case 409:
799
+ throw new SmplConflictError(body || context, 409, body);
800
+ case 422:
801
+ throw new SmplValidationError(body || context, 422, body);
802
+ default:
803
+ throw new SmplError(`HTTP ${response.status}: ${body}`, response.status, body);
804
+ }
805
+ }
806
+ function wrapFetchError2(err) {
807
+ if (err instanceof SmplNotFoundError || err instanceof SmplConflictError || err instanceof SmplValidationError || err instanceof SmplError) {
808
+ throw err;
809
+ }
810
+ if (err instanceof TypeError) {
811
+ throw new SmplConnectionError(`Network error: ${err.message}`);
812
+ }
813
+ throw new SmplConnectionError(
814
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
815
+ );
816
+ }
817
+ function contextsToEvalDict(contexts) {
818
+ const result = {};
819
+ for (const ctx of contexts) {
820
+ result[ctx.type] = { key: ctx.key, ...ctx.attributes };
821
+ }
822
+ return result;
823
+ }
824
+ function sortedStringify(obj) {
825
+ if (obj === null || obj === void 0) return "null";
826
+ if (typeof obj !== "object") return JSON.stringify(obj);
827
+ if (Array.isArray(obj)) return "[" + obj.map(sortedStringify).join(",") + "]";
828
+ const keys = Object.keys(obj).sort();
829
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + sortedStringify(obj[k])).join(",") + "}";
830
+ }
831
+ function hashContext(evalDict) {
832
+ const serialized = sortedStringify(evalDict);
833
+ let hash = 0;
834
+ for (let i = 0; i < serialized.length; i++) {
835
+ const chr = serialized.charCodeAt(i);
836
+ hash = (hash << 5) - hash + chr | 0;
837
+ }
838
+ return hash.toString(36);
839
+ }
840
+ function evaluateFlag(flagDef, environment, evalDict) {
841
+ const flagDefault = flagDef.default;
842
+ const environments = flagDef.environments ?? {};
843
+ if (environment === null || !(environment in environments)) {
844
+ return flagDefault;
845
+ }
846
+ const envConfig = environments[environment];
847
+ const envDefault = envConfig.default;
848
+ const fallback = envDefault !== void 0 && envDefault !== null ? envDefault : flagDefault;
849
+ if (!envConfig.enabled) {
850
+ return fallback;
851
+ }
852
+ const rules = envConfig.rules ?? [];
853
+ for (const rule of rules) {
854
+ const logic = rule.logic;
855
+ if (!logic || Object.keys(logic).length === 0) {
856
+ continue;
857
+ }
858
+ try {
859
+ const result = jsonLogic.apply(logic, evalDict);
860
+ if (result) {
861
+ return rule.value;
862
+ }
863
+ } catch {
864
+ continue;
865
+ }
866
+ }
867
+ return fallback;
868
+ }
869
+ var FlagChangeEvent = class {
870
+ key;
871
+ source;
872
+ constructor(key, source) {
873
+ this.key = key;
874
+ this.source = source;
875
+ }
876
+ };
877
+ var ResolutionCache = class {
878
+ _maxSize;
879
+ _cache = /* @__PURE__ */ new Map();
880
+ cacheHits = 0;
881
+ cacheMisses = 0;
882
+ constructor(maxSize = CACHE_MAX_SIZE) {
883
+ this._maxSize = maxSize;
884
+ }
885
+ get(cacheKey) {
886
+ if (this._cache.has(cacheKey)) {
887
+ const value = this._cache.get(cacheKey);
888
+ this._cache.delete(cacheKey);
889
+ this._cache.set(cacheKey, value);
890
+ this.cacheHits++;
891
+ return [true, value];
892
+ }
893
+ this.cacheMisses++;
894
+ return [false, null];
895
+ }
896
+ put(cacheKey, value) {
897
+ if (this._cache.has(cacheKey)) {
898
+ this._cache.delete(cacheKey);
899
+ }
900
+ this._cache.set(cacheKey, value);
901
+ if (this._cache.size > this._maxSize) {
902
+ const firstKey = this._cache.keys().next().value;
903
+ if (firstKey !== void 0) {
904
+ this._cache.delete(firstKey);
905
+ }
906
+ }
907
+ }
908
+ clear() {
909
+ this._cache.clear();
910
+ }
911
+ };
912
+ var FlagStats = class {
913
+ cacheHits;
914
+ cacheMisses;
915
+ constructor(cacheHits, cacheMisses) {
916
+ this.cacheHits = cacheHits;
917
+ this.cacheMisses = cacheMisses;
918
+ }
919
+ };
920
+ var FlagHandleBase = class {
921
+ /** @internal */
922
+ _namespace;
923
+ /** @internal */
924
+ _key;
925
+ /** @internal */
926
+ _default;
927
+ /** @internal */
928
+ _listeners = [];
929
+ constructor(namespace, key, defaultValue) {
930
+ this._namespace = namespace;
931
+ this._key = key;
932
+ this._default = defaultValue;
933
+ }
934
+ get key() {
935
+ return this._key;
936
+ }
937
+ get default() {
938
+ return this._default;
939
+ }
940
+ /* v8 ignore next 3 — overridden by all exported subclasses */
941
+ get(options) {
942
+ return this._namespace._evaluateHandle(this._key, this._default, options?.context ?? null);
943
+ }
944
+ /** Register a flag-specific change listener. Works as a decorator. */
945
+ onChange(callback) {
946
+ this._listeners.push(callback);
947
+ return callback;
948
+ }
949
+ };
950
+ var BoolFlagHandle = class extends FlagHandleBase {
951
+ get(options) {
952
+ const value = this._namespace._evaluateHandle(
953
+ this._key,
954
+ this._default,
955
+ options?.context ?? null
956
+ );
957
+ if (typeof value === "boolean") {
958
+ return value;
959
+ }
960
+ return this._default;
961
+ }
962
+ };
963
+ var StringFlagHandle = class extends FlagHandleBase {
964
+ get(options) {
965
+ const value = this._namespace._evaluateHandle(
966
+ this._key,
967
+ this._default,
968
+ options?.context ?? null
969
+ );
970
+ if (typeof value === "string") {
971
+ return value;
972
+ }
973
+ return this._default;
974
+ }
975
+ };
976
+ var NumberFlagHandle = class extends FlagHandleBase {
977
+ get(options) {
978
+ const value = this._namespace._evaluateHandle(
979
+ this._key,
980
+ this._default,
981
+ options?.context ?? null
982
+ );
983
+ if (typeof value === "number") {
984
+ return value;
985
+ }
986
+ return this._default;
987
+ }
988
+ };
989
+ var JsonFlagHandle = class extends FlagHandleBase {
990
+ get(options) {
991
+ const value = this._namespace._evaluateHandle(
992
+ this._key,
993
+ this._default,
994
+ options?.context ?? null
995
+ );
996
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
997
+ return value;
998
+ }
999
+ return this._default;
1000
+ }
1001
+ };
1002
+ var ContextRegistrationBuffer = class {
1003
+ _seen = /* @__PURE__ */ new Map();
1004
+ _pending = [];
1005
+ observe(contexts) {
1006
+ for (const ctx of contexts) {
1007
+ const cacheKey = `${ctx.type}:${ctx.key}`;
1008
+ if (!this._seen.has(cacheKey)) {
1009
+ if (this._seen.size >= CONTEXT_REGISTRATION_LRU_SIZE) {
1010
+ const firstKey = this._seen.keys().next().value;
1011
+ if (firstKey !== void 0) {
1012
+ this._seen.delete(firstKey);
1013
+ }
1014
+ }
1015
+ this._seen.set(cacheKey, ctx.attributes);
1016
+ this._pending.push({
1017
+ id: `${ctx.type}:${ctx.key}`,
1018
+ name: ctx.name ?? ctx.key,
1019
+ attributes: { ...ctx.attributes }
1020
+ });
1021
+ }
1022
+ }
1023
+ }
1024
+ drain() {
1025
+ const batch = this._pending;
1026
+ this._pending = [];
1027
+ return batch;
1028
+ }
1029
+ get pendingCount() {
1030
+ return this._pending.length;
1031
+ }
1032
+ };
1033
+ var FlagsClient = class {
1034
+ /** @internal */
1035
+ _apiKey;
1036
+ /** @internal */
1037
+ _baseUrl = FLAGS_BASE_URL;
1038
+ /** @internal */
1039
+ _http;
1040
+ /** @internal */
1041
+ _transport;
1042
+ // Runtime state
1043
+ _environment = null;
1044
+ _flagStore = {};
1045
+ _connected = false;
1046
+ _cache = new ResolutionCache();
1047
+ _contextProvider = null;
1048
+ _contextBuffer = new ContextRegistrationBuffer();
1049
+ _handles = {};
1050
+ _globalListeners = [];
1051
+ // Shared WebSocket (set during connect)
1052
+ _wsManager = null;
1053
+ _ensureWs;
1054
+ /** @internal */
1055
+ constructor(apiKey, ensureWs, timeout) {
1056
+ this._apiKey = apiKey;
1057
+ this._ensureWs = ensureWs;
1058
+ const ms = timeout ?? 3e4;
1059
+ this._http = createClient2({
1060
+ baseUrl: FLAGS_BASE_URL,
1061
+ headers: {
1062
+ Authorization: `Bearer ${apiKey}`,
1063
+ Accept: "application/json"
1064
+ },
1065
+ fetch: async (request) => {
1066
+ const controller = new AbortController();
1067
+ const timer = setTimeout(() => controller.abort(), ms);
1068
+ try {
1069
+ return await fetch(new Request(request, { signal: controller.signal }));
1070
+ } catch (err) {
1071
+ if (err instanceof DOMException && err.name === "AbortError") {
1072
+ throw new SmplTimeoutError(`Request timed out after ${ms}ms`);
1073
+ }
1074
+ throw err;
1075
+ } finally {
1076
+ clearTimeout(timer);
1077
+ }
1078
+ }
1079
+ });
1080
+ this._transport = new Transport({ apiKey, timeout: ms });
1081
+ }
1082
+ // ------------------------------------------------------------------
1083
+ // Management methods
1084
+ // ------------------------------------------------------------------
1085
+ /** Create a flag. */
1086
+ async create(key, options) {
1087
+ let values = options.values;
1088
+ if (values === void 0 && options.type === "BOOLEAN") {
1089
+ values = [
1090
+ { name: "True", value: true },
1091
+ { name: "False", value: false }
1092
+ ];
1093
+ }
1094
+ const body = {
1095
+ data: {
1096
+ type: "flag",
1097
+ attributes: {
1098
+ key,
1099
+ name: options.name,
1100
+ description: options.description ?? "",
1101
+ type: options.type,
1102
+ default: options.default,
1103
+ values: values ?? []
1104
+ }
1105
+ }
1106
+ };
1107
+ let data;
1108
+ try {
1109
+ const result = await this._http.POST("/api/v1/flags", { body });
1110
+ if (result.error !== void 0) await checkError2(result.response, "Failed to create flag");
1111
+ data = result.data;
1112
+ } catch (err) {
1113
+ wrapFetchError2(err);
1114
+ }
1115
+ if (!data || !data.data) throw new SmplValidationError("Failed to create flag");
1116
+ return this._resourceToModel(data.data);
1117
+ }
1118
+ /** Fetch a flag by UUID. */
1119
+ async get(flagId) {
1120
+ let data;
1121
+ try {
1122
+ const result = await this._http.GET("/api/v1/flags/{id}", {
1123
+ params: { path: { id: flagId } }
1124
+ });
1125
+ if (result.error !== void 0) await checkError2(result.response, `Flag ${flagId} not found`);
1126
+ data = result.data;
1127
+ } catch (err) {
1128
+ wrapFetchError2(err);
1129
+ }
1130
+ if (!data || !data.data) throw new SmplNotFoundError(`Flag ${flagId} not found`);
1131
+ return this._resourceToModel(data.data);
1132
+ }
1133
+ /** List all flags. */
1134
+ async list() {
1135
+ let data;
1136
+ try {
1137
+ const result = await this._http.GET("/api/v1/flags", {});
1138
+ if (result.error !== void 0) await checkError2(result.response, "Failed to list flags");
1139
+ data = result.data;
1140
+ } catch (err) {
1141
+ wrapFetchError2(err);
1142
+ }
1143
+ if (!data) return [];
1144
+ return data.data.map((r) => this._resourceToModel(r));
1145
+ }
1146
+ /** Delete a flag by UUID. */
1147
+ async delete(flagId) {
1148
+ try {
1149
+ const result = await this._http.DELETE("/api/v1/flags/{id}", {
1150
+ params: { path: { id: flagId } }
1151
+ });
1152
+ if (result.error !== void 0 && result.response.status !== 204)
1153
+ await checkError2(result.response, `Failed to delete flag ${flagId}`);
1154
+ } catch (err) {
1155
+ wrapFetchError2(err);
1156
+ }
1157
+ }
1158
+ /**
1159
+ * Internal: PUT a full flag update.
1160
+ * Called by {@link Flag} instance methods.
1161
+ * @internal
1162
+ */
1163
+ async _updateFlag(options) {
1164
+ const { flag } = options;
1165
+ const body = {
1166
+ data: {
1167
+ type: "flag",
1168
+ attributes: {
1169
+ key: flag.key,
1170
+ name: options.name !== void 0 ? options.name : flag.name,
1171
+ type: flag.type,
1172
+ default: options.default !== void 0 ? options.default : flag.default,
1173
+ values: options.values !== void 0 ? options.values : flag.values,
1174
+ description: options.description !== void 0 ? options.description : flag.description ?? "",
1175
+ ...options.environments !== void 0 ? { environments: options.environments } : flag.environments && Object.keys(flag.environments).length > 0 ? { environments: flag.environments } : {}
1176
+ }
1177
+ }
1178
+ };
1179
+ let data;
1180
+ try {
1181
+ const result = await this._http.PUT("/api/v1/flags/{id}", {
1182
+ params: { path: { id: flag.id } },
1183
+ body
1184
+ });
1185
+ if (result.error !== void 0)
1186
+ await checkError2(result.response, `Failed to update flag ${flag.id}`);
1187
+ data = result.data;
1188
+ } catch (err) {
1189
+ wrapFetchError2(err);
1190
+ }
1191
+ if (!data || !data.data) throw new SmplValidationError(`Failed to update flag ${flag.id}`);
1192
+ return this._resourceToModel(data.data);
1193
+ }
1194
+ // ------------------------------------------------------------------
1195
+ // Context type management (direct HTTP — not in generated spec)
1196
+ // ------------------------------------------------------------------
1197
+ /** Create a context type. */
1198
+ async createContextType(key, options) {
1199
+ const resp = await this._transport.post(`${APP_BASE_URL}/api/v1/context_types`, {
1200
+ data: { type: "context_type", attributes: { key, name: options.name } }
1201
+ });
1202
+ const data = resp.data ?? {};
1203
+ return this._parseContextType(data);
1204
+ }
1205
+ /** Update a context type (merge attributes). */
1206
+ async updateContextType(ctId, options) {
1207
+ const resp = await this._transport.put(`${APP_BASE_URL}/api/v1/context_types/${ctId}`, {
1208
+ data: { type: "context_type", attributes: { attributes: options.attributes } }
1209
+ });
1210
+ const data = resp.data ?? {};
1211
+ return this._parseContextType(data);
1212
+ }
1213
+ /** List all context types. */
1214
+ async listContextTypes() {
1215
+ const resp = await this._transport.get(`${APP_BASE_URL}/api/v1/context_types`);
1216
+ const items = resp.data ?? [];
1217
+ return items.map((item) => this._parseContextType(item));
1218
+ }
1219
+ /** Delete a context type. */
1220
+ async deleteContextType(ctId) {
1221
+ await this._transport.delete(`${APP_BASE_URL}/api/v1/context_types/${ctId}`);
1222
+ }
1223
+ /** List context instances filtered by context type key. */
1224
+ async listContexts(options) {
1225
+ const resp = await this._transport.get(`${APP_BASE_URL}/api/v1/contexts`, {
1226
+ "filter[context_type]": options.contextTypeKey
1227
+ });
1228
+ return resp.data ?? [];
1229
+ }
1230
+ // ------------------------------------------------------------------
1231
+ // Runtime: typed flag handles
1232
+ // ------------------------------------------------------------------
1233
+ /** Declare a boolean flag handle. */
1234
+ boolFlag(key, defaultValue) {
1235
+ const handle = new BoolFlagHandle(this, key, defaultValue);
1236
+ this._handles[key] = handle;
1237
+ return handle;
1238
+ }
1239
+ /** Declare a string flag handle. */
1240
+ stringFlag(key, defaultValue) {
1241
+ const handle = new StringFlagHandle(this, key, defaultValue);
1242
+ this._handles[key] = handle;
1243
+ return handle;
1244
+ }
1245
+ /** Declare a numeric flag handle. */
1246
+ numberFlag(key, defaultValue) {
1247
+ const handle = new NumberFlagHandle(this, key, defaultValue);
1248
+ this._handles[key] = handle;
1249
+ return handle;
1250
+ }
1251
+ /** Declare a JSON flag handle. */
1252
+ jsonFlag(key, defaultValue) {
1253
+ const handle = new JsonFlagHandle(this, key, defaultValue);
1254
+ this._handles[key] = handle;
1255
+ return handle;
1256
+ }
1257
+ // ------------------------------------------------------------------
1258
+ // Runtime: context provider
1259
+ // ------------------------------------------------------------------
1260
+ /**
1261
+ * Register a context provider function.
1262
+ *
1263
+ * Called on every `handle.get()` to supply the current evaluation
1264
+ * context. Can also be used as a decorator:
1265
+ *
1266
+ * ```typescript
1267
+ * client.flags.setContextProvider(() => [
1268
+ * new Context("user", userId, { plan: userPlan }),
1269
+ * ]);
1270
+ * ```
1271
+ */
1272
+ setContextProvider(fn) {
1273
+ this._contextProvider = fn;
1274
+ }
1275
+ /**
1276
+ * Register a context provider — decorator-style alias.
1277
+ *
1278
+ * ```typescript
1279
+ * const provider = client.flags.contextProvider(() => [...]);
1280
+ * ```
1281
+ */
1282
+ contextProvider(fn) {
1283
+ this._contextProvider = fn;
1284
+ return fn;
1285
+ }
1286
+ // ------------------------------------------------------------------
1287
+ // Runtime: connect / disconnect / refresh
1288
+ // ------------------------------------------------------------------
1289
+ /**
1290
+ * Connect to an environment: fetch flag definitions, register on
1291
+ * shared WebSocket, enable local evaluation.
1292
+ */
1293
+ async connect(environment, _options) {
1294
+ this._environment = environment;
1295
+ await this._fetchAllFlags();
1296
+ this._connected = true;
1297
+ this._cache.clear();
1298
+ this._wsManager = this._ensureWs();
1299
+ this._wsManager.on("flag_changed", this._handleFlagChanged);
1300
+ this._wsManager.on("flag_deleted", this._handleFlagDeleted);
1301
+ }
1302
+ /** Disconnect: unregister from WebSocket, flush contexts, clear state. */
1303
+ async disconnect() {
1304
+ if (this._wsManager !== null) {
1305
+ this._wsManager.off("flag_changed", this._handleFlagChanged);
1306
+ this._wsManager.off("flag_deleted", this._handleFlagDeleted);
1307
+ this._wsManager = null;
1308
+ }
1309
+ await this._flushContexts();
1310
+ this._flagStore = {};
1311
+ this._cache.clear();
1312
+ this._connected = false;
1313
+ this._environment = null;
1314
+ }
1315
+ /** Re-fetch all flag definitions and clear cache. */
1316
+ async refresh() {
1317
+ await this._fetchAllFlags();
1318
+ this._cache.clear();
1319
+ this._fireChangeListenersAll("manual");
1320
+ }
1321
+ /** Return the current WebSocket connection status. */
1322
+ connectionStatus() {
1323
+ if (this._wsManager !== null) {
1324
+ return this._wsManager.connectionStatus;
1325
+ }
1326
+ return "disconnected";
1327
+ }
1328
+ /** Return cache statistics. */
1329
+ stats() {
1330
+ return new FlagStats(this._cache.cacheHits, this._cache.cacheMisses);
1331
+ }
1332
+ // ------------------------------------------------------------------
1333
+ // Runtime: change listeners
1334
+ // ------------------------------------------------------------------
1335
+ /** Register a global change listener that fires for any flag change. */
1336
+ onChangeAny(callback) {
1337
+ this._globalListeners.push(callback);
1338
+ return callback;
1339
+ }
1340
+ /**
1341
+ * Register a global change listener — decorator-style alias.
1342
+ *
1343
+ * ```typescript
1344
+ * const listener = client.flags.onChange((event) => { ... });
1345
+ * ```
1346
+ */
1347
+ onChange(callback) {
1348
+ return this.onChangeAny(callback);
1349
+ }
1350
+ // ------------------------------------------------------------------
1351
+ // Runtime: context registration
1352
+ // ------------------------------------------------------------------
1353
+ /**
1354
+ * Explicitly register context(s) for background batch registration.
1355
+ *
1356
+ * Accepts a single Context or an array. Fire-and-forget — never
1357
+ * blocks. Works before `connect()` is called.
1358
+ */
1359
+ register(context) {
1360
+ if (Array.isArray(context)) {
1361
+ this._contextBuffer.observe(context);
1362
+ } else {
1363
+ this._contextBuffer.observe([context]);
1364
+ }
1365
+ }
1366
+ /** Flush pending context registrations to the server. */
1367
+ async flushContexts() {
1368
+ await this._flushContexts();
1369
+ }
1370
+ // ------------------------------------------------------------------
1371
+ // Runtime: Tier 1 evaluate
1372
+ // ------------------------------------------------------------------
1373
+ /**
1374
+ * Tier 1 explicit evaluation — stateless, no provider or cache.
1375
+ *
1376
+ * Useful for scripts, one-off jobs, and infrastructure code.
1377
+ */
1378
+ async evaluate(key, options) {
1379
+ const evalDict = contextsToEvalDict(options.context);
1380
+ let flagDef = null;
1381
+ if (this._connected && key in this._flagStore) {
1382
+ flagDef = this._flagStore[key];
1383
+ } else {
1384
+ const flags = await this._fetchFlagsList();
1385
+ for (const f of flags) {
1386
+ if (f.key === key) {
1387
+ flagDef = f;
1388
+ break;
1389
+ }
1390
+ }
1391
+ }
1392
+ if (flagDef === null) {
1393
+ return null;
1394
+ }
1395
+ return evaluateFlag(flagDef, options.environment, evalDict);
1396
+ }
1397
+ // ------------------------------------------------------------------
1398
+ // Internal: evaluation
1399
+ // ------------------------------------------------------------------
1400
+ /** @internal */
1401
+ _evaluateHandle(key, defaultValue, context) {
1402
+ if (!this._connected) {
1403
+ return defaultValue;
1404
+ }
1405
+ let evalDict;
1406
+ if (context !== null) {
1407
+ evalDict = contextsToEvalDict(context);
1408
+ } else if (this._contextProvider !== null) {
1409
+ const contexts = this._contextProvider();
1410
+ evalDict = contextsToEvalDict(contexts);
1411
+ this._contextBuffer.observe(contexts);
1412
+ if (this._contextBuffer.pendingCount >= CONTEXT_BATCH_FLUSH_SIZE) {
1413
+ void this._flushContexts();
1414
+ }
1415
+ } else {
1416
+ evalDict = {};
1417
+ }
1418
+ const ctxHash = hashContext(evalDict);
1419
+ const cacheKey = `${key}:${ctxHash}`;
1420
+ const [hit, cachedValue] = this._cache.get(cacheKey);
1421
+ if (hit) {
1422
+ return cachedValue;
1423
+ }
1424
+ const flagDef = this._flagStore[key];
1425
+ if (flagDef === void 0) {
1426
+ this._cache.put(cacheKey, defaultValue);
1427
+ return defaultValue;
1428
+ }
1429
+ let value = evaluateFlag(flagDef, this._environment, evalDict);
1430
+ if (value === null || value === void 0) {
1431
+ value = defaultValue;
1432
+ }
1433
+ this._cache.put(cacheKey, value);
1434
+ return value;
1435
+ }
1436
+ // ------------------------------------------------------------------
1437
+ // Internal: event handlers (called by SharedWebSocket)
1438
+ // ------------------------------------------------------------------
1439
+ _handleFlagChanged = (data) => {
1440
+ const flagKey = data.key;
1441
+ void this._fetchAllFlags().then(() => {
1442
+ this._cache.clear();
1443
+ this._fireChangeListeners(flagKey ?? null, "websocket");
1444
+ });
1445
+ };
1446
+ _handleFlagDeleted = (data) => {
1447
+ const flagKey = data.key;
1448
+ void this._fetchAllFlags().then(() => {
1449
+ this._cache.clear();
1450
+ this._fireChangeListeners(flagKey ?? null, "websocket");
1451
+ });
1452
+ };
1453
+ // ------------------------------------------------------------------
1454
+ // Internal: flag store
1455
+ // ------------------------------------------------------------------
1456
+ async _fetchAllFlags() {
1457
+ const flags = await this._fetchFlagsList();
1458
+ const store = {};
1459
+ for (const f of flags) {
1460
+ store[f.key] = f;
1461
+ }
1462
+ this._flagStore = store;
1463
+ }
1464
+ async _fetchFlagsList() {
1465
+ let data;
1466
+ try {
1467
+ const result = await this._http.GET("/api/v1/flags", {});
1468
+ if (result.error !== void 0) await checkError2(result.response, "Failed to list flags");
1469
+ data = result.data;
1470
+ } catch (err) {
1471
+ wrapFetchError2(err);
1472
+ }
1473
+ if (!data) return [];
1474
+ return data.data.map((r) => this._resourceToPlainDict(r));
1475
+ }
1476
+ // ------------------------------------------------------------------
1477
+ // Internal: change listeners
1478
+ // ------------------------------------------------------------------
1479
+ _fireChangeListeners(flagKey, source) {
1480
+ if (flagKey) {
1481
+ const event = new FlagChangeEvent(flagKey, source);
1482
+ for (const cb of this._globalListeners) {
1483
+ try {
1484
+ cb(event);
1485
+ } catch {
1486
+ }
1487
+ }
1488
+ const handle = this._handles[flagKey];
1489
+ if (handle) {
1490
+ for (const cb of handle._listeners) {
1491
+ try {
1492
+ cb(event);
1493
+ } catch {
1494
+ }
1495
+ }
1496
+ }
1497
+ }
1498
+ }
1499
+ _fireChangeListenersAll(source) {
1500
+ for (const flagKey of Object.keys(this._flagStore)) {
1501
+ this._fireChangeListeners(flagKey, source);
1502
+ }
1503
+ }
1504
+ // ------------------------------------------------------------------
1505
+ // Internal: context flush
1506
+ // ------------------------------------------------------------------
1507
+ async _flushContexts() {
1508
+ const batch = this._contextBuffer.drain();
1509
+ if (batch.length === 0) return;
1510
+ try {
1511
+ await this._transport.put(`${APP_BASE_URL}/api/v1/contexts/bulk`, {
1512
+ contexts: batch
1513
+ });
1514
+ } catch {
1515
+ }
1516
+ }
1517
+ // ------------------------------------------------------------------
1518
+ // Internal: model conversion
1519
+ // ------------------------------------------------------------------
1520
+ _resourceToModel(resource) {
1521
+ const attrs = resource.attributes;
1522
+ return new Flag(this, {
1523
+ id: resource.id ?? "",
1524
+ key: attrs.key,
1525
+ name: attrs.name,
1526
+ type: attrs.type,
1527
+ default: attrs.default,
1528
+ values: (attrs.values ?? []).map((v) => ({ name: v.name, value: v.value })),
1529
+ description: attrs.description ?? null,
1530
+ environments: attrs.environments ?? {},
1531
+ createdAt: attrs.created_at ?? null,
1532
+ updatedAt: attrs.updated_at ?? null
1533
+ });
1534
+ }
1535
+ _resourceToPlainDict(resource) {
1536
+ const attrs = resource.attributes;
1537
+ return {
1538
+ key: attrs.key,
1539
+ name: attrs.name,
1540
+ type: attrs.type,
1541
+ default: attrs.default,
1542
+ values: (attrs.values ?? []).map((v) => ({ name: v.name, value: v.value })),
1543
+ description: attrs.description ?? null,
1544
+ environments: attrs.environments ?? {}
1545
+ };
1546
+ }
1547
+ _parseContextType(data) {
1548
+ const attrs = data.attributes ?? {};
1549
+ return new ContextType({
1550
+ id: data.id ?? "",
1551
+ key: attrs.key ?? "",
1552
+ name: attrs.name ?? "",
1553
+ attributes: attrs.attributes ?? {}
1554
+ });
1555
+ }
1556
+ };
1557
+
1558
+ // src/ws.ts
1559
+ import WebSocket from "ws";
1560
+ var BACKOFF_MS = [1e3, 2e3, 4e3, 8e3, 16e3, 32e3, 6e4];
1561
+ var SharedWebSocket = class {
1562
+ _appBaseUrl;
1563
+ _apiKey;
1564
+ _listeners = /* @__PURE__ */ new Map();
1565
+ _connectionStatus = "disconnected";
1566
+ _closed = false;
1567
+ _ws = null;
1568
+ _reconnectTimer = null;
1569
+ _backoffIndex = 0;
1570
+ constructor(appBaseUrl, apiKey) {
1571
+ this._appBaseUrl = appBaseUrl;
1572
+ this._apiKey = apiKey;
1573
+ }
1574
+ // ------------------------------------------------------------------
1575
+ // Listener registration
1576
+ // ------------------------------------------------------------------
1577
+ /** Register a listener for a specific event type. */
1578
+ on(eventName, callback) {
1579
+ if (!this._listeners.has(eventName)) {
1580
+ this._listeners.set(eventName, []);
1581
+ }
1582
+ this._listeners.get(eventName).push(callback);
1583
+ }
1584
+ /** Unregister a listener for a specific event type. */
1585
+ off(eventName, callback) {
1586
+ const list = this._listeners.get(eventName);
1587
+ if (list) {
1588
+ const idx = list.indexOf(callback);
1589
+ if (idx !== -1) {
1590
+ list.splice(idx, 1);
1591
+ }
1592
+ }
1593
+ }
1594
+ _dispatch(eventName, data) {
1595
+ const callbacks = this._listeners.get(eventName);
1596
+ if (callbacks) {
1597
+ for (const cb of [...callbacks]) {
1598
+ try {
1599
+ cb(data);
1600
+ } catch {
1601
+ }
1602
+ }
1603
+ }
1604
+ }
1605
+ // ------------------------------------------------------------------
1606
+ // Connection status
1607
+ // ------------------------------------------------------------------
1608
+ get connectionStatus() {
1609
+ return this._connectionStatus;
1610
+ }
1611
+ // ------------------------------------------------------------------
1612
+ // Lifecycle
1613
+ // ------------------------------------------------------------------
1614
+ /** Start the WebSocket connection. */
1615
+ start() {
1616
+ this._closed = false;
1617
+ this._connect();
1618
+ }
1619
+ /** Stop the WebSocket connection. */
1620
+ stop() {
1621
+ this._closed = true;
1622
+ this._connectionStatus = "disconnected";
1623
+ if (this._reconnectTimer !== null) {
1624
+ clearTimeout(this._reconnectTimer);
1625
+ this._reconnectTimer = null;
1626
+ }
1627
+ if (this._ws !== null) {
1628
+ this._ws.close();
1629
+ this._ws = null;
1630
+ }
1631
+ }
1632
+ // ------------------------------------------------------------------
1633
+ // Connection internals
1634
+ // ------------------------------------------------------------------
1635
+ _buildWsUrl() {
1636
+ let url = this._appBaseUrl;
1637
+ if (url.startsWith("https://")) {
1638
+ url = "wss://" + url.slice("https://".length);
1639
+ } else if (url.startsWith("http://")) {
1640
+ url = "ws://" + url.slice("http://".length);
1641
+ } else {
1642
+ url = "wss://" + url;
1643
+ }
1644
+ url = url.replace(/\/$/, "");
1645
+ return `${url}/api/ws/v1/events?api_key=${this._apiKey}`;
1646
+ }
1647
+ _connect() {
1648
+ if (this._closed) return;
1649
+ this._connectionStatus = "connecting";
1650
+ const wsUrl = this._buildWsUrl();
1651
+ try {
1652
+ const ws = new WebSocket(wsUrl);
1653
+ this._ws = ws;
1654
+ ws.on("open", () => {
1655
+ if (this._closed) {
1656
+ ws.close();
1657
+ return;
1658
+ }
1659
+ });
1660
+ ws.on("message", (data) => {
1661
+ try {
1662
+ const raw = String(data);
1663
+ if (raw === "ping") {
1664
+ ws.send("pong");
1665
+ return;
1666
+ }
1667
+ const msg = JSON.parse(raw);
1668
+ if (msg.type === "connected") {
1669
+ this._backoffIndex = 0;
1670
+ this._connectionStatus = "connected";
1671
+ return;
1672
+ }
1673
+ if (msg.type === "error") {
1674
+ return;
1675
+ }
1676
+ const eventName = msg.event;
1677
+ if (eventName) {
1678
+ this._dispatch(eventName, msg);
1679
+ }
1680
+ } catch {
1681
+ }
1682
+ });
1683
+ ws.on("close", () => {
1684
+ if (!this._closed) {
1685
+ this._connectionStatus = "disconnected";
1686
+ this._scheduleReconnect();
1687
+ }
1688
+ });
1689
+ ws.on("error", () => {
1690
+ });
1691
+ } catch {
1692
+ if (!this._closed) {
1693
+ this._scheduleReconnect();
1694
+ }
1695
+ }
1696
+ }
1697
+ _scheduleReconnect() {
1698
+ if (this._closed) return;
1699
+ const delay = BACKOFF_MS[Math.min(this._backoffIndex, BACKOFF_MS.length - 1)];
1700
+ this._backoffIndex++;
1701
+ this._connectionStatus = "connecting";
1702
+ this._reconnectTimer = setTimeout(() => {
1703
+ this._reconnectTimer = null;
1704
+ this._connect();
1705
+ }, delay);
1706
+ }
1707
+ };
1708
+
531
1709
  // src/resolve.ts
532
1710
  import { readFileSync } from "fs";
533
1711
  import { homedir } from "os";
@@ -562,24 +1740,124 @@ function resolveApiKey(explicit) {
562
1740
  }
563
1741
 
564
1742
  // src/client.ts
1743
+ var APP_BASE_URL2 = "https://app.smplkit.com";
565
1744
  var SmplClient = class {
566
1745
  /** Client for config management-plane operations. */
567
1746
  config;
1747
+ /** Client for flags management and runtime operations. */
1748
+ flags;
1749
+ _wsManager = null;
1750
+ _apiKey;
568
1751
  constructor(options = {}) {
569
1752
  const apiKey = resolveApiKey(options.apiKey);
1753
+ this._apiKey = apiKey;
570
1754
  this.config = new ConfigClient(apiKey, options.timeout);
1755
+ this.flags = new FlagsClient(apiKey, () => this._ensureWs(), options.timeout);
1756
+ this.config._getSharedWs = () => this._ensureWs();
1757
+ }
1758
+ /** Lazily create and start the shared WebSocket. @internal */
1759
+ _ensureWs() {
1760
+ if (this._wsManager === null) {
1761
+ this._wsManager = new SharedWebSocket(APP_BASE_URL2, this._apiKey);
1762
+ this._wsManager.start();
1763
+ }
1764
+ return this._wsManager;
1765
+ }
1766
+ /** Close the shared WebSocket and release resources. */
1767
+ close() {
1768
+ if (this._wsManager !== null) {
1769
+ this._wsManager.stop();
1770
+ this._wsManager = null;
1771
+ }
1772
+ }
1773
+ };
1774
+
1775
+ // src/flags/types.ts
1776
+ var Context = class {
1777
+ type;
1778
+ key;
1779
+ name;
1780
+ attributes;
1781
+ constructor(type, key, attributes, options) {
1782
+ this.type = type;
1783
+ this.key = key;
1784
+ this.name = options?.name ?? null;
1785
+ this.attributes = { ...attributes ?? {} };
1786
+ }
1787
+ toString() {
1788
+ return `Context(type=${this.type}, key=${this.key}, name=${this.name})`;
1789
+ }
1790
+ };
1791
+ var Rule = class {
1792
+ _description;
1793
+ _conditions = [];
1794
+ _value = null;
1795
+ _environment = null;
1796
+ constructor(description) {
1797
+ this._description = description;
1798
+ }
1799
+ /** Tag this rule with an environment key (used by `addRule`). */
1800
+ environment(envKey) {
1801
+ this._environment = envKey;
1802
+ return this;
1803
+ }
1804
+ /** Add a condition. Multiple calls are AND'd. */
1805
+ when(variable, op, value) {
1806
+ if (op === "contains") {
1807
+ this._conditions.push({ in: [value, { var: variable }] });
1808
+ } else {
1809
+ this._conditions.push({ [op]: [{ var: variable }, value] });
1810
+ }
1811
+ return this;
1812
+ }
1813
+ /** Set the value returned when this rule matches. */
1814
+ serve(value) {
1815
+ this._value = value;
1816
+ return this;
1817
+ }
1818
+ /** Finalize and return the rule as a plain object. */
1819
+ build() {
1820
+ let logic;
1821
+ if (this._conditions.length === 1) {
1822
+ logic = this._conditions[0];
1823
+ } else if (this._conditions.length > 1) {
1824
+ logic = { and: this._conditions };
1825
+ } else {
1826
+ logic = {};
1827
+ }
1828
+ const result = {
1829
+ description: this._description,
1830
+ logic,
1831
+ value: this._value
1832
+ };
1833
+ if (this._environment !== null) {
1834
+ result.environment = this._environment;
1835
+ }
1836
+ return result;
571
1837
  }
572
1838
  };
573
1839
  export {
1840
+ BoolFlagHandle,
574
1841
  Config,
575
1842
  ConfigClient,
576
1843
  ConfigRuntime,
1844
+ Context,
1845
+ ContextType,
1846
+ Flag,
1847
+ FlagChangeEvent,
1848
+ FlagStats,
1849
+ FlagsClient,
1850
+ JsonFlagHandle,
1851
+ NumberFlagHandle,
1852
+ Rule,
1853
+ SharedWebSocket,
577
1854
  SmplClient,
578
1855
  SmplConflictError,
579
1856
  SmplConnectionError,
580
1857
  SmplError,
581
1858
  SmplNotFoundError,
582
1859
  SmplTimeoutError,
583
- SmplValidationError
1860
+ SmplValidationError,
1861
+ StringFlagHandle
584
1862
  };
585
1863
  //# sourceMappingURL=index.js.map