@schematichq/schematic-react 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.
@@ -28,17 +28,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
30
  // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ RuleType: () => RuleType,
33
34
  Schematic: () => Schematic,
34
35
  SchematicProvider: () => SchematicProvider,
36
+ UsagePeriod: () => UsagePeriod,
35
37
  useSchematic: () => useSchematic,
36
38
  useSchematicContext: () => useSchematicContext,
37
39
  useSchematicEvents: () => useSchematicEvents,
38
40
  useSchematicFlag: () => useSchematicFlag,
41
+ useSchematicFlagCheck: () => useSchematicFlagCheck,
39
42
  useSchematicIsPending: () => useSchematicIsPending
40
43
  });
41
- module.exports = __toCommonJS(src_exports);
44
+ module.exports = __toCommonJS(index_exports);
42
45
 
43
46
  // node_modules/@schematichq/schematic-js/dist/schematic.esm.js
44
47
  var __create2 = Object.create;
@@ -70,11 +73,12 @@ var require_browser_polyfill = __commonJS({
70
73
  "node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
71
74
  (function(self2) {
72
75
  var irrelevant = function(exports2) {
73
- var global = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || typeof global !== "undefined" && global;
76
+ var g = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || // eslint-disable-next-line no-undef
77
+ typeof global !== "undefined" && global || {};
74
78
  var support = {
75
- searchParams: "URLSearchParams" in global,
76
- iterable: "Symbol" in global && "iterator" in Symbol,
77
- blob: "FileReader" in global && "Blob" in global && function() {
79
+ searchParams: "URLSearchParams" in g,
80
+ iterable: "Symbol" in g && "iterator" in Symbol,
81
+ blob: "FileReader" in g && "Blob" in g && function() {
78
82
  try {
79
83
  new Blob();
80
84
  return true;
@@ -82,8 +86,8 @@ var require_browser_polyfill = __commonJS({
82
86
  return false;
83
87
  }
84
88
  }(),
85
- formData: "FormData" in global,
86
- arrayBuffer: "ArrayBuffer" in global
89
+ formData: "FormData" in g,
90
+ arrayBuffer: "ArrayBuffer" in g
87
91
  };
88
92
  function isDataView(obj) {
89
93
  return obj && DataView.prototype.isPrototypeOf(obj);
@@ -141,6 +145,9 @@ var require_browser_polyfill = __commonJS({
141
145
  }, this);
142
146
  } else if (Array.isArray(headers)) {
143
147
  headers.forEach(function(header) {
148
+ if (header.length != 2) {
149
+ throw new TypeError("Headers constructor: expected name/value pair to be length 2, found" + header.length);
150
+ }
144
151
  this.append(header[0], header[1]);
145
152
  }, this);
146
153
  } else if (headers) {
@@ -200,6 +207,7 @@ var require_browser_polyfill = __commonJS({
200
207
  Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
201
208
  }
202
209
  function consumed(body) {
210
+ if (body._noBody) return;
203
211
  if (body.bodyUsed) {
204
212
  return Promise.reject(new TypeError("Already read"));
205
213
  }
@@ -224,14 +232,16 @@ var require_browser_polyfill = __commonJS({
224
232
  function readBlobAsText(blob) {
225
233
  var reader = new FileReader();
226
234
  var promise = fileReaderReady(reader);
227
- reader.readAsText(blob);
235
+ var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type);
236
+ var encoding = match ? match[1] : "utf-8";
237
+ reader.readAsText(blob, encoding);
228
238
  return promise;
229
239
  }
230
240
  function readArrayBufferAsText(buf) {
231
241
  var view = new Uint8Array(buf);
232
242
  var chars = new Array(view.length);
233
- for (var i2 = 0; i2 < view.length; i2++) {
234
- chars[i2] = String.fromCharCode(view[i2]);
243
+ for (var i = 0; i < view.length; i++) {
244
+ chars[i] = String.fromCharCode(view[i]);
235
245
  }
236
246
  return chars.join("");
237
247
  }
@@ -250,6 +260,7 @@ var require_browser_polyfill = __commonJS({
250
260
  this.bodyUsed = this.bodyUsed;
251
261
  this._bodyInit = body;
252
262
  if (!body) {
263
+ this._noBody = true;
253
264
  this._bodyText = "";
254
265
  } else if (typeof body === "string") {
255
266
  this._bodyText = body;
@@ -293,27 +304,28 @@ var require_browser_polyfill = __commonJS({
293
304
  return Promise.resolve(new Blob([this._bodyText]));
294
305
  }
295
306
  };
296
- this.arrayBuffer = function() {
297
- if (this._bodyArrayBuffer) {
298
- var isConsumed = consumed(this);
299
- if (isConsumed) {
300
- return isConsumed;
301
- }
302
- if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
303
- return Promise.resolve(
304
- this._bodyArrayBuffer.buffer.slice(
305
- this._bodyArrayBuffer.byteOffset,
306
- this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
307
- )
308
- );
309
- } else {
310
- return Promise.resolve(this._bodyArrayBuffer);
311
- }
307
+ }
308
+ this.arrayBuffer = function() {
309
+ if (this._bodyArrayBuffer) {
310
+ var isConsumed = consumed(this);
311
+ if (isConsumed) {
312
+ return isConsumed;
313
+ } else if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
314
+ return Promise.resolve(
315
+ this._bodyArrayBuffer.buffer.slice(
316
+ this._bodyArrayBuffer.byteOffset,
317
+ this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
318
+ )
319
+ );
312
320
  } else {
313
- return this.blob().then(readBlobAsArrayBuffer);
321
+ return Promise.resolve(this._bodyArrayBuffer);
314
322
  }
315
- };
316
- }
323
+ } else if (support.blob) {
324
+ return this.blob().then(readBlobAsArrayBuffer);
325
+ } else {
326
+ throw new Error("could not read as ArrayBuffer");
327
+ }
328
+ };
317
329
  this.text = function() {
318
330
  var rejected = consumed(this);
319
331
  if (rejected) {
@@ -339,7 +351,7 @@ var require_browser_polyfill = __commonJS({
339
351
  };
340
352
  return this;
341
353
  }
342
- var methods = ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"];
354
+ var methods = ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"];
343
355
  function normalizeMethod(method) {
344
356
  var upcased = method.toUpperCase();
345
357
  return methods.indexOf(upcased) > -1 ? upcased : method;
@@ -375,7 +387,12 @@ var require_browser_polyfill = __commonJS({
375
387
  }
376
388
  this.method = normalizeMethod(options.method || this.method || "GET");
377
389
  this.mode = options.mode || this.mode || null;
378
- this.signal = options.signal || this.signal;
390
+ this.signal = options.signal || this.signal || function() {
391
+ if ("AbortController" in g) {
392
+ var ctrl = new AbortController();
393
+ return ctrl.signal;
394
+ }
395
+ }();
379
396
  this.referrer = null;
380
397
  if ((this.method === "GET" || this.method === "HEAD") && body) {
381
398
  throw new TypeError("Body not allowed for GET or HEAD requests");
@@ -418,7 +435,11 @@ var require_browser_polyfill = __commonJS({
418
435
  var key = parts.shift().trim();
419
436
  if (key) {
420
437
  var value = parts.join(":").trim();
421
- headers.append(key, value);
438
+ try {
439
+ headers.append(key, value);
440
+ } catch (error) {
441
+ console.warn("Response " + error.message);
442
+ }
422
443
  }
423
444
  });
424
445
  return headers;
@@ -433,6 +454,9 @@ var require_browser_polyfill = __commonJS({
433
454
  }
434
455
  this.type = "default";
435
456
  this.status = options.status === void 0 ? 200 : options.status;
457
+ if (this.status < 200 || this.status > 599) {
458
+ throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");
459
+ }
436
460
  this.ok = this.status >= 200 && this.status < 300;
437
461
  this.statusText = options.statusText === void 0 ? "" : "" + options.statusText;
438
462
  this.headers = new Headers(options.headers);
@@ -449,7 +473,9 @@ var require_browser_polyfill = __commonJS({
449
473
  });
450
474
  };
451
475
  Response.error = function() {
452
- var response = new Response(null, { status: 0, statusText: "" });
476
+ var response = new Response(null, { status: 200, statusText: "" });
477
+ response.ok = false;
478
+ response.status = 0;
453
479
  response.type = "error";
454
480
  return response;
455
481
  };
@@ -460,7 +486,7 @@ var require_browser_polyfill = __commonJS({
460
486
  }
461
487
  return new Response(null, { status, headers: { location: url } });
462
488
  };
463
- exports2.DOMException = global.DOMException;
489
+ exports2.DOMException = g.DOMException;
464
490
  try {
465
491
  new exports2.DOMException();
466
492
  } catch (err) {
@@ -485,10 +511,14 @@ var require_browser_polyfill = __commonJS({
485
511
  }
486
512
  xhr.onload = function() {
487
513
  var options = {
488
- status: xhr.status,
489
514
  statusText: xhr.statusText,
490
515
  headers: parseHeaders(xhr.getAllResponseHeaders() || "")
491
516
  };
517
+ if (request.url.indexOf("file://") === 0 && (xhr.status < 200 || xhr.status > 599)) {
518
+ options.status = 200;
519
+ } else {
520
+ options.status = xhr.status;
521
+ }
492
522
  options.url = "responseURL" in xhr ? xhr.responseURL : options.headers.get("X-Request-URL");
493
523
  var body = "response" in xhr ? xhr.response : xhr.responseText;
494
524
  setTimeout(function() {
@@ -502,7 +532,7 @@ var require_browser_polyfill = __commonJS({
502
532
  };
503
533
  xhr.ontimeout = function() {
504
534
  setTimeout(function() {
505
- reject(new TypeError("Network request failed"));
535
+ reject(new TypeError("Network request timed out"));
506
536
  }, 0);
507
537
  };
508
538
  xhr.onabort = function() {
@@ -512,7 +542,7 @@ var require_browser_polyfill = __commonJS({
512
542
  };
513
543
  function fixUrl(url) {
514
544
  try {
515
- return url === "" && global.location.href ? global.location.href : url;
545
+ return url === "" && g.location.href ? g.location.href : url;
516
546
  } catch (e) {
517
547
  return url;
518
548
  }
@@ -526,14 +556,21 @@ var require_browser_polyfill = __commonJS({
526
556
  if ("responseType" in xhr) {
527
557
  if (support.blob) {
528
558
  xhr.responseType = "blob";
529
- } else if (support.arrayBuffer && request.headers.get("Content-Type") && request.headers.get("Content-Type").indexOf("application/octet-stream") !== -1) {
559
+ } else if (support.arrayBuffer) {
530
560
  xhr.responseType = "arraybuffer";
531
561
  }
532
562
  }
533
- if (init && typeof init.headers === "object" && !(init.headers instanceof Headers)) {
563
+ if (init && typeof init.headers === "object" && !(init.headers instanceof Headers || g.Headers && init.headers instanceof g.Headers)) {
564
+ var names = [];
534
565
  Object.getOwnPropertyNames(init.headers).forEach(function(name) {
566
+ names.push(normalizeName(name));
535
567
  xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
536
568
  });
569
+ request.headers.forEach(function(value, name) {
570
+ if (names.indexOf(name) === -1) {
571
+ xhr.setRequestHeader(name, value);
572
+ }
573
+ });
537
574
  } else {
538
575
  request.headers.forEach(function(value, name) {
539
576
  xhr.setRequestHeader(name, value);
@@ -551,11 +588,11 @@ var require_browser_polyfill = __commonJS({
551
588
  });
552
589
  }
553
590
  fetch2.polyfill = true;
554
- if (!global.fetch) {
555
- global.fetch = fetch2;
556
- global.Headers = Headers;
557
- global.Request = Request;
558
- global.Response = Response;
591
+ if (!g.fetch) {
592
+ g.fetch = fetch2;
593
+ g.Headers = Headers;
594
+ g.Request = Request;
595
+ g.Response = Response;
559
596
  }
560
597
  exports2.Headers = Headers;
561
598
  exports2.Request = Request;
@@ -567,10 +604,9 @@ var require_browser_polyfill = __commonJS({
567
604
  }
568
605
  });
569
606
  var byteToHex = [];
570
- for (i = 0; i < 256; ++i) {
607
+ for (let i = 0; i < 256; ++i) {
571
608
  byteToHex.push((i + 256).toString(16).slice(1));
572
609
  }
573
- var i;
574
610
  function unsafeStringify(arr, offset = 0) {
575
611
  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();
576
612
  }
@@ -578,29 +614,33 @@ var getRandomValues;
578
614
  var rnds8 = new Uint8Array(16);
579
615
  function rng() {
580
616
  if (!getRandomValues) {
581
- getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
582
- if (!getRandomValues) {
617
+ if (typeof crypto === "undefined" || !crypto.getRandomValues) {
583
618
  throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
584
619
  }
620
+ getRandomValues = crypto.getRandomValues.bind(crypto);
585
621
  }
586
622
  return getRandomValues(rnds8);
587
623
  }
588
624
  var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
589
- var native_default = {
590
- randomUUID
591
- };
625
+ var native_default = { randomUUID };
592
626
  function v4(options, buf, offset) {
593
627
  if (native_default.randomUUID && !buf && !options) {
594
628
  return native_default.randomUUID();
595
629
  }
596
630
  options = options || {};
597
- var rnds = options.random || (options.rng || rng)();
631
+ const rnds = options.random ?? options.rng?.() ?? rng();
632
+ if (rnds.length < 16) {
633
+ throw new Error("Random bytes length must be >= 16");
634
+ }
598
635
  rnds[6] = rnds[6] & 15 | 64;
599
636
  rnds[8] = rnds[8] & 63 | 128;
600
637
  if (buf) {
601
638
  offset = offset || 0;
602
- for (var i2 = 0; i2 < 16; ++i2) {
603
- buf[offset + i2] = rnds[i2];
639
+ if (offset < 0 || offset + 16 > buf.length) {
640
+ throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
641
+ }
642
+ for (let i = 0; i < 16; ++i) {
643
+ buf[offset + i] = rnds[i];
604
644
  }
605
645
  return buf;
606
646
  }
@@ -608,6 +648,117 @@ function v4(options, buf, offset) {
608
648
  }
609
649
  var v4_default = v4;
610
650
  var import_polyfill = __toESM2(require_browser_polyfill());
651
+ function CheckFlagResponseDataFromJSON(json) {
652
+ return CheckFlagResponseDataFromJSONTyped(json, false);
653
+ }
654
+ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
655
+ if (json == null) {
656
+ return json;
657
+ }
658
+ return {
659
+ companyId: json["company_id"] == null ? void 0 : json["company_id"],
660
+ error: json["error"] == null ? void 0 : json["error"],
661
+ featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
662
+ featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
663
+ featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
664
+ featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
665
+ flag: json["flag"],
666
+ flagId: json["flag_id"] == null ? void 0 : json["flag_id"],
667
+ reason: json["reason"],
668
+ ruleId: json["rule_id"] == null ? void 0 : json["rule_id"],
669
+ ruleType: json["rule_type"] == null ? void 0 : json["rule_type"],
670
+ userId: json["user_id"] == null ? void 0 : json["user_id"],
671
+ value: json["value"]
672
+ };
673
+ }
674
+ function CheckFlagResponseFromJSON(json) {
675
+ return CheckFlagResponseFromJSONTyped(json, false);
676
+ }
677
+ function CheckFlagResponseFromJSONTyped(json, ignoreDiscriminator) {
678
+ if (json == null) {
679
+ return json;
680
+ }
681
+ return {
682
+ data: CheckFlagResponseDataFromJSON(json["data"]),
683
+ params: json["params"]
684
+ };
685
+ }
686
+ function CheckFlagsResponseDataFromJSON(json) {
687
+ return CheckFlagsResponseDataFromJSONTyped(json, false);
688
+ }
689
+ function CheckFlagsResponseDataFromJSONTyped(json, ignoreDiscriminator) {
690
+ if (json == null) {
691
+ return json;
692
+ }
693
+ return {
694
+ flags: json["flags"].map(CheckFlagResponseDataFromJSON)
695
+ };
696
+ }
697
+ function CheckFlagsResponseFromJSON(json) {
698
+ return CheckFlagsResponseFromJSONTyped(json, false);
699
+ }
700
+ function CheckFlagsResponseFromJSONTyped(json, ignoreDiscriminator) {
701
+ if (json == null) {
702
+ return json;
703
+ }
704
+ return {
705
+ data: CheckFlagsResponseDataFromJSON(json["data"]),
706
+ params: json["params"]
707
+ };
708
+ }
709
+ var RuleType = /* @__PURE__ */ ((RuleType2) => {
710
+ RuleType2["GLOBAL_OVERRIDE"] = "global_override";
711
+ RuleType2["COMPANY_OVERRIDE"] = "company_override";
712
+ RuleType2["COMPANY_OVERRIDE_USAGE_EXCEEDED"] = "company_override_usage_exceeded";
713
+ RuleType2["PLAN_ENTITLEMENT"] = "plan_entitlement";
714
+ RuleType2["PLAN_ENTITLEMENT_USAGE_EXCEEDED"] = "plan_entitlement_usage_exceeded";
715
+ RuleType2["STANDARD"] = "standard";
716
+ RuleType2["DEFAULT"] = "default";
717
+ return RuleType2;
718
+ })(RuleType || {});
719
+ var UsagePeriod = /* @__PURE__ */ ((UsagePeriod2) => {
720
+ UsagePeriod2["ALL_TIME"] = "all_time";
721
+ UsagePeriod2["CURRENT_DAY"] = "current_day";
722
+ UsagePeriod2["CURRENT_MONTH"] = "current_month";
723
+ UsagePeriod2["CURRENT_WEEK"] = "current_week";
724
+ return UsagePeriod2;
725
+ })(UsagePeriod || {});
726
+ var CheckFlagReturnFromJSON = (json) => {
727
+ const {
728
+ companyId,
729
+ error,
730
+ featureAllocation,
731
+ featureUsage,
732
+ featureUsagePeriod,
733
+ featureUsageResetAt,
734
+ flag,
735
+ flagId,
736
+ reason,
737
+ ruleId,
738
+ ruleType,
739
+ userId,
740
+ value
741
+ } = CheckFlagResponseDataFromJSON(json);
742
+ const featureUsageExceeded = !value && // if flag is not false, then we haven't exceeded usage
743
+ (ruleType == "company_override_usage_exceeded" || // if the rule type is one of these, then we have exceeded usage
744
+ ruleType == "plan_entitlement_usage_exceeded");
745
+ return {
746
+ featureUsageExceeded,
747
+ companyId: companyId == null ? void 0 : companyId,
748
+ error: error == null ? void 0 : error,
749
+ featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
750
+ featureUsage: featureUsage == null ? void 0 : featureUsage,
751
+ featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
752
+ featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
753
+ flag,
754
+ flagId: flagId == null ? void 0 : flagId,
755
+ reason,
756
+ ruleId: ruleId == null ? void 0 : ruleId,
757
+ ruleType: ruleType == null ? void 0 : ruleType,
758
+ userId: userId == null ? void 0 : userId,
759
+ value
760
+ };
761
+ };
611
762
  function contextString(context) {
612
763
  const sortedContext = Object.keys(context).reduce((acc, key) => {
613
764
  const sortedKeys = Object.keys(
@@ -631,19 +782,18 @@ var Schematic = class {
631
782
  context = {};
632
783
  eventQueue;
633
784
  eventUrl = "https://c.schematichq.com";
634
- flagListener;
785
+ flagCheckListeners = {};
635
786
  flagValueListeners = {};
636
787
  isPending = true;
637
788
  isPendingListeners = /* @__PURE__ */ new Set();
638
789
  storage;
639
790
  useWebSocket = false;
640
- values = {};
791
+ checks = {};
641
792
  webSocketUrl = "wss://api.schematichq.com";
642
793
  constructor(apiKey, options) {
643
794
  this.apiKey = apiKey;
644
795
  this.eventQueue = [];
645
796
  this.useWebSocket = options?.useWebSocket ?? false;
646
- this.flagListener = options?.flagListener;
647
797
  if (options?.additionalHeaders) {
648
798
  this.additionalHeaders = options.additionalHeaders;
649
799
  }
@@ -667,37 +817,89 @@ var Schematic = class {
667
817
  });
668
818
  }
669
819
  }
670
- // Get value for a single flag
671
- // If in websocket mode, return the local value, otherwise make an API call
820
+ /**
821
+ * Get value for a single flag.
822
+ * In WebSocket mode, returns cached values if connection is active, otherwise establishes
823
+ * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
824
+ * connection fails.
825
+ * In REST mode, makes an API call for each check.
826
+ */
672
827
  async checkFlag(options) {
673
828
  const { fallback = false, key } = options;
674
829
  const context = options.context || this.context;
675
- if (this.useWebSocket) {
676
- const contextVals = this.values[contextString(context)] ?? {};
677
- return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
830
+ const contextStr = contextString(context);
831
+ if (!this.useWebSocket) {
832
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
833
+ return fetch(requestUrl, {
834
+ method: "POST",
835
+ headers: {
836
+ ...this.additionalHeaders ?? {},
837
+ "Content-Type": "application/json;charset=UTF-8",
838
+ "X-Schematic-Api-Key": this.apiKey
839
+ },
840
+ body: JSON.stringify(context)
841
+ }).then((response) => {
842
+ if (!response.ok) {
843
+ throw new Error("Network response was not ok");
844
+ }
845
+ return response.json();
846
+ }).then((response) => {
847
+ return CheckFlagResponseFromJSON(response).data.value;
848
+ }).catch((error) => {
849
+ console.error("There was a problem with the fetch operation:", error);
850
+ return fallback;
851
+ });
678
852
  }
679
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
680
- return fetch(requestUrl, {
681
- method: "POST",
682
- headers: {
683
- ...this.additionalHeaders ?? {},
684
- "Content-Type": "application/json;charset=UTF-8",
685
- "X-Schematic-Api-Key": this.apiKey
686
- },
687
- body: JSON.stringify(context)
688
- }).then((response) => {
853
+ try {
854
+ const existingVals = this.checks[contextStr];
855
+ if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
856
+ return existingVals[key].value;
857
+ }
858
+ try {
859
+ await this.setContext(context);
860
+ } catch (error) {
861
+ console.error(
862
+ "WebSocket connection failed, falling back to REST:",
863
+ error
864
+ );
865
+ return this.fallbackToRest(key, context, fallback);
866
+ }
867
+ const contextVals = this.checks[contextStr] ?? {};
868
+ return contextVals[key]?.value ?? fallback;
869
+ } catch (error) {
870
+ console.error("Unexpected error in checkFlag:", error);
871
+ return fallback;
872
+ }
873
+ }
874
+ /**
875
+ * Helper method for falling back to REST API when WebSocket connection fails
876
+ */
877
+ async fallbackToRest(key, context, fallback) {
878
+ try {
879
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
880
+ const response = await fetch(requestUrl, {
881
+ method: "POST",
882
+ headers: {
883
+ ...this.additionalHeaders ?? {},
884
+ "Content-Type": "application/json;charset=UTF-8",
885
+ "X-Schematic-Api-Key": this.apiKey
886
+ },
887
+ body: JSON.stringify(context)
888
+ });
689
889
  if (!response.ok) {
690
890
  throw new Error("Network response was not ok");
691
891
  }
692
- return response.json();
693
- }).then((data) => {
694
- return data.data.value;
695
- }).catch((error) => {
696
- console.error("There was a problem with the fetch operation:", error);
892
+ const data = CheckFlagResponseFromJSON(await response.json());
893
+ return data?.data?.value ?? false;
894
+ } catch (error) {
895
+ console.error("REST API call failed, using fallback value:", error);
697
896
  return fallback;
698
- });
897
+ }
699
898
  }
700
- // Make an API call to fetch all flag values for a given context (use if not in websocket mode)
899
+ /**
900
+ * Make an API call to fetch all flag values for a given context.
901
+ * Recommended for use in REST mode only.
902
+ */
701
903
  checkFlags = async (context) => {
702
904
  context = context || this.context;
703
905
  const requestUrl = `${this.apiUrl}/flags/check`;
@@ -715,8 +917,9 @@ var Schematic = class {
715
917
  throw new Error("Network response was not ok");
716
918
  }
717
919
  return response.json();
718
- }).then((data) => {
719
- return (data?.data?.flags ?? []).reduce(
920
+ }).then((responseJson) => {
921
+ const resp = CheckFlagsResponseFromJSON(responseJson);
922
+ return (resp?.data?.flags ?? []).reduce(
720
923
  (accum, flag) => {
721
924
  accum[flag.flag] = flag.value;
722
925
  return accum;
@@ -725,21 +928,33 @@ var Schematic = class {
725
928
  );
726
929
  }).catch((error) => {
727
930
  console.error("There was a problem with the fetch operation:", error);
728
- return false;
931
+ return {};
729
932
  });
730
933
  };
731
- // Send an identify event
934
+ /**
935
+ * Send an identify event.
936
+ * This will set the context for subsequent flag evaluation and events, and will also
937
+ * send an identify event to the Schematic API which will upsert a user and company.
938
+ */
732
939
  identify = (body) => {
733
- this.setContext({
734
- company: body.company?.keys,
735
- user: body.keys
736
- });
940
+ try {
941
+ this.setContext({
942
+ company: body.company?.keys,
943
+ user: body.keys
944
+ });
945
+ } catch (error) {
946
+ console.error("Error setting context:", error);
947
+ }
737
948
  return this.handleEvent("identify", body);
738
949
  };
739
- // Set the flag evaluation context; if the context has changed,
740
- // this will open a websocket connection (if not already open)
741
- // and submit this context. The promise will resolve when the
742
- // websocket sends back an initial set of flag values.
950
+ /**
951
+ * Set the flag evaluation context.
952
+ * In WebSocket mode, this will:
953
+ * 1. Open a websocket connection if not already open
954
+ * 2. Send the context to the server
955
+ * 3. Wait for initial flag values to be returned
956
+ * The promise resolves when initial flag values are received.
957
+ */
743
958
  setContext = async (context) => {
744
959
  if (!this.useWebSocket) {
745
960
  this.context = context;
@@ -753,10 +968,14 @@ var Schematic = class {
753
968
  const socket = await this.conn;
754
969
  await this.wsSendMessage(socket, context);
755
970
  } catch (error) {
756
- console.error("Error setting Schematic context:", error);
971
+ console.error("Failed to establish WebSocket connection:", error);
972
+ throw error;
757
973
  }
758
974
  };
759
- // Send track event
975
+ /**
976
+ * Send a track event
977
+ * Track usage for a company and/or user.
978
+ */
760
979
  track = (body) => {
761
980
  const { company, user, event, traits } = body;
762
981
  return this.handleEvent("track", {
@@ -828,6 +1047,9 @@ var Schematic = class {
828
1047
  /**
829
1048
  * Websocket management
830
1049
  */
1050
+ /**
1051
+ * If using websocket mode, close the connection when done.
1052
+ */
831
1053
  cleanup = async () => {
832
1054
  if (this.conn) {
833
1055
  try {
@@ -868,18 +1090,15 @@ var Schematic = class {
868
1090
  let resolved = false;
869
1091
  const messageHandler = (event) => {
870
1092
  const message = JSON.parse(event.data);
871
- if (!(contextString(context) in this.values)) {
872
- this.values[contextString(context)] = {};
873
- }
874
- (message.flags ?? []).forEach(
875
- (flag) => {
876
- this.values[contextString(context)][flag.flag] = flag.value;
877
- this.notifyFlagValueListeners(flag.flag, flag.value);
878
- }
879
- );
880
- if (this.flagListener) {
881
- this.flagListener(this.getFlagValues());
1093
+ if (!(contextString(context) in this.checks)) {
1094
+ this.checks[contextString(context)] = {};
882
1095
  }
1096
+ (message.flags ?? []).forEach((flag) => {
1097
+ const flagCheck = CheckFlagReturnFromJSON(flag);
1098
+ this.checks[contextString(context)][flagCheck.flag] = flagCheck;
1099
+ this.notifyFlagCheckListeners(flag.flag, flagCheck);
1100
+ this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1101
+ });
883
1102
  this.setIsPending(false);
884
1103
  if (!resolved) {
885
1104
  resolved = true;
@@ -919,18 +1138,21 @@ var Schematic = class {
919
1138
  setIsPending = (isPending) => {
920
1139
  this.isPending = isPending;
921
1140
  this.isPendingListeners.forEach(
922
- (listener) => notifyListener(listener, isPending)
1141
+ (listener) => notifyPendingListener(listener, isPending)
923
1142
  );
924
1143
  };
1144
+ // flag checks state
1145
+ getFlagCheck = (flagKey) => {
1146
+ const contextStr = contextString(this.context);
1147
+ const checks = this.checks[contextStr] ?? {};
1148
+ return checks[flagKey];
1149
+ };
925
1150
  // flagValues state
926
1151
  getFlagValue = (flagKey) => {
927
- const values = this.getFlagValues();
928
- return values[flagKey];
929
- };
930
- getFlagValues = () => {
931
- const contextStr = contextString(this.context);
932
- return this.values[contextStr] ?? {};
1152
+ const check = this.getFlagCheck(flagKey);
1153
+ return check?.value;
933
1154
  };
1155
+ /** Register an event listener that will be notified with the boolean value for a given flag when this value changes */
934
1156
  addFlagValueListener = (flagKey, listener) => {
935
1157
  if (!(flagKey in this.flagValueListeners)) {
936
1158
  this.flagValueListeners[flagKey] = /* @__PURE__ */ new Set();
@@ -940,12 +1162,40 @@ var Schematic = class {
940
1162
  this.flagValueListeners[flagKey].delete(listener);
941
1163
  };
942
1164
  };
1165
+ /** Register an event listener that will be notified with the full flag check response for a given flag whenever this value changes */
1166
+ addFlagCheckListener = (flagKey, listener) => {
1167
+ if (!(flagKey in this.flagCheckListeners)) {
1168
+ this.flagCheckListeners[flagKey] = /* @__PURE__ */ new Set();
1169
+ }
1170
+ this.flagCheckListeners[flagKey].add(listener);
1171
+ return () => {
1172
+ this.flagCheckListeners[flagKey].delete(listener);
1173
+ };
1174
+ };
1175
+ notifyFlagCheckListeners = (flagKey, check) => {
1176
+ const listeners = this.flagCheckListeners?.[flagKey] ?? [];
1177
+ listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
1178
+ };
943
1179
  notifyFlagValueListeners = (flagKey, value) => {
944
1180
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
945
- listeners.forEach((listener) => notifyListener(listener, value));
1181
+ listeners.forEach((listener) => notifyFlagValueListener(listener, value));
946
1182
  };
947
1183
  };
948
- var notifyListener = (listener, value) => {
1184
+ var notifyPendingListener = (listener, value) => {
1185
+ if (listener.length > 0) {
1186
+ listener(value);
1187
+ } else {
1188
+ listener();
1189
+ }
1190
+ };
1191
+ var notifyFlagCheckListener = (listener, value) => {
1192
+ if (listener.length > 0) {
1193
+ listener(value);
1194
+ } else {
1195
+ listener();
1196
+ }
1197
+ };
1198
+ var notifyFlagValueListener = (listener, value) => {
949
1199
  if (listener.length > 0) {
950
1200
  listener(value);
951
1201
  } else {
@@ -965,16 +1215,19 @@ var SchematicProvider = ({
965
1215
  publishableKey,
966
1216
  ...clientOpts
967
1217
  }) => {
1218
+ const initialOptsRef = (0, import_react.useRef)({
1219
+ publishableKey,
1220
+ useWebSocket: clientOpts.useWebSocket ?? true,
1221
+ ...clientOpts
1222
+ });
968
1223
  const client = (0, import_react.useMemo)(() => {
969
- const { useWebSocket = true } = clientOpts;
970
1224
  if (providedClient) {
971
1225
  return providedClient;
972
1226
  }
973
- return new Schematic(publishableKey, {
974
- useWebSocket,
975
- ...clientOpts
1227
+ return new Schematic(initialOptsRef.current.publishableKey, {
1228
+ ...initialOptsRef.current
976
1229
  });
977
- }, [providedClient, publishableKey, clientOpts]);
1230
+ }, [providedClient]);
978
1231
  (0, import_react.useEffect)(() => {
979
1232
  return () => {
980
1233
  if (!providedClient) {
@@ -1046,6 +1299,27 @@ var useSchematicFlag = (key, opts) => {
1046
1299
  }, [client, key, fallback]);
1047
1300
  return (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot);
1048
1301
  };
1302
+ var useSchematicFlagCheck = (key, opts) => {
1303
+ const client = useSchematicClient(opts);
1304
+ const fallback = opts?.fallback ?? false;
1305
+ const fallbackCheck = (0, import_react2.useMemo)(
1306
+ () => ({
1307
+ flag: key,
1308
+ reason: "Fallback",
1309
+ value: fallback
1310
+ }),
1311
+ [key, fallback]
1312
+ );
1313
+ const subscribe = (0, import_react2.useCallback)(
1314
+ (callback) => client.addFlagCheckListener(key, callback),
1315
+ [client, key]
1316
+ );
1317
+ const getSnapshot = (0, import_react2.useCallback)(() => {
1318
+ const check = client.getFlagCheck(key);
1319
+ return check ?? fallbackCheck;
1320
+ }, [client, key, fallbackCheck]);
1321
+ return (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot);
1322
+ };
1049
1323
  var useSchematicIsPending = (opts) => {
1050
1324
  const client = useSchematicClient(opts);
1051
1325
  const subscribe = (0, import_react2.useCallback)(