@schematichq/schematic-js 1.0.2 → 1.1.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.
@@ -29,11 +29,12 @@ var require_browser_polyfill = __commonJS({
29
29
  "node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
30
30
  (function(self2) {
31
31
  var irrelevant = function(exports2) {
32
- var global = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || typeof global !== "undefined" && global;
32
+ var g = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || // eslint-disable-next-line no-undef
33
+ typeof global !== "undefined" && global || {};
33
34
  var support = {
34
- searchParams: "URLSearchParams" in global,
35
- iterable: "Symbol" in global && "iterator" in Symbol,
36
- blob: "FileReader" in global && "Blob" in global && function() {
35
+ searchParams: "URLSearchParams" in g,
36
+ iterable: "Symbol" in g && "iterator" in Symbol,
37
+ blob: "FileReader" in g && "Blob" in g && function() {
37
38
  try {
38
39
  new Blob();
39
40
  return true;
@@ -41,8 +42,8 @@ var require_browser_polyfill = __commonJS({
41
42
  return false;
42
43
  }
43
44
  }(),
44
- formData: "FormData" in global,
45
- arrayBuffer: "ArrayBuffer" in global
45
+ formData: "FormData" in g,
46
+ arrayBuffer: "ArrayBuffer" in g
46
47
  };
47
48
  function isDataView(obj) {
48
49
  return obj && DataView.prototype.isPrototypeOf(obj);
@@ -100,6 +101,9 @@ var require_browser_polyfill = __commonJS({
100
101
  }, this);
101
102
  } else if (Array.isArray(headers)) {
102
103
  headers.forEach(function(header) {
104
+ if (header.length != 2) {
105
+ throw new TypeError("Headers constructor: expected name/value pair to be length 2, found" + header.length);
106
+ }
103
107
  this.append(header[0], header[1]);
104
108
  }, this);
105
109
  } else if (headers) {
@@ -159,6 +163,7 @@ var require_browser_polyfill = __commonJS({
159
163
  Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
160
164
  }
161
165
  function consumed(body) {
166
+ if (body._noBody) return;
162
167
  if (body.bodyUsed) {
163
168
  return Promise.reject(new TypeError("Already read"));
164
169
  }
@@ -183,7 +188,9 @@ var require_browser_polyfill = __commonJS({
183
188
  function readBlobAsText(blob) {
184
189
  var reader = new FileReader();
185
190
  var promise = fileReaderReady(reader);
186
- reader.readAsText(blob);
191
+ var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type);
192
+ var encoding = match ? match[1] : "utf-8";
193
+ reader.readAsText(blob, encoding);
187
194
  return promise;
188
195
  }
189
196
  function readArrayBufferAsText(buf) {
@@ -209,6 +216,7 @@ var require_browser_polyfill = __commonJS({
209
216
  this.bodyUsed = this.bodyUsed;
210
217
  this._bodyInit = body;
211
218
  if (!body) {
219
+ this._noBody = true;
212
220
  this._bodyText = "";
213
221
  } else if (typeof body === "string") {
214
222
  this._bodyText = body;
@@ -252,27 +260,28 @@ var require_browser_polyfill = __commonJS({
252
260
  return Promise.resolve(new Blob([this._bodyText]));
253
261
  }
254
262
  };
255
- this.arrayBuffer = function() {
256
- if (this._bodyArrayBuffer) {
257
- var isConsumed = consumed(this);
258
- if (isConsumed) {
259
- return isConsumed;
260
- }
261
- if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
262
- return Promise.resolve(
263
- this._bodyArrayBuffer.buffer.slice(
264
- this._bodyArrayBuffer.byteOffset,
265
- this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
266
- )
267
- );
268
- } else {
269
- return Promise.resolve(this._bodyArrayBuffer);
270
- }
263
+ }
264
+ this.arrayBuffer = function() {
265
+ if (this._bodyArrayBuffer) {
266
+ var isConsumed = consumed(this);
267
+ if (isConsumed) {
268
+ return isConsumed;
269
+ } else if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
270
+ return Promise.resolve(
271
+ this._bodyArrayBuffer.buffer.slice(
272
+ this._bodyArrayBuffer.byteOffset,
273
+ this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
274
+ )
275
+ );
271
276
  } else {
272
- return this.blob().then(readBlobAsArrayBuffer);
277
+ return Promise.resolve(this._bodyArrayBuffer);
273
278
  }
274
- };
275
- }
279
+ } else if (support.blob) {
280
+ return this.blob().then(readBlobAsArrayBuffer);
281
+ } else {
282
+ throw new Error("could not read as ArrayBuffer");
283
+ }
284
+ };
276
285
  this.text = function() {
277
286
  var rejected = consumed(this);
278
287
  if (rejected) {
@@ -298,7 +307,7 @@ var require_browser_polyfill = __commonJS({
298
307
  };
299
308
  return this;
300
309
  }
301
- var methods = ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"];
310
+ var methods = ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"];
302
311
  function normalizeMethod(method) {
303
312
  var upcased = method.toUpperCase();
304
313
  return methods.indexOf(upcased) > -1 ? upcased : method;
@@ -334,7 +343,12 @@ var require_browser_polyfill = __commonJS({
334
343
  }
335
344
  this.method = normalizeMethod(options.method || this.method || "GET");
336
345
  this.mode = options.mode || this.mode || null;
337
- this.signal = options.signal || this.signal;
346
+ this.signal = options.signal || this.signal || function() {
347
+ if ("AbortController" in g) {
348
+ var ctrl = new AbortController();
349
+ return ctrl.signal;
350
+ }
351
+ }();
338
352
  this.referrer = null;
339
353
  if ((this.method === "GET" || this.method === "HEAD") && body) {
340
354
  throw new TypeError("Body not allowed for GET or HEAD requests");
@@ -377,7 +391,11 @@ var require_browser_polyfill = __commonJS({
377
391
  var key = parts.shift().trim();
378
392
  if (key) {
379
393
  var value = parts.join(":").trim();
380
- headers.append(key, value);
394
+ try {
395
+ headers.append(key, value);
396
+ } catch (error) {
397
+ console.warn("Response " + error.message);
398
+ }
381
399
  }
382
400
  });
383
401
  return headers;
@@ -392,6 +410,9 @@ var require_browser_polyfill = __commonJS({
392
410
  }
393
411
  this.type = "default";
394
412
  this.status = options.status === void 0 ? 200 : options.status;
413
+ if (this.status < 200 || this.status > 599) {
414
+ throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");
415
+ }
395
416
  this.ok = this.status >= 200 && this.status < 300;
396
417
  this.statusText = options.statusText === void 0 ? "" : "" + options.statusText;
397
418
  this.headers = new Headers(options.headers);
@@ -408,7 +429,9 @@ var require_browser_polyfill = __commonJS({
408
429
  });
409
430
  };
410
431
  Response.error = function() {
411
- var response = new Response(null, { status: 0, statusText: "" });
432
+ var response = new Response(null, { status: 200, statusText: "" });
433
+ response.ok = false;
434
+ response.status = 0;
412
435
  response.type = "error";
413
436
  return response;
414
437
  };
@@ -419,7 +442,7 @@ var require_browser_polyfill = __commonJS({
419
442
  }
420
443
  return new Response(null, { status, headers: { location: url } });
421
444
  };
422
- exports2.DOMException = global.DOMException;
445
+ exports2.DOMException = g.DOMException;
423
446
  try {
424
447
  new exports2.DOMException();
425
448
  } catch (err) {
@@ -444,10 +467,14 @@ var require_browser_polyfill = __commonJS({
444
467
  }
445
468
  xhr.onload = function() {
446
469
  var options = {
447
- status: xhr.status,
448
470
  statusText: xhr.statusText,
449
471
  headers: parseHeaders(xhr.getAllResponseHeaders() || "")
450
472
  };
473
+ if (request.url.indexOf("file://") === 0 && (xhr.status < 200 || xhr.status > 599)) {
474
+ options.status = 200;
475
+ } else {
476
+ options.status = xhr.status;
477
+ }
451
478
  options.url = "responseURL" in xhr ? xhr.responseURL : options.headers.get("X-Request-URL");
452
479
  var body = "response" in xhr ? xhr.response : xhr.responseText;
453
480
  setTimeout(function() {
@@ -461,7 +488,7 @@ var require_browser_polyfill = __commonJS({
461
488
  };
462
489
  xhr.ontimeout = function() {
463
490
  setTimeout(function() {
464
- reject(new TypeError("Network request failed"));
491
+ reject(new TypeError("Network request timed out"));
465
492
  }, 0);
466
493
  };
467
494
  xhr.onabort = function() {
@@ -471,7 +498,7 @@ var require_browser_polyfill = __commonJS({
471
498
  };
472
499
  function fixUrl(url) {
473
500
  try {
474
- return url === "" && global.location.href ? global.location.href : url;
501
+ return url === "" && g.location.href ? g.location.href : url;
475
502
  } catch (e) {
476
503
  return url;
477
504
  }
@@ -485,14 +512,21 @@ var require_browser_polyfill = __commonJS({
485
512
  if ("responseType" in xhr) {
486
513
  if (support.blob) {
487
514
  xhr.responseType = "blob";
488
- } else if (support.arrayBuffer && request.headers.get("Content-Type") && request.headers.get("Content-Type").indexOf("application/octet-stream") !== -1) {
515
+ } else if (support.arrayBuffer) {
489
516
  xhr.responseType = "arraybuffer";
490
517
  }
491
518
  }
492
- if (init && typeof init.headers === "object" && !(init.headers instanceof Headers)) {
519
+ if (init && typeof init.headers === "object" && !(init.headers instanceof Headers || g.Headers && init.headers instanceof g.Headers)) {
520
+ var names = [];
493
521
  Object.getOwnPropertyNames(init.headers).forEach(function(name) {
522
+ names.push(normalizeName(name));
494
523
  xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
495
524
  });
525
+ request.headers.forEach(function(value, name) {
526
+ if (names.indexOf(name) === -1) {
527
+ xhr.setRequestHeader(name, value);
528
+ }
529
+ });
496
530
  } else {
497
531
  request.headers.forEach(function(value, name) {
498
532
  xhr.setRequestHeader(name, value);
@@ -510,11 +544,11 @@ var require_browser_polyfill = __commonJS({
510
544
  });
511
545
  }
512
546
  fetch2.polyfill = true;
513
- if (!global.fetch) {
514
- global.fetch = fetch2;
515
- global.Headers = Headers;
516
- global.Request = Request;
517
- global.Response = Response;
547
+ if (!g.fetch) {
548
+ g.fetch = fetch2;
549
+ g.Headers = Headers;
550
+ g.Request = Request;
551
+ g.Response = Response;
518
552
  }
519
553
  exports2.Headers = Headers;
520
554
  exports2.Request = Request;
@@ -528,10 +562,9 @@ var require_browser_polyfill = __commonJS({
528
562
 
529
563
  // node_modules/uuid/dist/esm-browser/stringify.js
530
564
  var byteToHex = [];
531
- for (i = 0; i < 256; ++i) {
565
+ for (let i = 0; i < 256; ++i) {
532
566
  byteToHex.push((i + 256).toString(16).slice(1));
533
567
  }
534
- var i;
535
568
  function unsafeStringify(arr, offset = 0) {
536
569
  return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
537
570
  }
@@ -541,19 +574,17 @@ var getRandomValues;
541
574
  var rnds8 = new Uint8Array(16);
542
575
  function rng() {
543
576
  if (!getRandomValues) {
544
- getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
545
- if (!getRandomValues) {
577
+ if (typeof crypto === "undefined" || !crypto.getRandomValues) {
546
578
  throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
547
579
  }
580
+ getRandomValues = crypto.getRandomValues.bind(crypto);
548
581
  }
549
582
  return getRandomValues(rnds8);
550
583
  }
551
584
 
552
585
  // node_modules/uuid/dist/esm-browser/native.js
553
586
  var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
554
- var native_default = {
555
- randomUUID
556
- };
587
+ var native_default = { randomUUID };
557
588
 
558
589
  // node_modules/uuid/dist/esm-browser/v4.js
559
590
  function v4(options, buf, offset) {
@@ -561,12 +592,18 @@ function v4(options, buf, offset) {
561
592
  return native_default.randomUUID();
562
593
  }
563
594
  options = options || {};
564
- var rnds = options.random || (options.rng || rng)();
595
+ const rnds = options.random ?? options.rng?.() ?? rng();
596
+ if (rnds.length < 16) {
597
+ throw new Error("Random bytes length must be >= 16");
598
+ }
565
599
  rnds[6] = rnds[6] & 15 | 64;
566
600
  rnds[8] = rnds[8] & 63 | 128;
567
601
  if (buf) {
568
602
  offset = offset || 0;
569
- for (var i = 0; i < 16; ++i) {
603
+ if (offset < 0 || offset + 16 > buf.length) {
604
+ throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
605
+ }
606
+ for (let i = 0; i < 16; ++i) {
570
607
  buf[offset + i] = rnds[i];
571
608
  }
572
609
  return buf;
@@ -578,6 +615,127 @@ var v4_default = v4;
578
615
  // src/index.ts
579
616
  var import_polyfill = __toESM(require_browser_polyfill());
580
617
 
618
+ // src/types/api/models/CheckFlagResponseData.ts
619
+ function CheckFlagResponseDataFromJSON(json) {
620
+ return CheckFlagResponseDataFromJSONTyped(json, false);
621
+ }
622
+ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
623
+ if (json == null) {
624
+ return json;
625
+ }
626
+ return {
627
+ companyId: json["company_id"] == null ? void 0 : json["company_id"],
628
+ error: json["error"] == null ? void 0 : json["error"],
629
+ featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
630
+ featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
631
+ featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
632
+ featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
633
+ flag: json["flag"],
634
+ flagId: json["flag_id"] == null ? void 0 : json["flag_id"],
635
+ reason: json["reason"],
636
+ ruleId: json["rule_id"] == null ? void 0 : json["rule_id"],
637
+ ruleType: json["rule_type"] == null ? void 0 : json["rule_type"],
638
+ userId: json["user_id"] == null ? void 0 : json["user_id"],
639
+ value: json["value"]
640
+ };
641
+ }
642
+
643
+ // src/types/api/models/CheckFlagResponse.ts
644
+ function CheckFlagResponseFromJSON(json) {
645
+ return CheckFlagResponseFromJSONTyped(json, false);
646
+ }
647
+ function CheckFlagResponseFromJSONTyped(json, ignoreDiscriminator) {
648
+ if (json == null) {
649
+ return json;
650
+ }
651
+ return {
652
+ data: CheckFlagResponseDataFromJSON(json["data"]),
653
+ params: json["params"]
654
+ };
655
+ }
656
+
657
+ // src/types/api/models/CheckFlagsResponseData.ts
658
+ function CheckFlagsResponseDataFromJSON(json) {
659
+ return CheckFlagsResponseDataFromJSONTyped(json, false);
660
+ }
661
+ function CheckFlagsResponseDataFromJSONTyped(json, ignoreDiscriminator) {
662
+ if (json == null) {
663
+ return json;
664
+ }
665
+ return {
666
+ flags: json["flags"].map(CheckFlagResponseDataFromJSON)
667
+ };
668
+ }
669
+
670
+ // src/types/api/models/CheckFlagsResponse.ts
671
+ function CheckFlagsResponseFromJSON(json) {
672
+ return CheckFlagsResponseFromJSONTyped(json, false);
673
+ }
674
+ function CheckFlagsResponseFromJSONTyped(json, ignoreDiscriminator) {
675
+ if (json == null) {
676
+ return json;
677
+ }
678
+ return {
679
+ data: CheckFlagsResponseDataFromJSON(json["data"]),
680
+ params: json["params"]
681
+ };
682
+ }
683
+
684
+ // src/types/index.ts
685
+ var RuleType = /* @__PURE__ */ ((RuleType2) => {
686
+ RuleType2["GLOBAL_OVERRIDE"] = "global_override";
687
+ RuleType2["COMPANY_OVERRIDE"] = "company_override";
688
+ RuleType2["COMPANY_OVERRIDE_USAGE_EXCEEDED"] = "company_override_usage_exceeded";
689
+ RuleType2["PLAN_ENTITLEMENT"] = "plan_entitlement";
690
+ RuleType2["PLAN_ENTITLEMENT_USAGE_EXCEEDED"] = "plan_entitlement_usage_exceeded";
691
+ RuleType2["STANDARD"] = "standard";
692
+ RuleType2["DEFAULT"] = "default";
693
+ return RuleType2;
694
+ })(RuleType || {});
695
+ var UsagePeriod = /* @__PURE__ */ ((UsagePeriod2) => {
696
+ UsagePeriod2["ALL_TIME"] = "all_time";
697
+ UsagePeriod2["CURRENT_DAY"] = "current_day";
698
+ UsagePeriod2["CURRENT_MONTH"] = "current_month";
699
+ UsagePeriod2["CURRENT_WEEK"] = "current_week";
700
+ return UsagePeriod2;
701
+ })(UsagePeriod || {});
702
+ var CheckFlagReturnFromJSON = (json) => {
703
+ const {
704
+ companyId,
705
+ error,
706
+ featureAllocation,
707
+ featureUsage,
708
+ featureUsagePeriod,
709
+ featureUsageResetAt,
710
+ flag,
711
+ flagId,
712
+ reason,
713
+ ruleId,
714
+ ruleType,
715
+ userId,
716
+ value
717
+ } = CheckFlagResponseDataFromJSON(json);
718
+ const featureUsageExceeded = !value && // if flag is not false, then we haven't exceeded usage
719
+ (ruleType == "company_override_usage_exceeded" /* COMPANY_OVERRIDE_USAGE_EXCEEDED */ || // if the rule type is one of these, then we have exceeded usage
720
+ ruleType == "plan_entitlement_usage_exceeded" /* PLAN_ENTITLEMENT_USAGE_EXCEEDED */);
721
+ return {
722
+ featureUsageExceeded,
723
+ companyId: companyId == null ? void 0 : companyId,
724
+ error: error == null ? void 0 : error,
725
+ featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
726
+ featureUsage: featureUsage == null ? void 0 : featureUsage,
727
+ featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
728
+ featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
729
+ flag,
730
+ flagId: flagId == null ? void 0 : flagId,
731
+ reason,
732
+ ruleId: ruleId == null ? void 0 : ruleId,
733
+ ruleType: ruleType == null ? void 0 : ruleType,
734
+ userId: userId == null ? void 0 : userId,
735
+ value
736
+ };
737
+ };
738
+
581
739
  // src/utils.ts
582
740
  function contextString(context) {
583
741
  const sortedContext = Object.keys(context).reduce((acc, key) => {
@@ -604,19 +762,18 @@ var Schematic = class {
604
762
  context = {};
605
763
  eventQueue;
606
764
  eventUrl = "https://c.schematichq.com";
607
- flagListener;
765
+ flagCheckListeners = {};
608
766
  flagValueListeners = {};
609
767
  isPending = true;
610
768
  isPendingListeners = /* @__PURE__ */ new Set();
611
769
  storage;
612
770
  useWebSocket = false;
613
- values = {};
771
+ checks = {};
614
772
  webSocketUrl = "wss://api.schematichq.com";
615
773
  constructor(apiKey, options) {
616
774
  this.apiKey = apiKey;
617
775
  this.eventQueue = [];
618
776
  this.useWebSocket = options?.useWebSocket ?? false;
619
- this.flagListener = options?.flagListener;
620
777
  if (options?.additionalHeaders) {
621
778
  this.additionalHeaders = options.additionalHeaders;
622
779
  }
@@ -640,37 +797,89 @@ var Schematic = class {
640
797
  });
641
798
  }
642
799
  }
643
- // Get value for a single flag
644
- // If in websocket mode, return the local value, otherwise make an API call
800
+ /**
801
+ * Get value for a single flag.
802
+ * In WebSocket mode, returns cached values if connection is active, otherwise establishes
803
+ * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
804
+ * connection fails.
805
+ * In REST mode, makes an API call for each check.
806
+ */
645
807
  async checkFlag(options) {
646
808
  const { fallback = false, key } = options;
647
809
  const context = options.context || this.context;
648
- if (this.useWebSocket) {
649
- const contextVals = this.values[contextString(context)] ?? {};
650
- return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
810
+ const contextStr = contextString(context);
811
+ if (!this.useWebSocket) {
812
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
813
+ return fetch(requestUrl, {
814
+ method: "POST",
815
+ headers: {
816
+ ...this.additionalHeaders ?? {},
817
+ "Content-Type": "application/json;charset=UTF-8",
818
+ "X-Schematic-Api-Key": this.apiKey
819
+ },
820
+ body: JSON.stringify(context)
821
+ }).then((response) => {
822
+ if (!response.ok) {
823
+ throw new Error("Network response was not ok");
824
+ }
825
+ return response.json();
826
+ }).then((response) => {
827
+ return CheckFlagResponseFromJSON(response).data.value;
828
+ }).catch((error) => {
829
+ console.error("There was a problem with the fetch operation:", error);
830
+ return fallback;
831
+ });
651
832
  }
652
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
653
- return fetch(requestUrl, {
654
- method: "POST",
655
- headers: {
656
- ...this.additionalHeaders ?? {},
657
- "Content-Type": "application/json;charset=UTF-8",
658
- "X-Schematic-Api-Key": this.apiKey
659
- },
660
- body: JSON.stringify(context)
661
- }).then((response) => {
833
+ try {
834
+ const existingVals = this.checks[contextStr];
835
+ if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
836
+ return existingVals[key].value;
837
+ }
838
+ try {
839
+ await this.setContext(context);
840
+ } catch (error) {
841
+ console.error(
842
+ "WebSocket connection failed, falling back to REST:",
843
+ error
844
+ );
845
+ return this.fallbackToRest(key, context, fallback);
846
+ }
847
+ const contextVals = this.checks[contextStr] ?? {};
848
+ return contextVals[key]?.value ?? fallback;
849
+ } catch (error) {
850
+ console.error("Unexpected error in checkFlag:", error);
851
+ return fallback;
852
+ }
853
+ }
854
+ /**
855
+ * Helper method for falling back to REST API when WebSocket connection fails
856
+ */
857
+ async fallbackToRest(key, context, fallback) {
858
+ try {
859
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
860
+ const response = await fetch(requestUrl, {
861
+ method: "POST",
862
+ headers: {
863
+ ...this.additionalHeaders ?? {},
864
+ "Content-Type": "application/json;charset=UTF-8",
865
+ "X-Schematic-Api-Key": this.apiKey
866
+ },
867
+ body: JSON.stringify(context)
868
+ });
662
869
  if (!response.ok) {
663
870
  throw new Error("Network response was not ok");
664
871
  }
665
- return response.json();
666
- }).then((data) => {
667
- return data.data.value;
668
- }).catch((error) => {
669
- console.error("There was a problem with the fetch operation:", error);
872
+ const data = CheckFlagResponseFromJSON(await response.json());
873
+ return data?.data?.value ?? false;
874
+ } catch (error) {
875
+ console.error("REST API call failed, using fallback value:", error);
670
876
  return fallback;
671
- });
877
+ }
672
878
  }
673
- // Make an API call to fetch all flag values for a given context (use if not in websocket mode)
879
+ /**
880
+ * Make an API call to fetch all flag values for a given context.
881
+ * Recommended for use in REST mode only.
882
+ */
674
883
  checkFlags = async (context) => {
675
884
  context = context || this.context;
676
885
  const requestUrl = `${this.apiUrl}/flags/check`;
@@ -688,8 +897,9 @@ var Schematic = class {
688
897
  throw new Error("Network response was not ok");
689
898
  }
690
899
  return response.json();
691
- }).then((data) => {
692
- return (data?.data?.flags ?? []).reduce(
900
+ }).then((responseJson) => {
901
+ const resp = CheckFlagsResponseFromJSON(responseJson);
902
+ return (resp?.data?.flags ?? []).reduce(
693
903
  (accum, flag) => {
694
904
  accum[flag.flag] = flag.value;
695
905
  return accum;
@@ -698,21 +908,33 @@ var Schematic = class {
698
908
  );
699
909
  }).catch((error) => {
700
910
  console.error("There was a problem with the fetch operation:", error);
701
- return false;
911
+ return {};
702
912
  });
703
913
  };
704
- // Send an identify event
914
+ /**
915
+ * Send an identify event.
916
+ * This will set the context for subsequent flag evaluation and events, and will also
917
+ * send an identify event to the Schematic API which will upsert a user and company.
918
+ */
705
919
  identify = (body) => {
706
- this.setContext({
707
- company: body.company?.keys,
708
- user: body.keys
709
- });
920
+ try {
921
+ this.setContext({
922
+ company: body.company?.keys,
923
+ user: body.keys
924
+ });
925
+ } catch (error) {
926
+ console.error("Error setting context:", error);
927
+ }
710
928
  return this.handleEvent("identify", body);
711
929
  };
712
- // Set the flag evaluation context; if the context has changed,
713
- // this will open a websocket connection (if not already open)
714
- // and submit this context. The promise will resolve when the
715
- // websocket sends back an initial set of flag values.
930
+ /**
931
+ * Set the flag evaluation context.
932
+ * In WebSocket mode, this will:
933
+ * 1. Open a websocket connection if not already open
934
+ * 2. Send the context to the server
935
+ * 3. Wait for initial flag values to be returned
936
+ * The promise resolves when initial flag values are received.
937
+ */
716
938
  setContext = async (context) => {
717
939
  if (!this.useWebSocket) {
718
940
  this.context = context;
@@ -726,10 +948,14 @@ var Schematic = class {
726
948
  const socket = await this.conn;
727
949
  await this.wsSendMessage(socket, context);
728
950
  } catch (error) {
729
- console.error("Error setting Schematic context:", error);
951
+ console.error("Failed to establish WebSocket connection:", error);
952
+ throw error;
730
953
  }
731
954
  };
732
- // Send track event
955
+ /**
956
+ * Send a track event
957
+ * Track usage for a company and/or user.
958
+ */
733
959
  track = (body) => {
734
960
  const { company, user, event, traits } = body;
735
961
  return this.handleEvent("track", {
@@ -801,6 +1027,9 @@ var Schematic = class {
801
1027
  /**
802
1028
  * Websocket management
803
1029
  */
1030
+ /**
1031
+ * If using websocket mode, close the connection when done.
1032
+ */
804
1033
  cleanup = async () => {
805
1034
  if (this.conn) {
806
1035
  try {
@@ -841,18 +1070,15 @@ var Schematic = class {
841
1070
  let resolved = false;
842
1071
  const messageHandler = (event) => {
843
1072
  const message = JSON.parse(event.data);
844
- if (!(contextString(context) in this.values)) {
845
- this.values[contextString(context)] = {};
846
- }
847
- (message.flags ?? []).forEach(
848
- (flag) => {
849
- this.values[contextString(context)][flag.flag] = flag.value;
850
- this.notifyFlagValueListeners(flag.flag, flag.value);
851
- }
852
- );
853
- if (this.flagListener) {
854
- this.flagListener(this.getFlagValues());
1073
+ if (!(contextString(context) in this.checks)) {
1074
+ this.checks[contextString(context)] = {};
855
1075
  }
1076
+ (message.flags ?? []).forEach((flag) => {
1077
+ const flagCheck = CheckFlagReturnFromJSON(flag);
1078
+ this.checks[contextString(context)][flagCheck.flag] = flagCheck;
1079
+ this.notifyFlagCheckListeners(flag.flag, flagCheck);
1080
+ this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1081
+ });
856
1082
  this.setIsPending(false);
857
1083
  if (!resolved) {
858
1084
  resolved = true;
@@ -892,18 +1118,21 @@ var Schematic = class {
892
1118
  setIsPending = (isPending) => {
893
1119
  this.isPending = isPending;
894
1120
  this.isPendingListeners.forEach(
895
- (listener) => notifyListener(listener, isPending)
1121
+ (listener) => notifyPendingListener(listener, isPending)
896
1122
  );
897
1123
  };
1124
+ // flag checks state
1125
+ getFlagCheck = (flagKey) => {
1126
+ const contextStr = contextString(this.context);
1127
+ const checks = this.checks[contextStr] ?? {};
1128
+ return checks[flagKey];
1129
+ };
898
1130
  // flagValues state
899
1131
  getFlagValue = (flagKey) => {
900
- const values = this.getFlagValues();
901
- return values[flagKey];
902
- };
903
- getFlagValues = () => {
904
- const contextStr = contextString(this.context);
905
- return this.values[contextStr] ?? {};
1132
+ const check = this.getFlagCheck(flagKey);
1133
+ return check?.value;
906
1134
  };
1135
+ /** Register an event listener that will be notified with the boolean value for a given flag when this value changes */
907
1136
  addFlagValueListener = (flagKey, listener) => {
908
1137
  if (!(flagKey in this.flagValueListeners)) {
909
1138
  this.flagValueListeners[flagKey] = /* @__PURE__ */ new Set();
@@ -913,12 +1142,40 @@ var Schematic = class {
913
1142
  this.flagValueListeners[flagKey].delete(listener);
914
1143
  };
915
1144
  };
1145
+ /** Register an event listener that will be notified with the full flag check response for a given flag whenever this value changes */
1146
+ addFlagCheckListener = (flagKey, listener) => {
1147
+ if (!(flagKey in this.flagCheckListeners)) {
1148
+ this.flagCheckListeners[flagKey] = /* @__PURE__ */ new Set();
1149
+ }
1150
+ this.flagCheckListeners[flagKey].add(listener);
1151
+ return () => {
1152
+ this.flagCheckListeners[flagKey].delete(listener);
1153
+ };
1154
+ };
1155
+ notifyFlagCheckListeners = (flagKey, check) => {
1156
+ const listeners = this.flagCheckListeners?.[flagKey] ?? [];
1157
+ listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
1158
+ };
916
1159
  notifyFlagValueListeners = (flagKey, value) => {
917
1160
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
918
- listeners.forEach((listener) => notifyListener(listener, value));
1161
+ listeners.forEach((listener) => notifyFlagValueListener(listener, value));
919
1162
  };
920
1163
  };
921
- var notifyListener = (listener, value) => {
1164
+ var notifyPendingListener = (listener, value) => {
1165
+ if (listener.length > 0) {
1166
+ listener(value);
1167
+ } else {
1168
+ listener();
1169
+ }
1170
+ };
1171
+ var notifyFlagCheckListener = (listener, value) => {
1172
+ if (listener.length > 0) {
1173
+ listener(value);
1174
+ } else {
1175
+ listener();
1176
+ }
1177
+ };
1178
+ var notifyFlagValueListener = (listener, value) => {
922
1179
  if (listener.length > 0) {
923
1180
  listener(value);
924
1181
  } else {
@@ -926,6 +1183,11 @@ var notifyListener = (listener, value) => {
926
1183
  }
927
1184
  };
928
1185
  export {
929
- Schematic
1186
+ CheckFlagResponseFromJSON,
1187
+ CheckFlagReturnFromJSON,
1188
+ CheckFlagsResponseFromJSON,
1189
+ RuleType,
1190
+ Schematic,
1191
+ UsagePeriod
930
1192
  };
931
1193
  /* @preserve */