@shipeasy/sdk 4.5.0 → 5.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.
- package/README.md +174 -13
- package/dist/client/index.d.mts +110 -17
- package/dist/client/index.d.ts +110 -17
- package/dist/client/index.js +170 -73
- package/dist/client/index.mjs +169 -73
- package/dist/server/index.d.mts +170 -16
- package/dist/server/index.d.ts +170 -16
- package/dist/server/index.js +223 -48
- package/dist/server/index.mjs +228 -48
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -31,7 +31,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var server_exports = {};
|
|
32
32
|
__export(server_exports, {
|
|
33
33
|
ANON_ID_COOKIE: () => ANON_ID_COOKIE,
|
|
34
|
+
FLAG_REASONS: () => FLAG_REASONS,
|
|
34
35
|
FlagsClient: () => FlagsClient,
|
|
36
|
+
_murmur3ForTests: () => _murmur3ForTests,
|
|
35
37
|
_resetShipeasyServerForTests: () => _resetShipeasyServerForTests,
|
|
36
38
|
configureShipeasyServer: () => configureShipeasyServer,
|
|
37
39
|
fetchLabelsForSSR: () => fetchLabelsForSSR,
|
|
@@ -128,25 +130,29 @@ function causesThe(subject) {
|
|
|
128
130
|
};
|
|
129
131
|
}
|
|
130
132
|
function violation(name) {
|
|
131
|
-
|
|
132
|
-
__seViolation: true,
|
|
133
|
-
violationName: String(name),
|
|
134
|
-
...msg !== void 0 ? { violationMessage: msg } : {},
|
|
135
|
-
message(m) {
|
|
136
|
-
return make(String(m));
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
return make();
|
|
133
|
+
return { __seViolation: true, violationName: String(name) };
|
|
140
134
|
}
|
|
141
135
|
function isViolation(p) {
|
|
142
136
|
return typeof p === "object" && p !== null && p.__seViolation === true;
|
|
143
137
|
}
|
|
144
138
|
var EXPECTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-expected");
|
|
145
|
-
function
|
|
139
|
+
function readExpectedMark(err) {
|
|
140
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
141
|
+
const v = err[EXPECTED_SYM];
|
|
142
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
143
|
+
}
|
|
144
|
+
function markExpected(err, because, extras) {
|
|
146
145
|
if (typeof err !== "object" || err === null) return;
|
|
146
|
+
const prev = readExpectedMark(err);
|
|
147
|
+
const clean = sanitizeExtras(extras);
|
|
148
|
+
const merged = prev?.extras || clean ? { ...prev?.extras, ...clean } : void 0;
|
|
149
|
+
const mark = {
|
|
150
|
+
because: String(because),
|
|
151
|
+
...merged ? { extras: merged } : {}
|
|
152
|
+
};
|
|
147
153
|
try {
|
|
148
154
|
Object.defineProperty(err, EXPECTED_SYM, {
|
|
149
|
-
value:
|
|
155
|
+
value: mark,
|
|
150
156
|
enumerable: false,
|
|
151
157
|
configurable: true
|
|
152
158
|
});
|
|
@@ -227,7 +233,7 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlat
|
|
|
227
233
|
let kind;
|
|
228
234
|
if (isViolation(problem)) {
|
|
229
235
|
errorType = problem.violationName;
|
|
230
|
-
message = problem.
|
|
236
|
+
message = problem.violationName;
|
|
231
237
|
stack = captureCallsiteStack();
|
|
232
238
|
kind = kindOverride ?? "violation";
|
|
233
239
|
} else if (problem instanceof Error) {
|
|
@@ -310,19 +316,21 @@ function startSeeChain(getProblem, dispatch) {
|
|
|
310
316
|
return { causes_the: start, causesThe: start };
|
|
311
317
|
}
|
|
312
318
|
function startSeeViolationChain(name, dispatch) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
return startSeeChain(() => violation(name), dispatch);
|
|
320
|
+
}
|
|
321
|
+
function startControlFlowChain(err) {
|
|
322
|
+
return {
|
|
323
|
+
because(reason) {
|
|
324
|
+
markExpected(err, reason);
|
|
325
|
+
const tail = {
|
|
326
|
+
extras(x) {
|
|
327
|
+
markExpected(err, reason, x);
|
|
328
|
+
return tail;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
return tail;
|
|
323
332
|
}
|
|
324
333
|
};
|
|
325
|
-
return chain;
|
|
326
334
|
}
|
|
327
335
|
function topStackLine(stack) {
|
|
328
336
|
if (!stack) return "";
|
|
@@ -356,6 +364,9 @@ var SeeLimiter = class {
|
|
|
356
364
|
var version = "4.0.0";
|
|
357
365
|
var C1 = 3432918353;
|
|
358
366
|
var C2 = 461845907;
|
|
367
|
+
function _murmur3ForTests(key) {
|
|
368
|
+
return murmur3(key);
|
|
369
|
+
}
|
|
359
370
|
function murmur3(key) {
|
|
360
371
|
const bytes = new TextEncoder().encode(key);
|
|
361
372
|
const len = bytes.length;
|
|
@@ -396,6 +407,14 @@ function murmur3(key) {
|
|
|
396
407
|
h1 ^= h1 >>> 16;
|
|
397
408
|
return h1 >>> 0;
|
|
398
409
|
}
|
|
410
|
+
var FLAG_REASONS = [
|
|
411
|
+
"CLIENT_NOT_READY",
|
|
412
|
+
"FLAG_NOT_FOUND",
|
|
413
|
+
"OFF",
|
|
414
|
+
"OVERRIDE",
|
|
415
|
+
"RULE_MATCH",
|
|
416
|
+
"DEFAULT"
|
|
417
|
+
];
|
|
399
418
|
function isEnabled(v) {
|
|
400
419
|
return v === 1 || v === true;
|
|
401
420
|
}
|
|
@@ -503,7 +522,7 @@ function parseOverrides(rawUrl) {
|
|
|
503
522
|
}
|
|
504
523
|
return { gates, configs, experiments };
|
|
505
524
|
}
|
|
506
|
-
var FlagsClient = class {
|
|
525
|
+
var FlagsClient = class _FlagsClient {
|
|
507
526
|
apiKey;
|
|
508
527
|
baseUrl;
|
|
509
528
|
env;
|
|
@@ -516,47 +535,155 @@ var FlagsClient = class {
|
|
|
516
535
|
pollInterval = 30;
|
|
517
536
|
timer = null;
|
|
518
537
|
initialized = false;
|
|
538
|
+
// Test mode: built by `FlagsClient.forTesting()`. When set, init()/initOnce()
|
|
539
|
+
// never fetch, track() is a no-op, and telemetry is off — the client is a
|
|
540
|
+
// fully self-contained, network-free seam for unit tests.
|
|
541
|
+
testMode;
|
|
542
|
+
// Programmatic overrides (Statsig-style). Set on any client via
|
|
543
|
+
// overrideFlag/overrideConfig/overrideExperiment; they win over the fetched
|
|
544
|
+
// blob in getFlag/getConfig/getExperiment. Cleared by clearOverrides().
|
|
545
|
+
flagOverrides = /* @__PURE__ */ new Map();
|
|
546
|
+
configOverrides = /* @__PURE__ */ new Map();
|
|
547
|
+
experimentOverrides = /* @__PURE__ */ new Map();
|
|
548
|
+
// Change listeners fired after a background poll returns NEW data (200, not
|
|
549
|
+
// 304). Never fired in testMode/offline (no polling happens there).
|
|
550
|
+
changeListeners = /* @__PURE__ */ new Set();
|
|
519
551
|
constructor(opts) {
|
|
520
552
|
this.apiKey = opts.apiKey;
|
|
521
553
|
this.baseUrl = (opts.baseUrl ?? "https://cdn.shipeasy.ai").replace(/\/$/, "");
|
|
522
554
|
this.env = opts.env ?? "prod";
|
|
555
|
+
this.testMode = opts.testMode === true;
|
|
523
556
|
this.telemetry = new Telemetry({
|
|
524
557
|
endpoint: opts.telemetryUrl ?? DEFAULT_TELEMETRY_URL,
|
|
525
558
|
sdkKey: this.apiKey,
|
|
526
559
|
side: "server",
|
|
527
560
|
env: this.env,
|
|
528
|
-
|
|
561
|
+
// Test mode never talks to the network — telemetry off regardless of opt.
|
|
562
|
+
disabled: this.testMode || opts.disableTelemetry
|
|
529
563
|
});
|
|
530
|
-
if (opts.initialBlob) {
|
|
531
|
-
this.flagsBlob = opts.initialBlob;
|
|
564
|
+
if (opts.initialBlob || this.testMode) {
|
|
565
|
+
this.flagsBlob = opts.initialBlob ?? { version: "test", plan: "free", gates: {}, configs: {}, killswitches: {} };
|
|
566
|
+
this.expsBlob = this.expsBlob ?? { version: "test", universes: {}, experiments: {} };
|
|
532
567
|
this.initialized = true;
|
|
533
568
|
}
|
|
534
569
|
}
|
|
570
|
+
/**
|
|
571
|
+
* Build a no-network, immediately-usable client for tests (Statsig-style).
|
|
572
|
+
* init()/initOnce() are no-ops (never fetch), track() is a no-op, telemetry
|
|
573
|
+
* is disabled, and the client is already "initialized" — seed every entity
|
|
574
|
+
* with overrideFlag/overrideConfig/overrideExperiment. No SDK key required.
|
|
575
|
+
*
|
|
576
|
+
* ```ts
|
|
577
|
+
* const client = FlagsClient.forTesting();
|
|
578
|
+
* client.overrideFlag("new_checkout", true);
|
|
579
|
+
* client.getFlag("new_checkout", { user_id: "u1" }); // true
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
static forTesting(opts) {
|
|
583
|
+
return new _FlagsClient({ apiKey: "", ...opts, testMode: true });
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Build a fully OFFLINE client from a pre-captured snapshot — no network ever.
|
|
587
|
+
* Reuses the test-mode plumbing (init()/initOnce()/track() are no-ops,
|
|
588
|
+
* telemetry off) but, unlike forTesting(), seeds the REAL flags + experiments
|
|
589
|
+
* blobs so evaluations run the canonical eval against the snapshot. Local
|
|
590
|
+
* overrides still apply on top.
|
|
591
|
+
*
|
|
592
|
+
* Snapshot shape mirrors the wire bodies:
|
|
593
|
+
* `{ flags: <GET /sdk/flags body>, experiments: <GET /sdk/experiments body> }`.
|
|
594
|
+
*/
|
|
595
|
+
static fromSnapshot(snapshot) {
|
|
596
|
+
const client = new _FlagsClient({ apiKey: "", testMode: true });
|
|
597
|
+
client.flagsBlob = snapshot.flags;
|
|
598
|
+
client.expsBlob = snapshot.experiments;
|
|
599
|
+
client.initialized = true;
|
|
600
|
+
return client;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Build a fully OFFLINE client from a snapshot JSON file on disk (Node only —
|
|
604
|
+
* not available in the browser entrypoint). The file must contain
|
|
605
|
+
* `{ "flags": <GET /sdk/flags body>, "experiments": <GET /sdk/experiments body> }`.
|
|
606
|
+
* See {@link FlagsClient.fromSnapshot}.
|
|
607
|
+
*/
|
|
608
|
+
static fromFile(path) {
|
|
609
|
+
const fs = require("fs");
|
|
610
|
+
const raw = fs.readFileSync(path, "utf8");
|
|
611
|
+
const snapshot = JSON.parse(raw);
|
|
612
|
+
return _FlagsClient.fromSnapshot(snapshot);
|
|
613
|
+
}
|
|
535
614
|
async init() {
|
|
615
|
+
if (this.testMode) {
|
|
616
|
+
this.initialized = true;
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
536
619
|
await this.fetchAll();
|
|
537
620
|
this.initialized = true;
|
|
538
621
|
this.startPoll();
|
|
539
622
|
}
|
|
540
623
|
async initOnce() {
|
|
541
|
-
if (this.initialized) return;
|
|
624
|
+
if (this.testMode || this.initialized) return;
|
|
542
625
|
await this.fetchAll();
|
|
543
626
|
this.initialized = true;
|
|
544
627
|
}
|
|
628
|
+
// ---- Local overrides (Statsig-style) ----
|
|
629
|
+
/** Force `getFlag(name, …)` to return `value`, ignoring the fetched gate. */
|
|
630
|
+
overrideFlag(name, value) {
|
|
631
|
+
this.flagOverrides.set(name, value);
|
|
632
|
+
}
|
|
633
|
+
/** Force `getConfig(name)` to return `value`, ignoring the fetched config. */
|
|
634
|
+
overrideConfig(name, value) {
|
|
635
|
+
this.configOverrides.set(name, value);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Force `getExperiment(name, …)` to return `{ inExperiment: true, group, params }`,
|
|
639
|
+
* ignoring allocation, holdouts, and targeting.
|
|
640
|
+
*/
|
|
641
|
+
overrideExperiment(name, group, params) {
|
|
642
|
+
this.experimentOverrides.set(name, { group, params });
|
|
643
|
+
}
|
|
644
|
+
/** Remove every programmatic override set via the override* methods. */
|
|
645
|
+
clearOverrides() {
|
|
646
|
+
this.flagOverrides.clear();
|
|
647
|
+
this.configOverrides.clear();
|
|
648
|
+
this.experimentOverrides.clear();
|
|
649
|
+
}
|
|
545
650
|
destroy() {
|
|
546
651
|
if (this.timer !== null) {
|
|
547
652
|
clearInterval(this.timer);
|
|
548
653
|
this.timer = null;
|
|
549
654
|
}
|
|
550
655
|
}
|
|
656
|
+
/**
|
|
657
|
+
* Subscribe to data-change notifications. The listener fires after a
|
|
658
|
+
* background poll fetch returns NEW data (200, not 304) — i.e. after the
|
|
659
|
+
* cached blob is updated. Never fires in testMode/offline (no polling).
|
|
660
|
+
* Returns an unsubscribe function.
|
|
661
|
+
*/
|
|
662
|
+
onChange(listener) {
|
|
663
|
+
this.changeListeners.add(listener);
|
|
664
|
+
return () => {
|
|
665
|
+
this.changeListeners.delete(listener);
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
notifyChange() {
|
|
669
|
+
for (const l of this.changeListeners) {
|
|
670
|
+
try {
|
|
671
|
+
l();
|
|
672
|
+
} catch (err) {
|
|
673
|
+
console.warn("[shipeasy] onChange listener threw:", String(err));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
551
677
|
startPoll() {
|
|
552
678
|
this.timer = setInterval(() => {
|
|
553
|
-
this.fetchAll().catch(
|
|
679
|
+
this.fetchAll(true).catch(
|
|
554
680
|
(err) => console.warn("[shipeasy] background poll failed:", String(err))
|
|
555
681
|
);
|
|
556
682
|
}, this.pollInterval * 1e3);
|
|
557
683
|
}
|
|
558
|
-
async fetchAll() {
|
|
559
|
-
const [
|
|
684
|
+
async fetchAll(fromPoll = false) {
|
|
685
|
+
const [flagsRes, expsChanged] = await Promise.all([this.fetchFlags(), this.fetchExps()]);
|
|
686
|
+
const { interval, changed: flagsChanged } = flagsRes;
|
|
560
687
|
if (interval !== null && interval !== this.pollInterval) {
|
|
561
688
|
this.pollInterval = interval;
|
|
562
689
|
if (this.timer !== null) {
|
|
@@ -564,41 +691,72 @@ var FlagsClient = class {
|
|
|
564
691
|
this.startPoll();
|
|
565
692
|
}
|
|
566
693
|
}
|
|
694
|
+
if (fromPoll && (flagsChanged || expsChanged)) this.notifyChange();
|
|
567
695
|
}
|
|
568
696
|
async fetchFlags() {
|
|
569
697
|
const headers = { "X-SDK-Key": this.apiKey };
|
|
570
698
|
if (this.flagsEtag) headers["If-None-Match"] = this.flagsEtag;
|
|
571
699
|
const res = await globalThis.fetch(`${this.baseUrl}/sdk/flags?env=${this.env}`, { headers });
|
|
572
700
|
const interval = Number(res.headers.get("X-Poll-Interval") ?? "30") || 30;
|
|
573
|
-
if (res.status === 304) return interval;
|
|
701
|
+
if (res.status === 304) return { interval, changed: false };
|
|
574
702
|
if (!res.ok) throw new Error(`/sdk/flags returned ${res.status}`);
|
|
575
703
|
const etag = res.headers.get("ETag");
|
|
576
704
|
if (etag) this.flagsEtag = etag;
|
|
577
705
|
this.flagsBlob = await res.json();
|
|
578
|
-
return interval;
|
|
706
|
+
return { interval, changed: true };
|
|
579
707
|
}
|
|
580
708
|
async fetchExps() {
|
|
581
709
|
const headers = { "X-SDK-Key": this.apiKey };
|
|
582
710
|
if (this.expsEtag) headers["If-None-Match"] = this.expsEtag;
|
|
583
711
|
const res = await globalThis.fetch(`${this.baseUrl}/sdk/experiments`, { headers });
|
|
584
|
-
if (res.status === 304) return;
|
|
712
|
+
if (res.status === 304) return false;
|
|
585
713
|
if (!res.ok) throw new Error(`/sdk/experiments returned ${res.status}`);
|
|
586
714
|
const etag = res.headers.get("ETag");
|
|
587
715
|
if (etag) this.expsEtag = etag;
|
|
588
716
|
this.expsBlob = await res.json();
|
|
717
|
+
return true;
|
|
589
718
|
}
|
|
590
|
-
|
|
719
|
+
/**
|
|
720
|
+
* Evaluate a gate and report WHY (LaunchDarkly variationDetail parity). The
|
|
721
|
+
* reason is computed entirely at this boundary — the canonical eval
|
|
722
|
+
* (evalGateInternal) is untouched. A local override short-circuits BEFORE
|
|
723
|
+
* telemetry, exactly like getFlag's override path; otherwise exactly one
|
|
724
|
+
* "gate" beacon is emitted.
|
|
725
|
+
*/
|
|
726
|
+
getFlagDetail(name, user) {
|
|
727
|
+
const ov = this.flagOverrides.get(name);
|
|
728
|
+
if (ov !== void 0) return { value: ov, reason: "OVERRIDE" };
|
|
591
729
|
this.telemetry.emit("gate", name);
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
730
|
+
if (!this.flagsBlob) return { value: false, reason: "CLIENT_NOT_READY" };
|
|
731
|
+
const gate = this.flagsBlob.gates[name];
|
|
732
|
+
if (!gate) return { value: false, reason: "FLAG_NOT_FOUND" };
|
|
733
|
+
if (isEnabled(gate.killswitch) || !isEnabled(gate.enabled)) {
|
|
734
|
+
return { value: false, reason: "OFF" };
|
|
735
|
+
}
|
|
736
|
+
const value = evalGateInternal(gate, user);
|
|
737
|
+
return { value, reason: value ? "RULE_MATCH" : "DEFAULT" };
|
|
595
738
|
}
|
|
596
|
-
|
|
739
|
+
/**
|
|
740
|
+
* Read a feature gate. Returns `defaultValue` ONLY when the gate cannot be
|
|
741
|
+
* evaluated (client not initialized or flag not found) — never for a gate that
|
|
742
|
+
* legitimately evaluates to false. Plain `getFlag(name, user)` keeps returning
|
|
743
|
+
* false for a missing flag.
|
|
744
|
+
*/
|
|
745
|
+
getFlag(name, user, defaultValue = false) {
|
|
746
|
+
const d = this.getFlagDetail(name, user);
|
|
747
|
+
if (d.reason === "CLIENT_NOT_READY" || d.reason === "FLAG_NOT_FOUND") return defaultValue;
|
|
748
|
+
return d.value;
|
|
749
|
+
}
|
|
750
|
+
getConfig(name, decodeOrOpts) {
|
|
597
751
|
this.telemetry.emit("config", name);
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
752
|
+
const opts = typeof decodeOrOpts === "function" ? { decode: decodeOrOpts } : decodeOrOpts ?? {};
|
|
753
|
+
const has = this.configOverrides.has(name);
|
|
754
|
+
const raw = has ? this.configOverrides.get(name) : this.flagsBlob?.configs[name]?.value;
|
|
755
|
+
if (raw === void 0 && !has) {
|
|
756
|
+
return "defaultValue" in opts ? opts.defaultValue : void 0;
|
|
757
|
+
}
|
|
758
|
+
if (!opts.decode) return raw;
|
|
759
|
+
return opts.decode(raw);
|
|
602
760
|
}
|
|
603
761
|
getExperiment(name, user, defaultParams, decode) {
|
|
604
762
|
this.telemetry.emit("experiment", name);
|
|
@@ -607,6 +765,16 @@ var FlagsClient = class {
|
|
|
607
765
|
group: "control",
|
|
608
766
|
params: defaultParams
|
|
609
767
|
};
|
|
768
|
+
const ov = this.experimentOverrides.get(name);
|
|
769
|
+
if (ov) {
|
|
770
|
+
if (!decode) return { inExperiment: true, group: ov.group, params: ov.params };
|
|
771
|
+
try {
|
|
772
|
+
return { inExperiment: true, group: ov.group, params: decode(ov.params) };
|
|
773
|
+
} catch (err) {
|
|
774
|
+
console.warn(`[shipeasy] getExperiment('${name}') override decode failed:`, String(err));
|
|
775
|
+
return notIn;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
610
778
|
if (!this.flagsBlob || !this.expsBlob) return notIn;
|
|
611
779
|
const exp = this.expsBlob.experiments[name];
|
|
612
780
|
if (!exp || exp.status !== "running") return notIn;
|
|
@@ -644,6 +812,7 @@ var FlagsClient = class {
|
|
|
644
812
|
return notIn;
|
|
645
813
|
}
|
|
646
814
|
track(userId, eventName, props) {
|
|
815
|
+
if (this.testMode) return;
|
|
647
816
|
const body = JSON.stringify({
|
|
648
817
|
events: [
|
|
649
818
|
{
|
|
@@ -978,11 +1147,15 @@ var flags = {
|
|
|
978
1147
|
destroy() {
|
|
979
1148
|
_server?.destroy();
|
|
980
1149
|
},
|
|
981
|
-
get(name, user) {
|
|
982
|
-
return _server?.getFlag(name, user) ??
|
|
1150
|
+
get(name, user, defaultValue = false) {
|
|
1151
|
+
return _server?.getFlag(name, user, defaultValue) ?? defaultValue;
|
|
1152
|
+
},
|
|
1153
|
+
/** Evaluate a gate and report why (value + reason). See {@link FlagDetail}. */
|
|
1154
|
+
getDetail(name, user) {
|
|
1155
|
+
return _server?.getFlagDetail(name, user) ?? { value: false, reason: "CLIENT_NOT_READY" };
|
|
983
1156
|
},
|
|
984
|
-
getConfig(name,
|
|
985
|
-
return _server?.getConfig(name,
|
|
1157
|
+
getConfig(name, decodeOrOpts) {
|
|
1158
|
+
return _server?.getConfig(name, decodeOrOpts);
|
|
986
1159
|
},
|
|
987
1160
|
getExperiment(name, user, defaultParams, decode) {
|
|
988
1161
|
return _server?.getExperiment(name, user, defaultParams, decode) ?? {
|
|
@@ -1027,13 +1200,15 @@ var see = Object.assign(
|
|
|
1027
1200
|
(problem) => startSeeChain(() => problem, dispatchSee),
|
|
1028
1201
|
{
|
|
1029
1202
|
Violation: (name) => startSeeViolationChain(name, dispatchSee),
|
|
1030
|
-
ControlFlowException:
|
|
1203
|
+
ControlFlowException: (err) => startControlFlowChain(err)
|
|
1031
1204
|
}
|
|
1032
1205
|
);
|
|
1033
1206
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1034
1207
|
0 && (module.exports = {
|
|
1035
1208
|
ANON_ID_COOKIE,
|
|
1209
|
+
FLAG_REASONS,
|
|
1036
1210
|
FlagsClient,
|
|
1211
|
+
_murmur3ForTests,
|
|
1037
1212
|
_resetShipeasyServerForTests,
|
|
1038
1213
|
configureShipeasyServer,
|
|
1039
1214
|
fetchLabelsForSSR,
|