@shipeasy/sdk 5.1.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.d.mts +86 -2
- package/dist/client/index.d.ts +86 -2
- package/dist/client/index.js +90 -7
- package/dist/client/index.mjs +90 -7
- package/dist/openfeature-server/index.d.mts +338 -0
- package/dist/openfeature-server/index.d.ts +338 -0
- package/dist/openfeature-server/index.js +125 -0
- package/dist/openfeature-server/index.mjs +100 -0
- package/dist/openfeature-web/index.d.mts +332 -0
- package/dist/openfeature-web/index.d.ts +332 -0
- package/dist/openfeature-web/index.js +120 -0
- package/dist/openfeature-web/index.mjs +95 -0
- package/dist/server/index.d.mts +58 -1
- package/dist/server/index.d.ts +58 -1
- package/dist/server/index.js +97 -15
- package/dist/server/index.mjs +96 -15
- package/package.json +23 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/openfeature-server/index.ts
|
|
21
|
+
var openfeature_server_exports = {};
|
|
22
|
+
__export(openfeature_server_exports, {
|
|
23
|
+
ShipeasyProvider: () => ShipeasyProvider
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(openfeature_server_exports);
|
|
26
|
+
var import_server_sdk = require("@openfeature/server-sdk");
|
|
27
|
+
|
|
28
|
+
// src/openfeature/shared.ts
|
|
29
|
+
var REASON_MAP = {
|
|
30
|
+
RULE_MATCH: { reason: "TARGETING_MATCH" },
|
|
31
|
+
DEFAULT: { reason: "DEFAULT" },
|
|
32
|
+
OFF: { reason: "DISABLED" },
|
|
33
|
+
OVERRIDE: { reason: "STATIC" },
|
|
34
|
+
FLAG_NOT_FOUND: { reason: "ERROR", errorCode: "FLAG_NOT_FOUND" },
|
|
35
|
+
CLIENT_NOT_READY: { reason: "ERROR", errorCode: "PROVIDER_NOT_READY" }
|
|
36
|
+
};
|
|
37
|
+
function mapFlagReason(reason) {
|
|
38
|
+
return REASON_MAP[reason] ?? { reason: "UNKNOWN" };
|
|
39
|
+
}
|
|
40
|
+
function toUser(ctx) {
|
|
41
|
+
if (!ctx) return {};
|
|
42
|
+
const { targetingKey, ...rest } = ctx;
|
|
43
|
+
const user = { ...rest };
|
|
44
|
+
if (typeof targetingKey === "string" && targetingKey.length > 0) {
|
|
45
|
+
user.user_id = targetingKey;
|
|
46
|
+
}
|
|
47
|
+
return user;
|
|
48
|
+
}
|
|
49
|
+
function resolveConfigValue(raw, type) {
|
|
50
|
+
if (raw === void 0) return { kind: "default" };
|
|
51
|
+
const ok = type === "object" ? typeof raw === "object" && raw !== null : typeof raw === type;
|
|
52
|
+
if (!ok) {
|
|
53
|
+
return {
|
|
54
|
+
kind: "type_mismatch",
|
|
55
|
+
message: `flag value ${JSON.stringify(raw)} is not of type ${type}`
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { kind: "match", value: raw };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/openfeature-server/index.ts
|
|
62
|
+
var ERROR_CODE = {
|
|
63
|
+
FLAG_NOT_FOUND: import_server_sdk.ErrorCode.FLAG_NOT_FOUND,
|
|
64
|
+
PROVIDER_NOT_READY: import_server_sdk.ErrorCode.PROVIDER_NOT_READY,
|
|
65
|
+
TYPE_MISMATCH: import_server_sdk.ErrorCode.TYPE_MISMATCH
|
|
66
|
+
};
|
|
67
|
+
function resolveConfig(raw, defaultValue, type) {
|
|
68
|
+
const r = resolveConfigValue(raw, type);
|
|
69
|
+
if (r.kind === "default") return { value: defaultValue, reason: "DEFAULT" };
|
|
70
|
+
if (r.kind === "type_mismatch") {
|
|
71
|
+
return {
|
|
72
|
+
value: defaultValue,
|
|
73
|
+
reason: "ERROR",
|
|
74
|
+
errorCode: ERROR_CODE.TYPE_MISMATCH,
|
|
75
|
+
errorMessage: r.message
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return { value: r.value, reason: "TARGETING_MATCH" };
|
|
79
|
+
}
|
|
80
|
+
var ShipeasyProvider = class {
|
|
81
|
+
constructor(client) {
|
|
82
|
+
this.client = client;
|
|
83
|
+
}
|
|
84
|
+
client;
|
|
85
|
+
metadata = { name: "shipeasy" };
|
|
86
|
+
runsOn = "server";
|
|
87
|
+
/** Fetch the rules blob once. The SDK fires `Ready` when this resolves. */
|
|
88
|
+
async initialize() {
|
|
89
|
+
await this.client.initOnce();
|
|
90
|
+
}
|
|
91
|
+
async onClose() {
|
|
92
|
+
this.client.destroy();
|
|
93
|
+
}
|
|
94
|
+
async resolveBooleanEvaluation(flagKey, defaultValue, context, _logger) {
|
|
95
|
+
const detail = this.client.getFlagDetail(flagKey, toUser(context));
|
|
96
|
+
const m = mapFlagReason(detail.reason);
|
|
97
|
+
if (m.errorCode) {
|
|
98
|
+
return {
|
|
99
|
+
value: defaultValue,
|
|
100
|
+
reason: m.reason,
|
|
101
|
+
errorCode: ERROR_CODE[m.errorCode]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return { value: detail.value, reason: m.reason };
|
|
105
|
+
}
|
|
106
|
+
async resolveStringEvaluation(flagKey, defaultValue, _context, _logger) {
|
|
107
|
+
return resolveConfig(this.client.getConfig(flagKey), defaultValue, "string");
|
|
108
|
+
}
|
|
109
|
+
async resolveNumberEvaluation(flagKey, defaultValue, _context, _logger) {
|
|
110
|
+
return resolveConfig(this.client.getConfig(flagKey), defaultValue, "number");
|
|
111
|
+
}
|
|
112
|
+
async resolveObjectEvaluation(flagKey, defaultValue, _context, _logger) {
|
|
113
|
+
return resolveConfig(this.client.getConfig(flagKey), defaultValue, "object");
|
|
114
|
+
}
|
|
115
|
+
/** OpenFeature `track()` → Shipeasy `track()`. No-ops without a targeting key. */
|
|
116
|
+
track(trackingEventName, context, details) {
|
|
117
|
+
const userId = context.targetingKey ?? context.user_id;
|
|
118
|
+
if (!userId) return;
|
|
119
|
+
this.client.track(userId, trackingEventName, details);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
123
|
+
0 && (module.exports = {
|
|
124
|
+
ShipeasyProvider
|
|
125
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/openfeature-server/index.ts
|
|
2
|
+
import { ErrorCode } from "@openfeature/server-sdk";
|
|
3
|
+
|
|
4
|
+
// src/openfeature/shared.ts
|
|
5
|
+
var REASON_MAP = {
|
|
6
|
+
RULE_MATCH: { reason: "TARGETING_MATCH" },
|
|
7
|
+
DEFAULT: { reason: "DEFAULT" },
|
|
8
|
+
OFF: { reason: "DISABLED" },
|
|
9
|
+
OVERRIDE: { reason: "STATIC" },
|
|
10
|
+
FLAG_NOT_FOUND: { reason: "ERROR", errorCode: "FLAG_NOT_FOUND" },
|
|
11
|
+
CLIENT_NOT_READY: { reason: "ERROR", errorCode: "PROVIDER_NOT_READY" }
|
|
12
|
+
};
|
|
13
|
+
function mapFlagReason(reason) {
|
|
14
|
+
return REASON_MAP[reason] ?? { reason: "UNKNOWN" };
|
|
15
|
+
}
|
|
16
|
+
function toUser(ctx) {
|
|
17
|
+
if (!ctx) return {};
|
|
18
|
+
const { targetingKey, ...rest } = ctx;
|
|
19
|
+
const user = { ...rest };
|
|
20
|
+
if (typeof targetingKey === "string" && targetingKey.length > 0) {
|
|
21
|
+
user.user_id = targetingKey;
|
|
22
|
+
}
|
|
23
|
+
return user;
|
|
24
|
+
}
|
|
25
|
+
function resolveConfigValue(raw, type) {
|
|
26
|
+
if (raw === void 0) return { kind: "default" };
|
|
27
|
+
const ok = type === "object" ? typeof raw === "object" && raw !== null : typeof raw === type;
|
|
28
|
+
if (!ok) {
|
|
29
|
+
return {
|
|
30
|
+
kind: "type_mismatch",
|
|
31
|
+
message: `flag value ${JSON.stringify(raw)} is not of type ${type}`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return { kind: "match", value: raw };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/openfeature-server/index.ts
|
|
38
|
+
var ERROR_CODE = {
|
|
39
|
+
FLAG_NOT_FOUND: ErrorCode.FLAG_NOT_FOUND,
|
|
40
|
+
PROVIDER_NOT_READY: ErrorCode.PROVIDER_NOT_READY,
|
|
41
|
+
TYPE_MISMATCH: ErrorCode.TYPE_MISMATCH
|
|
42
|
+
};
|
|
43
|
+
function resolveConfig(raw, defaultValue, type) {
|
|
44
|
+
const r = resolveConfigValue(raw, type);
|
|
45
|
+
if (r.kind === "default") return { value: defaultValue, reason: "DEFAULT" };
|
|
46
|
+
if (r.kind === "type_mismatch") {
|
|
47
|
+
return {
|
|
48
|
+
value: defaultValue,
|
|
49
|
+
reason: "ERROR",
|
|
50
|
+
errorCode: ERROR_CODE.TYPE_MISMATCH,
|
|
51
|
+
errorMessage: r.message
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { value: r.value, reason: "TARGETING_MATCH" };
|
|
55
|
+
}
|
|
56
|
+
var ShipeasyProvider = class {
|
|
57
|
+
constructor(client) {
|
|
58
|
+
this.client = client;
|
|
59
|
+
}
|
|
60
|
+
client;
|
|
61
|
+
metadata = { name: "shipeasy" };
|
|
62
|
+
runsOn = "server";
|
|
63
|
+
/** Fetch the rules blob once. The SDK fires `Ready` when this resolves. */
|
|
64
|
+
async initialize() {
|
|
65
|
+
await this.client.initOnce();
|
|
66
|
+
}
|
|
67
|
+
async onClose() {
|
|
68
|
+
this.client.destroy();
|
|
69
|
+
}
|
|
70
|
+
async resolveBooleanEvaluation(flagKey, defaultValue, context, _logger) {
|
|
71
|
+
const detail = this.client.getFlagDetail(flagKey, toUser(context));
|
|
72
|
+
const m = mapFlagReason(detail.reason);
|
|
73
|
+
if (m.errorCode) {
|
|
74
|
+
return {
|
|
75
|
+
value: defaultValue,
|
|
76
|
+
reason: m.reason,
|
|
77
|
+
errorCode: ERROR_CODE[m.errorCode]
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { value: detail.value, reason: m.reason };
|
|
81
|
+
}
|
|
82
|
+
async resolveStringEvaluation(flagKey, defaultValue, _context, _logger) {
|
|
83
|
+
return resolveConfig(this.client.getConfig(flagKey), defaultValue, "string");
|
|
84
|
+
}
|
|
85
|
+
async resolveNumberEvaluation(flagKey, defaultValue, _context, _logger) {
|
|
86
|
+
return resolveConfig(this.client.getConfig(flagKey), defaultValue, "number");
|
|
87
|
+
}
|
|
88
|
+
async resolveObjectEvaluation(flagKey, defaultValue, _context, _logger) {
|
|
89
|
+
return resolveConfig(this.client.getConfig(flagKey), defaultValue, "object");
|
|
90
|
+
}
|
|
91
|
+
/** OpenFeature `track()` → Shipeasy `track()`. No-ops without a targeting key. */
|
|
92
|
+
track(trackingEventName, context, details) {
|
|
93
|
+
const userId = context.targetingKey ?? context.user_id;
|
|
94
|
+
if (!userId) return;
|
|
95
|
+
this.client.track(userId, trackingEventName, details);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
export {
|
|
99
|
+
ShipeasyProvider
|
|
100
|
+
};
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { Provider, EvaluationContext, Logger, ResolutionDetails, JsonValue } from '@openfeature/web-sdk';
|
|
2
|
+
|
|
3
|
+
type SeeExtras = Record<string, string | number | boolean | null | undefined>;
|
|
4
|
+
type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
|
|
5
|
+
/** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
|
|
6
|
+
interface Consequence {
|
|
7
|
+
readonly __seConsequence: true;
|
|
8
|
+
readonly subject: string;
|
|
9
|
+
readonly outcome: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare global {
|
|
13
|
+
interface Window {
|
|
14
|
+
i18n?: {
|
|
15
|
+
t: (key: string, vars?: Record<string, string | number>) => string;
|
|
16
|
+
ready: (cb: () => void) => void;
|
|
17
|
+
on: (event: "update", cb: () => void) => () => void;
|
|
18
|
+
locale: string | null;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
interface User {
|
|
23
|
+
user_id?: string;
|
|
24
|
+
[attr: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
interface ExperimentResult<P> {
|
|
27
|
+
inExperiment: boolean;
|
|
28
|
+
group: string;
|
|
29
|
+
params: P;
|
|
30
|
+
}
|
|
31
|
+
/** Options object form of `getExperiment` — the legacy `decode`/`variants`
|
|
32
|
+
* positional args plus per-call exposure control. */
|
|
33
|
+
interface GetExperimentOptions<P> {
|
|
34
|
+
/** Decode the raw stored params into the typed shape callers want. */
|
|
35
|
+
decode?: (raw: unknown) => P;
|
|
36
|
+
/** Variant-specific param overrides merged on top of group params. */
|
|
37
|
+
variants?: Record<string, Partial<P>>;
|
|
38
|
+
/**
|
|
39
|
+
* Override automatic exposure logging for this read. Defaults to the client's
|
|
40
|
+
* setting (`disableAutoExposure` flips it). `false` reads the variant without
|
|
41
|
+
* logging an exposure — pair with `logExposure(name)` at render time.
|
|
42
|
+
*/
|
|
43
|
+
logExposure?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Why a flag evaluated the way it did (LaunchDarkly variationDetail parity).
|
|
47
|
+
* Computed at the client boundary:
|
|
48
|
+
* - CLIENT_NOT_READY — no eval result yet (identify() hasn't resolved)
|
|
49
|
+
* - FLAG_NOT_FOUND — the gate name isn't present in the eval result
|
|
50
|
+
* - OFF — folded into DEFAULT here (the server pre-evaluates the
|
|
51
|
+
* gate's enabled/killed state into a plain boolean, so the browser can't
|
|
52
|
+
* distinguish a disabled gate from one a rule denied)
|
|
53
|
+
* - OVERRIDE — a local override or a ?se_gate_/?se_ks_ URL override
|
|
54
|
+
* decided the value
|
|
55
|
+
* - RULE_MATCH — the gate evaluated true
|
|
56
|
+
* - DEFAULT — the gate evaluated false
|
|
57
|
+
*/
|
|
58
|
+
declare const FLAG_REASONS: readonly ["CLIENT_NOT_READY", "FLAG_NOT_FOUND", "OFF", "OVERRIDE", "RULE_MATCH", "DEFAULT"];
|
|
59
|
+
type FlagReason = (typeof FLAG_REASONS)[number];
|
|
60
|
+
interface FlagDetail {
|
|
61
|
+
value: boolean;
|
|
62
|
+
reason: FlagReason;
|
|
63
|
+
}
|
|
64
|
+
/** Options object form of `getConfig` — keeps the legacy `decode` callback and
|
|
65
|
+
* adds a `defaultValue` returned when the config key is absent. */
|
|
66
|
+
interface GetConfigOptions<T = unknown> {
|
|
67
|
+
/** Decode the raw stored value into the typed shape callers want. */
|
|
68
|
+
decode?: (raw: unknown) => T;
|
|
69
|
+
/** Returned when the config key is absent (not overridden, not in the eval result). */
|
|
70
|
+
defaultValue?: T;
|
|
71
|
+
}
|
|
72
|
+
interface EvalExpResult {
|
|
73
|
+
inExperiment: boolean;
|
|
74
|
+
group: string;
|
|
75
|
+
params: Record<string, unknown>;
|
|
76
|
+
}
|
|
77
|
+
interface EvalResponse {
|
|
78
|
+
flags: Record<string, boolean>;
|
|
79
|
+
configs: Record<string, unknown>;
|
|
80
|
+
experiments: Record<string, EvalExpResult>;
|
|
81
|
+
/**
|
|
82
|
+
* Killswitch state, flattened by the server. A boolean means the killswitch
|
|
83
|
+
* is whole-killed; an object means it's not whole-killed and carries per-
|
|
84
|
+
* switch booleans.
|
|
85
|
+
*/
|
|
86
|
+
killswitches?: Record<string, boolean | Record<string, boolean>>;
|
|
87
|
+
/**
|
|
88
|
+
* Newly-assigned sticky entries (doc 20 §2). The worker returns these so the
|
|
89
|
+
* browser can merge them into the `__se_sticky` cookie. Present only when
|
|
90
|
+
* sticky bucketing is on and at least one assignment was made/refreshed.
|
|
91
|
+
*/
|
|
92
|
+
sticky?: Record<string, StickyCookieEntry>;
|
|
93
|
+
}
|
|
94
|
+
/** One persisted sticky assignment: group + 8-char salt prefix (reshuffle key). */
|
|
95
|
+
interface StickyCookieEntry {
|
|
96
|
+
g: string;
|
|
97
|
+
s: string;
|
|
98
|
+
}
|
|
99
|
+
interface AutoCollectGroups {
|
|
100
|
+
vitals: boolean;
|
|
101
|
+
errors: boolean;
|
|
102
|
+
engagement: boolean;
|
|
103
|
+
}
|
|
104
|
+
type FlagsClientBrowserEnv = "dev" | "staging" | "prod";
|
|
105
|
+
interface FlagsClientBrowserOptions {
|
|
106
|
+
sdkKey: string;
|
|
107
|
+
baseUrl?: string;
|
|
108
|
+
autoGuardrails?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Per-group enablement for auto-collected metrics. When set, overrides the
|
|
111
|
+
* blanket `autoGuardrails` flag for the specific groups listed. Any group
|
|
112
|
+
* not present in the object falls back to `autoGuardrails` (defaulting to
|
|
113
|
+
* true when `autoGuardrails` is true).
|
|
114
|
+
*/
|
|
115
|
+
autoGuardrailGroups?: Partial<AutoCollectGroups>;
|
|
116
|
+
/**
|
|
117
|
+
* Emit `__auto_*` metric events for ALL visitors, not just experiment
|
|
118
|
+
* participants. Default false: auto-metrics are gated on the visitor having
|
|
119
|
+
* seen ≥1 experiment exposure (the only data the analysis pipeline reads;
|
|
120
|
+
* ungated emission is pure AE write cost at scale — see cost.md).
|
|
121
|
+
*/
|
|
122
|
+
autoCollectAlways?: boolean;
|
|
123
|
+
/** Which published env to read values from. Defaults to "prod". */
|
|
124
|
+
env?: FlagsClientBrowserEnv;
|
|
125
|
+
/**
|
|
126
|
+
* Per-evaluation usage telemetry. ON by default — each getFlag/getConfig/
|
|
127
|
+
* getExperiment/getKillswitch call fires one fire-and-forget sendBeacon so
|
|
128
|
+
* usage is counted by Cloudflare's native per-path analytics. Pass `true` to
|
|
129
|
+
* disable entirely.
|
|
130
|
+
*/
|
|
131
|
+
disableTelemetry?: boolean;
|
|
132
|
+
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
133
|
+
telemetryUrl?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Suppress automatic exposure logging in `getExperiment` (Statsig's
|
|
136
|
+
* `disableExposureLogging`). Default false — reading an enrolled variant
|
|
137
|
+
* auto-logs a deduped exposure. When true, no exposure fires unless you call
|
|
138
|
+
* `logExposure(name)` yourself, or pass `{ logExposure: true }` per call.
|
|
139
|
+
*/
|
|
140
|
+
disableAutoExposure?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Attribute names usable for targeting but never persisted in analytics
|
|
143
|
+
* (LD/Statsig `privateAttributes`). They are sent to `/sdk/evaluate` under
|
|
144
|
+
* `private_attributes` so the edge can evaluate with them (unavoidable —
|
|
145
|
+
* the edge evaluates), but the worker never stores them, and the listed keys
|
|
146
|
+
* are stripped from any `track(props)` payload.
|
|
147
|
+
*/
|
|
148
|
+
privateAttributes?: string[];
|
|
149
|
+
/**
|
|
150
|
+
* Sticky bucketing (doc 20 §2). ON by default in the browser: a unit's
|
|
151
|
+
* first-assigned variant is locked in the `__se_sticky` cookie so changing an
|
|
152
|
+
* experiment's allocation % or group weights never silently re-buckets
|
|
153
|
+
* enrolled users. Changing the experiment salt is the deliberate reshuffle
|
|
154
|
+
* lever. Pass `false` to opt out (pure deterministic eval).
|
|
155
|
+
*/
|
|
156
|
+
stickyBucketing?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Test mode — no network at all. identify()/init are no-ops (never call
|
|
159
|
+
* /sdk/evaluate), track() is a no-op, telemetry is forced off, and the client
|
|
160
|
+
* starts "ready" with an empty eval result. Prefer the
|
|
161
|
+
* {@link FlagsClientBrowser.forTesting} factory over passing this directly.
|
|
162
|
+
*/
|
|
163
|
+
testMode?: boolean;
|
|
164
|
+
}
|
|
165
|
+
declare class FlagsClientBrowser {
|
|
166
|
+
private readonly sdkKey;
|
|
167
|
+
private readonly baseUrl;
|
|
168
|
+
private readonly autoGuardrails;
|
|
169
|
+
private readonly autoGuardrailGroups;
|
|
170
|
+
private readonly autoCollectAlways;
|
|
171
|
+
private readonly disableAutoExposure;
|
|
172
|
+
private readonly privateAttributes;
|
|
173
|
+
private readonly stickyBucketing;
|
|
174
|
+
private readonly env;
|
|
175
|
+
private evalResult;
|
|
176
|
+
private anonId;
|
|
177
|
+
private userId;
|
|
178
|
+
private buffer;
|
|
179
|
+
private telemetry;
|
|
180
|
+
private seeLimiter;
|
|
181
|
+
private guardrailsInstalled;
|
|
182
|
+
private listeners;
|
|
183
|
+
private overrideListenerInstalled;
|
|
184
|
+
private identifySeq;
|
|
185
|
+
private readonly testMode;
|
|
186
|
+
private readonly flagOverrides;
|
|
187
|
+
private readonly configOverrides;
|
|
188
|
+
private readonly experimentOverrides;
|
|
189
|
+
private onOverrideChange;
|
|
190
|
+
constructor(opts: FlagsClientBrowserOptions);
|
|
191
|
+
/**
|
|
192
|
+
* Build a no-network, immediately-usable browser client for tests
|
|
193
|
+
* (Statsig-style). identify() is a no-op (never calls /sdk/evaluate), track()
|
|
194
|
+
* is a no-op, telemetry is disabled, and the client is already ready — seed
|
|
195
|
+
* every entity with overrideFlag/overrideConfig/overrideExperiment. No SDK
|
|
196
|
+
* key required.
|
|
197
|
+
*
|
|
198
|
+
* ```ts
|
|
199
|
+
* const client = FlagsClientBrowser.forTesting();
|
|
200
|
+
* client.overrideFlag("new_checkout", true);
|
|
201
|
+
* client.getFlag("new_checkout"); // true
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
static forTesting(opts?: Partial<FlagsClientBrowserOptions>): FlagsClientBrowser;
|
|
205
|
+
identify(user: User): Promise<void>;
|
|
206
|
+
/**
|
|
207
|
+
* Report a structured error into the errors primitive. Flushes immediately
|
|
208
|
+
* (beacon-first) — error occurrences are near-real-time, never queued behind
|
|
209
|
+
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
210
|
+
*/
|
|
211
|
+
reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind, correlationId?: string): void;
|
|
212
|
+
get ready(): boolean;
|
|
213
|
+
private notify;
|
|
214
|
+
initFromBootstrap(data: EvalResponse): void;
|
|
215
|
+
/** Force `getFlag(name)` to return `value`, ignoring URL overrides + eval. */
|
|
216
|
+
overrideFlag(name: string, value: boolean): void;
|
|
217
|
+
/** Force `getConfig(name)` to return `value`, ignoring URL overrides + eval. */
|
|
218
|
+
overrideConfig(name: string, value: unknown): void;
|
|
219
|
+
/**
|
|
220
|
+
* Force `getExperiment(name, …)` to return `{ inExperiment: true, group, params }`,
|
|
221
|
+
* ignoring URL overrides, the eval result, and exposure logging.
|
|
222
|
+
*/
|
|
223
|
+
overrideExperiment(name: string, group: string, params: Record<string, unknown>): void;
|
|
224
|
+
/** Remove every programmatic override set via the override* methods. */
|
|
225
|
+
clearOverrides(): void;
|
|
226
|
+
/**
|
|
227
|
+
* Evaluate a gate and report WHY (LaunchDarkly variationDetail parity). A
|
|
228
|
+
* local override OR a ?se_gate_/?se_ks_ URL override short-circuits BEFORE
|
|
229
|
+
* telemetry; otherwise exactly one "gate" beacon is emitted. The server
|
|
230
|
+
* pre-evaluates a gate's enabled/killed state into a plain boolean, so OFF
|
|
231
|
+
* folds into DEFAULT here (the browser can't tell "disabled" from "rule
|
|
232
|
+
* denied").
|
|
233
|
+
*/
|
|
234
|
+
getFlagDetail(name: string): FlagDetail;
|
|
235
|
+
/**
|
|
236
|
+
* Read a feature gate. Returns `defaultValue` ONLY when the gate cannot be
|
|
237
|
+
* evaluated (not ready or flag not found) — never for a gate that legitimately
|
|
238
|
+
* evaluates to false. Plain `getFlag(name)` keeps returning false for a
|
|
239
|
+
* missing flag.
|
|
240
|
+
*/
|
|
241
|
+
getFlag(name: string, defaultValue?: boolean): boolean;
|
|
242
|
+
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
243
|
+
getConfig<T = unknown>(name: string, opts: GetConfigOptions<T>): T;
|
|
244
|
+
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
|
|
245
|
+
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, opts: GetExperimentOptions<P>): ExperimentResult<P>;
|
|
246
|
+
/**
|
|
247
|
+
* Manually log an exposure for an enrolled experiment (Statsig's
|
|
248
|
+
* `manuallyLogExposure`). Reads the cached eval result; if the visitor is in
|
|
249
|
+
* the experiment, pushes the session-deduped exposure. Pair this with the
|
|
250
|
+
* render of the treatment when reading with `{ logExposure: false }` (or
|
|
251
|
+
* `disableAutoExposure: true`). No-op if the visitor isn't enrolled.
|
|
252
|
+
*/
|
|
253
|
+
logExposure(name: string): void;
|
|
254
|
+
/**
|
|
255
|
+
* Subscribe to state changes — fires after identify() completes and on
|
|
256
|
+
* `se:override:change` events from the devtools overlay. Returns an
|
|
257
|
+
* unsubscribe function. Used by framework adapters to trigger re-renders.
|
|
258
|
+
*/
|
|
259
|
+
subscribe(listener: () => void): () => void;
|
|
260
|
+
/**
|
|
261
|
+
* Publishes the SDK to `window.__shipeasy` so the devtools overlay can read
|
|
262
|
+
* current values. Idempotent. Returns the bridge object for tests.
|
|
263
|
+
*/
|
|
264
|
+
installBridge(): ShipeasySdkBridge | null;
|
|
265
|
+
track(eventName: string, props?: Record<string, unknown>): void;
|
|
266
|
+
/** Drop caller-marked private attributes from an outbound props bag. */
|
|
267
|
+
private stripPrivate;
|
|
268
|
+
/**
|
|
269
|
+
* Read a killswitch from the server's evaluated state. Without `switchKey`,
|
|
270
|
+
* returns true when the killswitch is whole-killed. With `switchKey`, returns
|
|
271
|
+
* the per-switch state. Returns false for unknown killswitches / switches.
|
|
272
|
+
*/
|
|
273
|
+
getKillswitch(name: string, switchKey?: string): boolean;
|
|
274
|
+
flush(): Promise<void>;
|
|
275
|
+
destroy(): void;
|
|
276
|
+
}
|
|
277
|
+
/** Bridge written to window.__shipeasy — mirrors @shipeasy/devtools' contract. */
|
|
278
|
+
interface ShipeasySdkBridge {
|
|
279
|
+
getFlag(name: string): boolean;
|
|
280
|
+
getExperiment(name: string): {
|
|
281
|
+
inExperiment: boolean;
|
|
282
|
+
group: string;
|
|
283
|
+
} | undefined;
|
|
284
|
+
getConfig(name: string): unknown;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* OpenFeature **web** provider for Shipeasy.
|
|
289
|
+
*
|
|
290
|
+
* ```ts
|
|
291
|
+
* import { OpenFeature } from "@openfeature/web-sdk";
|
|
292
|
+
* import { FlagsClientBrowser } from "@shipeasy/sdk/client";
|
|
293
|
+
* import { ShipeasyProvider } from "@shipeasy/sdk/openfeature-web";
|
|
294
|
+
*
|
|
295
|
+
* const client = new FlagsClientBrowser({ sdkKey: NEXT_PUBLIC_SHIPEASY_CLIENT_KEY });
|
|
296
|
+
* await OpenFeature.setContext({ targetingKey: "u1", plan: "pro" });
|
|
297
|
+
* await OpenFeature.setProviderAndWait(new ShipeasyProvider(client));
|
|
298
|
+
*
|
|
299
|
+
* const on = OpenFeature.getClient().getBooleanValue("new_checkout", false);
|
|
300
|
+
* ```
|
|
301
|
+
*
|
|
302
|
+
* The browser evaluates at the edge, so the provider maps OpenFeature's static
|
|
303
|
+
* context onto `client.identify()` (via `initialize` + `onContextChange`) and
|
|
304
|
+
* reads the cached eval result synchronously in the resolve methods.
|
|
305
|
+
* `@openfeature/web-sdk` is an optional peer dependency.
|
|
306
|
+
*/
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Shipeasy OpenFeature provider (client/web paradigm). Wraps a
|
|
310
|
+
* `FlagsClientBrowser`. Context changes are reconciled through `identify()`;
|
|
311
|
+
* flag reads come from the cached eval result.
|
|
312
|
+
*/
|
|
313
|
+
declare class ShipeasyProvider implements Provider {
|
|
314
|
+
private readonly client;
|
|
315
|
+
readonly metadata: {
|
|
316
|
+
readonly name: "shipeasy";
|
|
317
|
+
};
|
|
318
|
+
readonly runsOn: "client";
|
|
319
|
+
constructor(client: FlagsClientBrowser);
|
|
320
|
+
/** Identify with the initial static context so the first read is warm. */
|
|
321
|
+
initialize(context?: EvaluationContext): Promise<void>;
|
|
322
|
+
/** Re-identify when the application author changes the static context. */
|
|
323
|
+
onContextChange(_old: EvaluationContext, next: EvaluationContext): Promise<void>;
|
|
324
|
+
resolveBooleanEvaluation(flagKey: string, defaultValue: boolean, _context: EvaluationContext, _logger?: Logger): ResolutionDetails<boolean>;
|
|
325
|
+
resolveStringEvaluation(flagKey: string, defaultValue: string, _context: EvaluationContext, _logger?: Logger): ResolutionDetails<string>;
|
|
326
|
+
resolveNumberEvaluation(flagKey: string, defaultValue: number, _context: EvaluationContext, _logger?: Logger): ResolutionDetails<number>;
|
|
327
|
+
resolveObjectEvaluation<T extends JsonValue>(flagKey: string, defaultValue: T, _context: EvaluationContext, _logger?: Logger): ResolutionDetails<T>;
|
|
328
|
+
/** OpenFeature `track()` → Shipeasy `track()` (uses the identified user/anon). */
|
|
329
|
+
track(trackingEventName: string, _context: EvaluationContext, details?: Record<string, unknown>): void;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export { ShipeasyProvider };
|