@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.
@@ -35,11 +35,12 @@ var require_browser_polyfill = __commonJS({
35
35
  "node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
36
36
  (function(self2) {
37
37
  var irrelevant = function(exports2) {
38
- var global = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || typeof global !== "undefined" && global;
38
+ var g = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || // eslint-disable-next-line no-undef
39
+ typeof global !== "undefined" && global || {};
39
40
  var support = {
40
- searchParams: "URLSearchParams" in global,
41
- iterable: "Symbol" in global && "iterator" in Symbol,
42
- blob: "FileReader" in global && "Blob" in global && function() {
41
+ searchParams: "URLSearchParams" in g,
42
+ iterable: "Symbol" in g && "iterator" in Symbol,
43
+ blob: "FileReader" in g && "Blob" in g && function() {
43
44
  try {
44
45
  new Blob();
45
46
  return true;
@@ -47,8 +48,8 @@ var require_browser_polyfill = __commonJS({
47
48
  return false;
48
49
  }
49
50
  }(),
50
- formData: "FormData" in global,
51
- arrayBuffer: "ArrayBuffer" in global
51
+ formData: "FormData" in g,
52
+ arrayBuffer: "ArrayBuffer" in g
52
53
  };
53
54
  function isDataView(obj) {
54
55
  return obj && DataView.prototype.isPrototypeOf(obj);
@@ -106,6 +107,9 @@ var require_browser_polyfill = __commonJS({
106
107
  }, this);
107
108
  } else if (Array.isArray(headers)) {
108
109
  headers.forEach(function(header) {
110
+ if (header.length != 2) {
111
+ throw new TypeError("Headers constructor: expected name/value pair to be length 2, found" + header.length);
112
+ }
109
113
  this.append(header[0], header[1]);
110
114
  }, this);
111
115
  } else if (headers) {
@@ -165,6 +169,7 @@ var require_browser_polyfill = __commonJS({
165
169
  Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
166
170
  }
167
171
  function consumed(body) {
172
+ if (body._noBody) return;
168
173
  if (body.bodyUsed) {
169
174
  return Promise.reject(new TypeError("Already read"));
170
175
  }
@@ -189,7 +194,9 @@ var require_browser_polyfill = __commonJS({
189
194
  function readBlobAsText(blob) {
190
195
  var reader = new FileReader();
191
196
  var promise = fileReaderReady(reader);
192
- reader.readAsText(blob);
197
+ var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type);
198
+ var encoding = match ? match[1] : "utf-8";
199
+ reader.readAsText(blob, encoding);
193
200
  return promise;
194
201
  }
195
202
  function readArrayBufferAsText(buf) {
@@ -215,6 +222,7 @@ var require_browser_polyfill = __commonJS({
215
222
  this.bodyUsed = this.bodyUsed;
216
223
  this._bodyInit = body;
217
224
  if (!body) {
225
+ this._noBody = true;
218
226
  this._bodyText = "";
219
227
  } else if (typeof body === "string") {
220
228
  this._bodyText = body;
@@ -258,27 +266,28 @@ var require_browser_polyfill = __commonJS({
258
266
  return Promise.resolve(new Blob([this._bodyText]));
259
267
  }
260
268
  };
261
- this.arrayBuffer = function() {
262
- if (this._bodyArrayBuffer) {
263
- var isConsumed = consumed(this);
264
- if (isConsumed) {
265
- return isConsumed;
266
- }
267
- if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
268
- return Promise.resolve(
269
- this._bodyArrayBuffer.buffer.slice(
270
- this._bodyArrayBuffer.byteOffset,
271
- this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
272
- )
273
- );
274
- } else {
275
- return Promise.resolve(this._bodyArrayBuffer);
276
- }
269
+ }
270
+ this.arrayBuffer = function() {
271
+ if (this._bodyArrayBuffer) {
272
+ var isConsumed = consumed(this);
273
+ if (isConsumed) {
274
+ return isConsumed;
275
+ } else if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
276
+ return Promise.resolve(
277
+ this._bodyArrayBuffer.buffer.slice(
278
+ this._bodyArrayBuffer.byteOffset,
279
+ this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
280
+ )
281
+ );
277
282
  } else {
278
- return this.blob().then(readBlobAsArrayBuffer);
283
+ return Promise.resolve(this._bodyArrayBuffer);
279
284
  }
280
- };
281
- }
285
+ } else if (support.blob) {
286
+ return this.blob().then(readBlobAsArrayBuffer);
287
+ } else {
288
+ throw new Error("could not read as ArrayBuffer");
289
+ }
290
+ };
282
291
  this.text = function() {
283
292
  var rejected = consumed(this);
284
293
  if (rejected) {
@@ -304,7 +313,7 @@ var require_browser_polyfill = __commonJS({
304
313
  };
305
314
  return this;
306
315
  }
307
- var methods = ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"];
316
+ var methods = ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"];
308
317
  function normalizeMethod(method) {
309
318
  var upcased = method.toUpperCase();
310
319
  return methods.indexOf(upcased) > -1 ? upcased : method;
@@ -340,7 +349,12 @@ var require_browser_polyfill = __commonJS({
340
349
  }
341
350
  this.method = normalizeMethod(options.method || this.method || "GET");
342
351
  this.mode = options.mode || this.mode || null;
343
- this.signal = options.signal || this.signal;
352
+ this.signal = options.signal || this.signal || function() {
353
+ if ("AbortController" in g) {
354
+ var ctrl = new AbortController();
355
+ return ctrl.signal;
356
+ }
357
+ }();
344
358
  this.referrer = null;
345
359
  if ((this.method === "GET" || this.method === "HEAD") && body) {
346
360
  throw new TypeError("Body not allowed for GET or HEAD requests");
@@ -383,7 +397,11 @@ var require_browser_polyfill = __commonJS({
383
397
  var key = parts.shift().trim();
384
398
  if (key) {
385
399
  var value = parts.join(":").trim();
386
- headers.append(key, value);
400
+ try {
401
+ headers.append(key, value);
402
+ } catch (error) {
403
+ console.warn("Response " + error.message);
404
+ }
387
405
  }
388
406
  });
389
407
  return headers;
@@ -398,6 +416,9 @@ var require_browser_polyfill = __commonJS({
398
416
  }
399
417
  this.type = "default";
400
418
  this.status = options.status === void 0 ? 200 : options.status;
419
+ if (this.status < 200 || this.status > 599) {
420
+ throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");
421
+ }
401
422
  this.ok = this.status >= 200 && this.status < 300;
402
423
  this.statusText = options.statusText === void 0 ? "" : "" + options.statusText;
403
424
  this.headers = new Headers(options.headers);
@@ -414,7 +435,9 @@ var require_browser_polyfill = __commonJS({
414
435
  });
415
436
  };
416
437
  Response.error = function() {
417
- var response = new Response(null, { status: 0, statusText: "" });
438
+ var response = new Response(null, { status: 200, statusText: "" });
439
+ response.ok = false;
440
+ response.status = 0;
418
441
  response.type = "error";
419
442
  return response;
420
443
  };
@@ -425,7 +448,7 @@ var require_browser_polyfill = __commonJS({
425
448
  }
426
449
  return new Response(null, { status, headers: { location: url } });
427
450
  };
428
- exports2.DOMException = global.DOMException;
451
+ exports2.DOMException = g.DOMException;
429
452
  try {
430
453
  new exports2.DOMException();
431
454
  } catch (err) {
@@ -450,10 +473,14 @@ var require_browser_polyfill = __commonJS({
450
473
  }
451
474
  xhr.onload = function() {
452
475
  var options = {
453
- status: xhr.status,
454
476
  statusText: xhr.statusText,
455
477
  headers: parseHeaders(xhr.getAllResponseHeaders() || "")
456
478
  };
479
+ if (request.url.indexOf("file://") === 0 && (xhr.status < 200 || xhr.status > 599)) {
480
+ options.status = 200;
481
+ } else {
482
+ options.status = xhr.status;
483
+ }
457
484
  options.url = "responseURL" in xhr ? xhr.responseURL : options.headers.get("X-Request-URL");
458
485
  var body = "response" in xhr ? xhr.response : xhr.responseText;
459
486
  setTimeout(function() {
@@ -467,7 +494,7 @@ var require_browser_polyfill = __commonJS({
467
494
  };
468
495
  xhr.ontimeout = function() {
469
496
  setTimeout(function() {
470
- reject(new TypeError("Network request failed"));
497
+ reject(new TypeError("Network request timed out"));
471
498
  }, 0);
472
499
  };
473
500
  xhr.onabort = function() {
@@ -477,7 +504,7 @@ var require_browser_polyfill = __commonJS({
477
504
  };
478
505
  function fixUrl(url) {
479
506
  try {
480
- return url === "" && global.location.href ? global.location.href : url;
507
+ return url === "" && g.location.href ? g.location.href : url;
481
508
  } catch (e) {
482
509
  return url;
483
510
  }
@@ -491,14 +518,21 @@ var require_browser_polyfill = __commonJS({
491
518
  if ("responseType" in xhr) {
492
519
  if (support.blob) {
493
520
  xhr.responseType = "blob";
494
- } else if (support.arrayBuffer && request.headers.get("Content-Type") && request.headers.get("Content-Type").indexOf("application/octet-stream") !== -1) {
521
+ } else if (support.arrayBuffer) {
495
522
  xhr.responseType = "arraybuffer";
496
523
  }
497
524
  }
498
- if (init && typeof init.headers === "object" && !(init.headers instanceof Headers)) {
525
+ if (init && typeof init.headers === "object" && !(init.headers instanceof Headers || g.Headers && init.headers instanceof g.Headers)) {
526
+ var names = [];
499
527
  Object.getOwnPropertyNames(init.headers).forEach(function(name) {
528
+ names.push(normalizeName(name));
500
529
  xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
501
530
  });
531
+ request.headers.forEach(function(value, name) {
532
+ if (names.indexOf(name) === -1) {
533
+ xhr.setRequestHeader(name, value);
534
+ }
535
+ });
502
536
  } else {
503
537
  request.headers.forEach(function(value, name) {
504
538
  xhr.setRequestHeader(name, value);
@@ -516,11 +550,11 @@ var require_browser_polyfill = __commonJS({
516
550
  });
517
551
  }
518
552
  fetch2.polyfill = true;
519
- if (!global.fetch) {
520
- global.fetch = fetch2;
521
- global.Headers = Headers;
522
- global.Request = Request;
523
- global.Response = Response;
553
+ if (!g.fetch) {
554
+ g.fetch = fetch2;
555
+ g.Headers = Headers;
556
+ g.Request = Request;
557
+ g.Response = Response;
524
558
  }
525
559
  exports2.Headers = Headers;
526
560
  exports2.Request = Request;
@@ -533,18 +567,22 @@ var require_browser_polyfill = __commonJS({
533
567
  });
534
568
 
535
569
  // src/index.ts
536
- var src_exports = {};
537
- __export(src_exports, {
538
- Schematic: () => Schematic
570
+ var index_exports = {};
571
+ __export(index_exports, {
572
+ CheckFlagResponseFromJSON: () => CheckFlagResponseFromJSON,
573
+ CheckFlagReturnFromJSON: () => CheckFlagReturnFromJSON,
574
+ CheckFlagsResponseFromJSON: () => CheckFlagsResponseFromJSON,
575
+ RuleType: () => RuleType,
576
+ Schematic: () => Schematic,
577
+ UsagePeriod: () => UsagePeriod
539
578
  });
540
- module.exports = __toCommonJS(src_exports);
579
+ module.exports = __toCommonJS(index_exports);
541
580
 
542
581
  // node_modules/uuid/dist/esm-browser/stringify.js
543
582
  var byteToHex = [];
544
- for (i = 0; i < 256; ++i) {
583
+ for (let i = 0; i < 256; ++i) {
545
584
  byteToHex.push((i + 256).toString(16).slice(1));
546
585
  }
547
- var i;
548
586
  function unsafeStringify(arr, offset = 0) {
549
587
  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();
550
588
  }
@@ -554,19 +592,17 @@ var getRandomValues;
554
592
  var rnds8 = new Uint8Array(16);
555
593
  function rng() {
556
594
  if (!getRandomValues) {
557
- getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
558
- if (!getRandomValues) {
595
+ if (typeof crypto === "undefined" || !crypto.getRandomValues) {
559
596
  throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
560
597
  }
598
+ getRandomValues = crypto.getRandomValues.bind(crypto);
561
599
  }
562
600
  return getRandomValues(rnds8);
563
601
  }
564
602
 
565
603
  // node_modules/uuid/dist/esm-browser/native.js
566
604
  var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
567
- var native_default = {
568
- randomUUID
569
- };
605
+ var native_default = { randomUUID };
570
606
 
571
607
  // node_modules/uuid/dist/esm-browser/v4.js
572
608
  function v4(options, buf, offset) {
@@ -574,12 +610,18 @@ function v4(options, buf, offset) {
574
610
  return native_default.randomUUID();
575
611
  }
576
612
  options = options || {};
577
- var rnds = options.random || (options.rng || rng)();
613
+ const rnds = options.random ?? options.rng?.() ?? rng();
614
+ if (rnds.length < 16) {
615
+ throw new Error("Random bytes length must be >= 16");
616
+ }
578
617
  rnds[6] = rnds[6] & 15 | 64;
579
618
  rnds[8] = rnds[8] & 63 | 128;
580
619
  if (buf) {
581
620
  offset = offset || 0;
582
- for (var i = 0; i < 16; ++i) {
621
+ if (offset < 0 || offset + 16 > buf.length) {
622
+ throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
623
+ }
624
+ for (let i = 0; i < 16; ++i) {
583
625
  buf[offset + i] = rnds[i];
584
626
  }
585
627
  return buf;
@@ -591,6 +633,127 @@ var v4_default = v4;
591
633
  // src/index.ts
592
634
  var import_polyfill = __toESM(require_browser_polyfill());
593
635
 
636
+ // src/types/api/models/CheckFlagResponseData.ts
637
+ function CheckFlagResponseDataFromJSON(json) {
638
+ return CheckFlagResponseDataFromJSONTyped(json, false);
639
+ }
640
+ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
641
+ if (json == null) {
642
+ return json;
643
+ }
644
+ return {
645
+ companyId: json["company_id"] == null ? void 0 : json["company_id"],
646
+ error: json["error"] == null ? void 0 : json["error"],
647
+ featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
648
+ featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
649
+ featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
650
+ featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
651
+ flag: json["flag"],
652
+ flagId: json["flag_id"] == null ? void 0 : json["flag_id"],
653
+ reason: json["reason"],
654
+ ruleId: json["rule_id"] == null ? void 0 : json["rule_id"],
655
+ ruleType: json["rule_type"] == null ? void 0 : json["rule_type"],
656
+ userId: json["user_id"] == null ? void 0 : json["user_id"],
657
+ value: json["value"]
658
+ };
659
+ }
660
+
661
+ // src/types/api/models/CheckFlagResponse.ts
662
+ function CheckFlagResponseFromJSON(json) {
663
+ return CheckFlagResponseFromJSONTyped(json, false);
664
+ }
665
+ function CheckFlagResponseFromJSONTyped(json, ignoreDiscriminator) {
666
+ if (json == null) {
667
+ return json;
668
+ }
669
+ return {
670
+ data: CheckFlagResponseDataFromJSON(json["data"]),
671
+ params: json["params"]
672
+ };
673
+ }
674
+
675
+ // src/types/api/models/CheckFlagsResponseData.ts
676
+ function CheckFlagsResponseDataFromJSON(json) {
677
+ return CheckFlagsResponseDataFromJSONTyped(json, false);
678
+ }
679
+ function CheckFlagsResponseDataFromJSONTyped(json, ignoreDiscriminator) {
680
+ if (json == null) {
681
+ return json;
682
+ }
683
+ return {
684
+ flags: json["flags"].map(CheckFlagResponseDataFromJSON)
685
+ };
686
+ }
687
+
688
+ // src/types/api/models/CheckFlagsResponse.ts
689
+ function CheckFlagsResponseFromJSON(json) {
690
+ return CheckFlagsResponseFromJSONTyped(json, false);
691
+ }
692
+ function CheckFlagsResponseFromJSONTyped(json, ignoreDiscriminator) {
693
+ if (json == null) {
694
+ return json;
695
+ }
696
+ return {
697
+ data: CheckFlagsResponseDataFromJSON(json["data"]),
698
+ params: json["params"]
699
+ };
700
+ }
701
+
702
+ // src/types/index.ts
703
+ var RuleType = /* @__PURE__ */ ((RuleType2) => {
704
+ RuleType2["GLOBAL_OVERRIDE"] = "global_override";
705
+ RuleType2["COMPANY_OVERRIDE"] = "company_override";
706
+ RuleType2["COMPANY_OVERRIDE_USAGE_EXCEEDED"] = "company_override_usage_exceeded";
707
+ RuleType2["PLAN_ENTITLEMENT"] = "plan_entitlement";
708
+ RuleType2["PLAN_ENTITLEMENT_USAGE_EXCEEDED"] = "plan_entitlement_usage_exceeded";
709
+ RuleType2["STANDARD"] = "standard";
710
+ RuleType2["DEFAULT"] = "default";
711
+ return RuleType2;
712
+ })(RuleType || {});
713
+ var UsagePeriod = /* @__PURE__ */ ((UsagePeriod2) => {
714
+ UsagePeriod2["ALL_TIME"] = "all_time";
715
+ UsagePeriod2["CURRENT_DAY"] = "current_day";
716
+ UsagePeriod2["CURRENT_MONTH"] = "current_month";
717
+ UsagePeriod2["CURRENT_WEEK"] = "current_week";
718
+ return UsagePeriod2;
719
+ })(UsagePeriod || {});
720
+ var CheckFlagReturnFromJSON = (json) => {
721
+ const {
722
+ companyId,
723
+ error,
724
+ featureAllocation,
725
+ featureUsage,
726
+ featureUsagePeriod,
727
+ featureUsageResetAt,
728
+ flag,
729
+ flagId,
730
+ reason,
731
+ ruleId,
732
+ ruleType,
733
+ userId,
734
+ value
735
+ } = CheckFlagResponseDataFromJSON(json);
736
+ const featureUsageExceeded = !value && // if flag is not false, then we haven't exceeded usage
737
+ (ruleType == "company_override_usage_exceeded" /* COMPANY_OVERRIDE_USAGE_EXCEEDED */ || // if the rule type is one of these, then we have exceeded usage
738
+ ruleType == "plan_entitlement_usage_exceeded" /* PLAN_ENTITLEMENT_USAGE_EXCEEDED */);
739
+ return {
740
+ featureUsageExceeded,
741
+ companyId: companyId == null ? void 0 : companyId,
742
+ error: error == null ? void 0 : error,
743
+ featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
744
+ featureUsage: featureUsage == null ? void 0 : featureUsage,
745
+ featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
746
+ featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
747
+ flag,
748
+ flagId: flagId == null ? void 0 : flagId,
749
+ reason,
750
+ ruleId: ruleId == null ? void 0 : ruleId,
751
+ ruleType: ruleType == null ? void 0 : ruleType,
752
+ userId: userId == null ? void 0 : userId,
753
+ value
754
+ };
755
+ };
756
+
594
757
  // src/utils.ts
595
758
  function contextString(context) {
596
759
  const sortedContext = Object.keys(context).reduce((acc, key) => {
@@ -617,19 +780,18 @@ var Schematic = class {
617
780
  context = {};
618
781
  eventQueue;
619
782
  eventUrl = "https://c.schematichq.com";
620
- flagListener;
783
+ flagCheckListeners = {};
621
784
  flagValueListeners = {};
622
785
  isPending = true;
623
786
  isPendingListeners = /* @__PURE__ */ new Set();
624
787
  storage;
625
788
  useWebSocket = false;
626
- values = {};
789
+ checks = {};
627
790
  webSocketUrl = "wss://api.schematichq.com";
628
791
  constructor(apiKey, options) {
629
792
  this.apiKey = apiKey;
630
793
  this.eventQueue = [];
631
794
  this.useWebSocket = options?.useWebSocket ?? false;
632
- this.flagListener = options?.flagListener;
633
795
  if (options?.additionalHeaders) {
634
796
  this.additionalHeaders = options.additionalHeaders;
635
797
  }
@@ -653,37 +815,89 @@ var Schematic = class {
653
815
  });
654
816
  }
655
817
  }
656
- // Get value for a single flag
657
- // If in websocket mode, return the local value, otherwise make an API call
818
+ /**
819
+ * Get value for a single flag.
820
+ * In WebSocket mode, returns cached values if connection is active, otherwise establishes
821
+ * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
822
+ * connection fails.
823
+ * In REST mode, makes an API call for each check.
824
+ */
658
825
  async checkFlag(options) {
659
826
  const { fallback = false, key } = options;
660
827
  const context = options.context || this.context;
661
- if (this.useWebSocket) {
662
- const contextVals = this.values[contextString(context)] ?? {};
663
- return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
828
+ const contextStr = contextString(context);
829
+ if (!this.useWebSocket) {
830
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
831
+ return fetch(requestUrl, {
832
+ method: "POST",
833
+ headers: {
834
+ ...this.additionalHeaders ?? {},
835
+ "Content-Type": "application/json;charset=UTF-8",
836
+ "X-Schematic-Api-Key": this.apiKey
837
+ },
838
+ body: JSON.stringify(context)
839
+ }).then((response) => {
840
+ if (!response.ok) {
841
+ throw new Error("Network response was not ok");
842
+ }
843
+ return response.json();
844
+ }).then((response) => {
845
+ return CheckFlagResponseFromJSON(response).data.value;
846
+ }).catch((error) => {
847
+ console.error("There was a problem with the fetch operation:", error);
848
+ return fallback;
849
+ });
664
850
  }
665
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
666
- return fetch(requestUrl, {
667
- method: "POST",
668
- headers: {
669
- ...this.additionalHeaders ?? {},
670
- "Content-Type": "application/json;charset=UTF-8",
671
- "X-Schematic-Api-Key": this.apiKey
672
- },
673
- body: JSON.stringify(context)
674
- }).then((response) => {
851
+ try {
852
+ const existingVals = this.checks[contextStr];
853
+ if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
854
+ return existingVals[key].value;
855
+ }
856
+ try {
857
+ await this.setContext(context);
858
+ } catch (error) {
859
+ console.error(
860
+ "WebSocket connection failed, falling back to REST:",
861
+ error
862
+ );
863
+ return this.fallbackToRest(key, context, fallback);
864
+ }
865
+ const contextVals = this.checks[contextStr] ?? {};
866
+ return contextVals[key]?.value ?? fallback;
867
+ } catch (error) {
868
+ console.error("Unexpected error in checkFlag:", error);
869
+ return fallback;
870
+ }
871
+ }
872
+ /**
873
+ * Helper method for falling back to REST API when WebSocket connection fails
874
+ */
875
+ async fallbackToRest(key, context, fallback) {
876
+ try {
877
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
878
+ const response = await fetch(requestUrl, {
879
+ method: "POST",
880
+ headers: {
881
+ ...this.additionalHeaders ?? {},
882
+ "Content-Type": "application/json;charset=UTF-8",
883
+ "X-Schematic-Api-Key": this.apiKey
884
+ },
885
+ body: JSON.stringify(context)
886
+ });
675
887
  if (!response.ok) {
676
888
  throw new Error("Network response was not ok");
677
889
  }
678
- return response.json();
679
- }).then((data) => {
680
- return data.data.value;
681
- }).catch((error) => {
682
- console.error("There was a problem with the fetch operation:", error);
890
+ const data = CheckFlagResponseFromJSON(await response.json());
891
+ return data?.data?.value ?? false;
892
+ } catch (error) {
893
+ console.error("REST API call failed, using fallback value:", error);
683
894
  return fallback;
684
- });
895
+ }
685
896
  }
686
- // Make an API call to fetch all flag values for a given context (use if not in websocket mode)
897
+ /**
898
+ * Make an API call to fetch all flag values for a given context.
899
+ * Recommended for use in REST mode only.
900
+ */
687
901
  checkFlags = async (context) => {
688
902
  context = context || this.context;
689
903
  const requestUrl = `${this.apiUrl}/flags/check`;
@@ -701,8 +915,9 @@ var Schematic = class {
701
915
  throw new Error("Network response was not ok");
702
916
  }
703
917
  return response.json();
704
- }).then((data) => {
705
- return (data?.data?.flags ?? []).reduce(
918
+ }).then((responseJson) => {
919
+ const resp = CheckFlagsResponseFromJSON(responseJson);
920
+ return (resp?.data?.flags ?? []).reduce(
706
921
  (accum, flag) => {
707
922
  accum[flag.flag] = flag.value;
708
923
  return accum;
@@ -711,21 +926,33 @@ var Schematic = class {
711
926
  );
712
927
  }).catch((error) => {
713
928
  console.error("There was a problem with the fetch operation:", error);
714
- return false;
929
+ return {};
715
930
  });
716
931
  };
717
- // Send an identify event
932
+ /**
933
+ * Send an identify event.
934
+ * This will set the context for subsequent flag evaluation and events, and will also
935
+ * send an identify event to the Schematic API which will upsert a user and company.
936
+ */
718
937
  identify = (body) => {
719
- this.setContext({
720
- company: body.company?.keys,
721
- user: body.keys
722
- });
938
+ try {
939
+ this.setContext({
940
+ company: body.company?.keys,
941
+ user: body.keys
942
+ });
943
+ } catch (error) {
944
+ console.error("Error setting context:", error);
945
+ }
723
946
  return this.handleEvent("identify", body);
724
947
  };
725
- // Set the flag evaluation context; if the context has changed,
726
- // this will open a websocket connection (if not already open)
727
- // and submit this context. The promise will resolve when the
728
- // websocket sends back an initial set of flag values.
948
+ /**
949
+ * Set the flag evaluation context.
950
+ * In WebSocket mode, this will:
951
+ * 1. Open a websocket connection if not already open
952
+ * 2. Send the context to the server
953
+ * 3. Wait for initial flag values to be returned
954
+ * The promise resolves when initial flag values are received.
955
+ */
729
956
  setContext = async (context) => {
730
957
  if (!this.useWebSocket) {
731
958
  this.context = context;
@@ -739,10 +966,14 @@ var Schematic = class {
739
966
  const socket = await this.conn;
740
967
  await this.wsSendMessage(socket, context);
741
968
  } catch (error) {
742
- console.error("Error setting Schematic context:", error);
969
+ console.error("Failed to establish WebSocket connection:", error);
970
+ throw error;
743
971
  }
744
972
  };
745
- // Send track event
973
+ /**
974
+ * Send a track event
975
+ * Track usage for a company and/or user.
976
+ */
746
977
  track = (body) => {
747
978
  const { company, user, event, traits } = body;
748
979
  return this.handleEvent("track", {
@@ -814,6 +1045,9 @@ var Schematic = class {
814
1045
  /**
815
1046
  * Websocket management
816
1047
  */
1048
+ /**
1049
+ * If using websocket mode, close the connection when done.
1050
+ */
817
1051
  cleanup = async () => {
818
1052
  if (this.conn) {
819
1053
  try {
@@ -854,18 +1088,15 @@ var Schematic = class {
854
1088
  let resolved = false;
855
1089
  const messageHandler = (event) => {
856
1090
  const message = JSON.parse(event.data);
857
- if (!(contextString(context) in this.values)) {
858
- this.values[contextString(context)] = {};
859
- }
860
- (message.flags ?? []).forEach(
861
- (flag) => {
862
- this.values[contextString(context)][flag.flag] = flag.value;
863
- this.notifyFlagValueListeners(flag.flag, flag.value);
864
- }
865
- );
866
- if (this.flagListener) {
867
- this.flagListener(this.getFlagValues());
1091
+ if (!(contextString(context) in this.checks)) {
1092
+ this.checks[contextString(context)] = {};
868
1093
  }
1094
+ (message.flags ?? []).forEach((flag) => {
1095
+ const flagCheck = CheckFlagReturnFromJSON(flag);
1096
+ this.checks[contextString(context)][flagCheck.flag] = flagCheck;
1097
+ this.notifyFlagCheckListeners(flag.flag, flagCheck);
1098
+ this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1099
+ });
869
1100
  this.setIsPending(false);
870
1101
  if (!resolved) {
871
1102
  resolved = true;
@@ -905,18 +1136,21 @@ var Schematic = class {
905
1136
  setIsPending = (isPending) => {
906
1137
  this.isPending = isPending;
907
1138
  this.isPendingListeners.forEach(
908
- (listener) => notifyListener(listener, isPending)
1139
+ (listener) => notifyPendingListener(listener, isPending)
909
1140
  );
910
1141
  };
1142
+ // flag checks state
1143
+ getFlagCheck = (flagKey) => {
1144
+ const contextStr = contextString(this.context);
1145
+ const checks = this.checks[contextStr] ?? {};
1146
+ return checks[flagKey];
1147
+ };
911
1148
  // flagValues state
912
1149
  getFlagValue = (flagKey) => {
913
- const values = this.getFlagValues();
914
- return values[flagKey];
915
- };
916
- getFlagValues = () => {
917
- const contextStr = contextString(this.context);
918
- return this.values[contextStr] ?? {};
1150
+ const check = this.getFlagCheck(flagKey);
1151
+ return check?.value;
919
1152
  };
1153
+ /** Register an event listener that will be notified with the boolean value for a given flag when this value changes */
920
1154
  addFlagValueListener = (flagKey, listener) => {
921
1155
  if (!(flagKey in this.flagValueListeners)) {
922
1156
  this.flagValueListeners[flagKey] = /* @__PURE__ */ new Set();
@@ -926,12 +1160,40 @@ var Schematic = class {
926
1160
  this.flagValueListeners[flagKey].delete(listener);
927
1161
  };
928
1162
  };
1163
+ /** Register an event listener that will be notified with the full flag check response for a given flag whenever this value changes */
1164
+ addFlagCheckListener = (flagKey, listener) => {
1165
+ if (!(flagKey in this.flagCheckListeners)) {
1166
+ this.flagCheckListeners[flagKey] = /* @__PURE__ */ new Set();
1167
+ }
1168
+ this.flagCheckListeners[flagKey].add(listener);
1169
+ return () => {
1170
+ this.flagCheckListeners[flagKey].delete(listener);
1171
+ };
1172
+ };
1173
+ notifyFlagCheckListeners = (flagKey, check) => {
1174
+ const listeners = this.flagCheckListeners?.[flagKey] ?? [];
1175
+ listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
1176
+ };
929
1177
  notifyFlagValueListeners = (flagKey, value) => {
930
1178
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
931
- listeners.forEach((listener) => notifyListener(listener, value));
1179
+ listeners.forEach((listener) => notifyFlagValueListener(listener, value));
932
1180
  };
933
1181
  };
934
- var notifyListener = (listener, value) => {
1182
+ var notifyPendingListener = (listener, value) => {
1183
+ if (listener.length > 0) {
1184
+ listener(value);
1185
+ } else {
1186
+ listener();
1187
+ }
1188
+ };
1189
+ var notifyFlagCheckListener = (listener, value) => {
1190
+ if (listener.length > 0) {
1191
+ listener(value);
1192
+ } else {
1193
+ listener();
1194
+ }
1195
+ };
1196
+ var notifyFlagValueListener = (listener, value) => {
935
1197
  if (listener.length > 0) {
936
1198
  listener(value);
937
1199
  } else {