@shipeasy/sdk 6.0.0 → 6.3.1

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.
@@ -1,5 +1,7 @@
1
1
  import { Provider, EvaluationContext, Logger, ResolutionDetails, JsonValue } from '@openfeature/server-sdk';
2
2
 
3
+ type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
4
+
3
5
  type SeeExtras = Record<string, string | number | boolean | null | undefined>;
4
6
  type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
5
7
  /** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
@@ -137,6 +139,16 @@ interface EngineOptions {
137
139
  disableTelemetry?: boolean;
138
140
  /** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
139
141
  telemetryUrl?: string;
142
+ /**
143
+ * How chatty the SDK is on `console` when it catches an error internally.
144
+ * Every public runtime method (getFlag/getConfig/getExperiment/getKillswitch/
145
+ * track/logExposure/see) fails silently — it returns a safe default rather
146
+ * than throwing — and surfaces the swallowed error through this level so you
147
+ * still find out. Ordering: `silent` < `error` < `warn` < `info` < `debug`.
148
+ * Defaults to `"warn"` (prints `error` + `warn`). Pass `"silent"` to mute the
149
+ * SDK entirely. See {@link LogLevel}.
150
+ */
151
+ logLevel?: LogLevel;
140
152
  /**
141
153
  * Attribute names usable for targeting but never persisted in analytics
142
154
  * (LD/Statsig `privateAttributes`). The server evaluates locally so private
@@ -318,12 +330,25 @@ declare class Engine {
318
330
  * synchronous (the methods are `async` only to satisfy the server contract).
319
331
  */
320
332
  declare class ShipeasyProvider implements Provider {
321
- private readonly client;
322
333
  readonly metadata: {
323
334
  readonly name: "shipeasy";
324
335
  };
325
336
  readonly runsOn: "server";
326
- constructor(client: Engine);
337
+ private readonly client;
338
+ /**
339
+ * Construct the provider. The **global form** (no argument) resolves the
340
+ * engine built by `configure({ apiKey })`, so the docs build it after
341
+ * configuration without ever naming the `Engine`:
342
+ *
343
+ * ```ts
344
+ * configure({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
345
+ * await OpenFeature.setProviderAndWait(new ShipeasyProvider());
346
+ * ```
347
+ *
348
+ * Passing an explicit `Engine` stays supported for advanced/back-compat use.
349
+ * Throws if no engine is passed and `configure()` has not run.
350
+ */
351
+ constructor(client?: Engine);
327
352
  /** Fetch the rules blob once. The SDK fires `Ready` when this resolves. */
328
353
  initialize(): Promise<void>;
329
354
  onClose(): Promise<void>;
@@ -1,5 +1,7 @@
1
1
  import { Provider, EvaluationContext, Logger, ResolutionDetails, JsonValue } from '@openfeature/server-sdk';
2
2
 
3
+ type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
4
+
3
5
  type SeeExtras = Record<string, string | number | boolean | null | undefined>;
4
6
  type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
5
7
  /** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
@@ -137,6 +139,16 @@ interface EngineOptions {
137
139
  disableTelemetry?: boolean;
138
140
  /** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
139
141
  telemetryUrl?: string;
142
+ /**
143
+ * How chatty the SDK is on `console` when it catches an error internally.
144
+ * Every public runtime method (getFlag/getConfig/getExperiment/getKillswitch/
145
+ * track/logExposure/see) fails silently — it returns a safe default rather
146
+ * than throwing — and surfaces the swallowed error through this level so you
147
+ * still find out. Ordering: `silent` < `error` < `warn` < `info` < `debug`.
148
+ * Defaults to `"warn"` (prints `error` + `warn`). Pass `"silent"` to mute the
149
+ * SDK entirely. See {@link LogLevel}.
150
+ */
151
+ logLevel?: LogLevel;
140
152
  /**
141
153
  * Attribute names usable for targeting but never persisted in analytics
142
154
  * (LD/Statsig `privateAttributes`). The server evaluates locally so private
@@ -318,12 +330,25 @@ declare class Engine {
318
330
  * synchronous (the methods are `async` only to satisfy the server contract).
319
331
  */
320
332
  declare class ShipeasyProvider implements Provider {
321
- private readonly client;
322
333
  readonly metadata: {
323
334
  readonly name: "shipeasy";
324
335
  };
325
336
  readonly runsOn: "server";
326
- constructor(client: Engine);
337
+ private readonly client;
338
+ /**
339
+ * Construct the provider. The **global form** (no argument) resolves the
340
+ * engine built by `configure({ apiKey })`, so the docs build it after
341
+ * configuration without ever naming the `Engine`:
342
+ *
343
+ * ```ts
344
+ * configure({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
345
+ * await OpenFeature.setProviderAndWait(new ShipeasyProvider());
346
+ * ```
347
+ *
348
+ * Passing an explicit `Engine` stays supported for advanced/back-compat use.
349
+ * Throws if no engine is passed and `configure()` has not run.
350
+ */
351
+ constructor(client?: Engine);
327
352
  /** Fetch the rules blob once. The SDK fires `Ready` when this resolves. */
328
353
  initialize(): Promise<void>;
329
354
  onClose(): Promise<void>;
@@ -25,6 +25,210 @@ __export(openfeature_server_exports, {
25
25
  module.exports = __toCommonJS(openfeature_server_exports);
26
26
  var import_server_sdk = require("@openfeature/server-sdk");
27
27
 
28
+ // src/server/index.ts
29
+ var import_node_async_hooks = require("async_hooks");
30
+
31
+ // src/logger.ts
32
+ var RANK = {
33
+ silent: 0,
34
+ error: 1,
35
+ warn: 2,
36
+ info: 3,
37
+ debug: 4
38
+ };
39
+ var currentLevel = "warn";
40
+ function write(method, args) {
41
+ try {
42
+ const c = globalThis.console;
43
+ if (!c) return;
44
+ const fn = c[method] ?? c.log;
45
+ if (typeof fn === "function") fn.call(c, ...args);
46
+ } catch {
47
+ }
48
+ }
49
+ var logger = {
50
+ error(...args) {
51
+ if (RANK[currentLevel] >= RANK.error) write("error", args);
52
+ },
53
+ warn(...args) {
54
+ if (RANK[currentLevel] >= RANK.warn) write("warn", args);
55
+ },
56
+ info(...args) {
57
+ if (RANK[currentLevel] >= RANK.info) write("info", args);
58
+ },
59
+ debug(...args) {
60
+ if (RANK[currentLevel] >= RANK.debug) write("debug", args);
61
+ }
62
+ };
63
+
64
+ // src/see/core.ts
65
+ var SEE_MAX_SUBJECT = 200;
66
+ var SEE_MAX_EXTRA_VALUE = 200;
67
+ var SEE_MAX_EXTRA_KEYS = 20;
68
+ function causesThe(subject) {
69
+ return {
70
+ to(outcome) {
71
+ return {
72
+ __seConsequence: true,
73
+ subject: truncate(String(subject), SEE_MAX_SUBJECT),
74
+ outcome: truncate(String(outcome), SEE_MAX_SUBJECT)
75
+ };
76
+ }
77
+ };
78
+ }
79
+ function violation(name) {
80
+ return { __seViolation: true, violationName: String(name) };
81
+ }
82
+ var EXPECTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-expected");
83
+ function readExpectedMark(err) {
84
+ if (typeof err !== "object" || err === null) return void 0;
85
+ const v = err[EXPECTED_SYM];
86
+ return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
87
+ }
88
+ function markExpected(err, because, extras) {
89
+ if (typeof err !== "object" || err === null) return;
90
+ const prev = readExpectedMark(err);
91
+ const clean = sanitizeExtras(extras);
92
+ const merged = prev?.extras || clean ? { ...prev?.extras, ...clean } : void 0;
93
+ const mark = {
94
+ because: String(because),
95
+ ...merged ? { extras: merged } : {}
96
+ };
97
+ try {
98
+ Object.defineProperty(err, EXPECTED_SYM, {
99
+ value: mark,
100
+ enumerable: false,
101
+ configurable: true
102
+ });
103
+ } catch {
104
+ }
105
+ }
106
+ function truncate(s, max) {
107
+ return s.length > max ? s.slice(0, max) : s;
108
+ }
109
+ function sanitizeExtras(extras) {
110
+ if (!extras || typeof extras !== "object") return void 0;
111
+ const out = {};
112
+ let n = 0;
113
+ for (const [k, v] of Object.entries(extras)) {
114
+ if (v === null || v === void 0) continue;
115
+ if (n >= SEE_MAX_EXTRA_KEYS) break;
116
+ if (typeof v === "string") out[k] = truncate(v, SEE_MAX_EXTRA_VALUE);
117
+ else if (typeof v === "number" && Number.isFinite(v)) out[k] = v;
118
+ else if (typeof v === "boolean") out[k] = v;
119
+ else continue;
120
+ n += 1;
121
+ }
122
+ return n > 0 ? out : void 0;
123
+ }
124
+ var scheduleMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : (cb) => {
125
+ void Promise.resolve().then(cb);
126
+ };
127
+ function startSeeChain(getProblem, dispatch) {
128
+ let subject;
129
+ let outcome;
130
+ let collected;
131
+ let flushed = false;
132
+ scheduleMicrotask(() => {
133
+ if (flushed) return;
134
+ flushed = true;
135
+ dispatch(
136
+ getProblem(),
137
+ // Bare noun phrase — titles render as "… causes the {subject} …", so a
138
+ // leading article would double up ("causes the the app").
139
+ causesThe(subject ?? "app").to(outcome ?? "hit an error"),
140
+ collected
141
+ );
142
+ });
143
+ const tail = {
144
+ extras(x) {
145
+ if (x && typeof x === "object") collected = { ...collected, ...x };
146
+ return tail;
147
+ }
148
+ };
149
+ const step = {
150
+ to(o) {
151
+ outcome = String(o);
152
+ return tail;
153
+ }
154
+ };
155
+ const start = (s) => {
156
+ subject = String(s);
157
+ return step;
158
+ };
159
+ return { causes_the: start, causesThe: start };
160
+ }
161
+ function startSeeViolationChain(name, dispatch) {
162
+ return startSeeChain(() => violation(name), dispatch);
163
+ }
164
+ function startControlFlowChain(err) {
165
+ return {
166
+ because(reason) {
167
+ markExpected(err, reason);
168
+ const tail = {
169
+ extras(x) {
170
+ markExpected(err, reason, x);
171
+ return tail;
172
+ }
173
+ };
174
+ return tail;
175
+ }
176
+ };
177
+ }
178
+
179
+ // src/server/index.ts
180
+ var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
181
+ var _EDIT_MODE_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode");
182
+ var _i18nALS = new import_node_async_hooks.AsyncLocalStorage();
183
+ var _I18N_CACHE_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n-cache");
184
+ var _i18nCache = globalThis[_I18N_CACHE_SYM] ?? (globalThis[_I18N_CACHE_SYM] = /* @__PURE__ */ new Map());
185
+ globalThis[_I18N_SSR_SYM] = () => {
186
+ const fromALS = _i18nALS.getStore();
187
+ if (fromALS && Object.keys(fromALS.strings).length > 0) return fromALS;
188
+ for (const v of _i18nCache.values()) {
189
+ if (Object.keys(v.strings).length > 0) return v;
190
+ }
191
+ return fromALS ?? null;
192
+ };
193
+ var _EDIT_MODE_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-als");
194
+ var _editModeALS = globalThis[_EDIT_MODE_ALS_SYM] ?? (globalThis[_EDIT_MODE_ALS_SYM] = new import_node_async_hooks.AsyncLocalStorage());
195
+ var _EDIT_MODE_FALLBACK_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-fallback");
196
+ if (globalThis[_EDIT_MODE_FALLBACK_SYM] === void 0) {
197
+ globalThis[_EDIT_MODE_FALLBACK_SYM] = false;
198
+ }
199
+ Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
200
+ get: () => _editModeALS.getStore() ?? globalThis[_EDIT_MODE_FALLBACK_SYM] ?? false,
201
+ set: (v) => {
202
+ const b = Boolean(v);
203
+ try {
204
+ _editModeALS.enterWith(b);
205
+ } catch {
206
+ }
207
+ globalThis[_EDIT_MODE_FALLBACK_SYM] = b;
208
+ },
209
+ configurable: true
210
+ });
211
+ var _SEE_CORR_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-correlation-als");
212
+ var seeContext = globalThis[_SEE_CORR_ALS_SYM] ?? (globalThis[_SEE_CORR_ALS_SYM] = new import_node_async_hooks.AsyncLocalStorage());
213
+ var _server = null;
214
+ function getShipeasyServerClient() {
215
+ return _server;
216
+ }
217
+ function dispatchSee(problem, consequence, extras, kind) {
218
+ if (!_server) {
219
+ logger.warn("[shipeasy] see() called before shipeasy({ serverKey }) \u2014 error dropped");
220
+ return;
221
+ }
222
+ _server.reportError(problem, consequence, extras, kind);
223
+ }
224
+ var see = Object.assign(
225
+ (problem) => startSeeChain(() => problem, dispatchSee),
226
+ {
227
+ Violation: (name) => startSeeViolationChain(name, dispatchSee),
228
+ ControlFlowException: (err) => startControlFlowChain(err)
229
+ }
230
+ );
231
+
28
232
  // src/openfeature/shared.ts
29
233
  var REASON_MAP = {
30
234
  RULE_MATCH: { reason: "TARGETING_MATCH" },
@@ -78,12 +282,31 @@ function resolveConfig(raw, defaultValue, type) {
78
282
  return { value: r.value, reason: "TARGETING_MATCH" };
79
283
  }
80
284
  var ShipeasyProvider = class {
81
- constructor(client) {
82
- this.client = client;
83
- }
84
- client;
85
285
  metadata = { name: "shipeasy" };
86
286
  runsOn = "server";
287
+ client;
288
+ /**
289
+ * Construct the provider. The **global form** (no argument) resolves the
290
+ * engine built by `configure({ apiKey })`, so the docs build it after
291
+ * configuration without ever naming the `Engine`:
292
+ *
293
+ * ```ts
294
+ * configure({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
295
+ * await OpenFeature.setProviderAndWait(new ShipeasyProvider());
296
+ * ```
297
+ *
298
+ * Passing an explicit `Engine` stays supported for advanced/back-compat use.
299
+ * Throws if no engine is passed and `configure()` has not run.
300
+ */
301
+ constructor(client) {
302
+ const resolved = client ?? getShipeasyServerClient();
303
+ if (!resolved) {
304
+ throw new Error(
305
+ "[shipeasy] new ShipeasyProvider() resolves the configured global engine \u2014 call configure({ apiKey }) first, or pass an Engine explicitly."
306
+ );
307
+ }
308
+ this.client = resolved;
309
+ }
87
310
  /** Fetch the rules blob once. The SDK fires `Ready` when this resolves. */
88
311
  async initialize() {
89
312
  await this.client.initOnce();
@@ -1,6 +1,210 @@
1
1
  // src/openfeature-server/index.ts
2
2
  import { ErrorCode } from "@openfeature/server-sdk";
3
3
 
4
+ // src/server/index.ts
5
+ import { AsyncLocalStorage } from "async_hooks";
6
+
7
+ // src/logger.ts
8
+ var RANK = {
9
+ silent: 0,
10
+ error: 1,
11
+ warn: 2,
12
+ info: 3,
13
+ debug: 4
14
+ };
15
+ var currentLevel = "warn";
16
+ function write(method, args) {
17
+ try {
18
+ const c = globalThis.console;
19
+ if (!c) return;
20
+ const fn = c[method] ?? c.log;
21
+ if (typeof fn === "function") fn.call(c, ...args);
22
+ } catch {
23
+ }
24
+ }
25
+ var logger = {
26
+ error(...args) {
27
+ if (RANK[currentLevel] >= RANK.error) write("error", args);
28
+ },
29
+ warn(...args) {
30
+ if (RANK[currentLevel] >= RANK.warn) write("warn", args);
31
+ },
32
+ info(...args) {
33
+ if (RANK[currentLevel] >= RANK.info) write("info", args);
34
+ },
35
+ debug(...args) {
36
+ if (RANK[currentLevel] >= RANK.debug) write("debug", args);
37
+ }
38
+ };
39
+
40
+ // src/see/core.ts
41
+ var SEE_MAX_SUBJECT = 200;
42
+ var SEE_MAX_EXTRA_VALUE = 200;
43
+ var SEE_MAX_EXTRA_KEYS = 20;
44
+ function causesThe(subject) {
45
+ return {
46
+ to(outcome) {
47
+ return {
48
+ __seConsequence: true,
49
+ subject: truncate(String(subject), SEE_MAX_SUBJECT),
50
+ outcome: truncate(String(outcome), SEE_MAX_SUBJECT)
51
+ };
52
+ }
53
+ };
54
+ }
55
+ function violation(name) {
56
+ return { __seViolation: true, violationName: String(name) };
57
+ }
58
+ var EXPECTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-expected");
59
+ function readExpectedMark(err) {
60
+ if (typeof err !== "object" || err === null) return void 0;
61
+ const v = err[EXPECTED_SYM];
62
+ return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
63
+ }
64
+ function markExpected(err, because, extras) {
65
+ if (typeof err !== "object" || err === null) return;
66
+ const prev = readExpectedMark(err);
67
+ const clean = sanitizeExtras(extras);
68
+ const merged = prev?.extras || clean ? { ...prev?.extras, ...clean } : void 0;
69
+ const mark = {
70
+ because: String(because),
71
+ ...merged ? { extras: merged } : {}
72
+ };
73
+ try {
74
+ Object.defineProperty(err, EXPECTED_SYM, {
75
+ value: mark,
76
+ enumerable: false,
77
+ configurable: true
78
+ });
79
+ } catch {
80
+ }
81
+ }
82
+ function truncate(s, max) {
83
+ return s.length > max ? s.slice(0, max) : s;
84
+ }
85
+ function sanitizeExtras(extras) {
86
+ if (!extras || typeof extras !== "object") return void 0;
87
+ const out = {};
88
+ let n = 0;
89
+ for (const [k, v] of Object.entries(extras)) {
90
+ if (v === null || v === void 0) continue;
91
+ if (n >= SEE_MAX_EXTRA_KEYS) break;
92
+ if (typeof v === "string") out[k] = truncate(v, SEE_MAX_EXTRA_VALUE);
93
+ else if (typeof v === "number" && Number.isFinite(v)) out[k] = v;
94
+ else if (typeof v === "boolean") out[k] = v;
95
+ else continue;
96
+ n += 1;
97
+ }
98
+ return n > 0 ? out : void 0;
99
+ }
100
+ var scheduleMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : (cb) => {
101
+ void Promise.resolve().then(cb);
102
+ };
103
+ function startSeeChain(getProblem, dispatch) {
104
+ let subject;
105
+ let outcome;
106
+ let collected;
107
+ let flushed = false;
108
+ scheduleMicrotask(() => {
109
+ if (flushed) return;
110
+ flushed = true;
111
+ dispatch(
112
+ getProblem(),
113
+ // Bare noun phrase — titles render as "… causes the {subject} …", so a
114
+ // leading article would double up ("causes the the app").
115
+ causesThe(subject ?? "app").to(outcome ?? "hit an error"),
116
+ collected
117
+ );
118
+ });
119
+ const tail = {
120
+ extras(x) {
121
+ if (x && typeof x === "object") collected = { ...collected, ...x };
122
+ return tail;
123
+ }
124
+ };
125
+ const step = {
126
+ to(o) {
127
+ outcome = String(o);
128
+ return tail;
129
+ }
130
+ };
131
+ const start = (s) => {
132
+ subject = String(s);
133
+ return step;
134
+ };
135
+ return { causes_the: start, causesThe: start };
136
+ }
137
+ function startSeeViolationChain(name, dispatch) {
138
+ return startSeeChain(() => violation(name), dispatch);
139
+ }
140
+ function startControlFlowChain(err) {
141
+ return {
142
+ because(reason) {
143
+ markExpected(err, reason);
144
+ const tail = {
145
+ extras(x) {
146
+ markExpected(err, reason, x);
147
+ return tail;
148
+ }
149
+ };
150
+ return tail;
151
+ }
152
+ };
153
+ }
154
+
155
+ // src/server/index.ts
156
+ var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
157
+ var _EDIT_MODE_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode");
158
+ var _i18nALS = new AsyncLocalStorage();
159
+ var _I18N_CACHE_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n-cache");
160
+ var _i18nCache = globalThis[_I18N_CACHE_SYM] ?? (globalThis[_I18N_CACHE_SYM] = /* @__PURE__ */ new Map());
161
+ globalThis[_I18N_SSR_SYM] = () => {
162
+ const fromALS = _i18nALS.getStore();
163
+ if (fromALS && Object.keys(fromALS.strings).length > 0) return fromALS;
164
+ for (const v of _i18nCache.values()) {
165
+ if (Object.keys(v.strings).length > 0) return v;
166
+ }
167
+ return fromALS ?? null;
168
+ };
169
+ var _EDIT_MODE_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-als");
170
+ var _editModeALS = globalThis[_EDIT_MODE_ALS_SYM] ?? (globalThis[_EDIT_MODE_ALS_SYM] = new AsyncLocalStorage());
171
+ var _EDIT_MODE_FALLBACK_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-fallback");
172
+ if (globalThis[_EDIT_MODE_FALLBACK_SYM] === void 0) {
173
+ globalThis[_EDIT_MODE_FALLBACK_SYM] = false;
174
+ }
175
+ Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
176
+ get: () => _editModeALS.getStore() ?? globalThis[_EDIT_MODE_FALLBACK_SYM] ?? false,
177
+ set: (v) => {
178
+ const b = Boolean(v);
179
+ try {
180
+ _editModeALS.enterWith(b);
181
+ } catch {
182
+ }
183
+ globalThis[_EDIT_MODE_FALLBACK_SYM] = b;
184
+ },
185
+ configurable: true
186
+ });
187
+ var _SEE_CORR_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-correlation-als");
188
+ var seeContext = globalThis[_SEE_CORR_ALS_SYM] ?? (globalThis[_SEE_CORR_ALS_SYM] = new AsyncLocalStorage());
189
+ var _server = null;
190
+ function getShipeasyServerClient() {
191
+ return _server;
192
+ }
193
+ function dispatchSee(problem, consequence, extras, kind) {
194
+ if (!_server) {
195
+ logger.warn("[shipeasy] see() called before shipeasy({ serverKey }) \u2014 error dropped");
196
+ return;
197
+ }
198
+ _server.reportError(problem, consequence, extras, kind);
199
+ }
200
+ var see = Object.assign(
201
+ (problem) => startSeeChain(() => problem, dispatchSee),
202
+ {
203
+ Violation: (name) => startSeeViolationChain(name, dispatchSee),
204
+ ControlFlowException: (err) => startControlFlowChain(err)
205
+ }
206
+ );
207
+
4
208
  // src/openfeature/shared.ts
5
209
  var REASON_MAP = {
6
210
  RULE_MATCH: { reason: "TARGETING_MATCH" },
@@ -54,12 +258,31 @@ function resolveConfig(raw, defaultValue, type) {
54
258
  return { value: r.value, reason: "TARGETING_MATCH" };
55
259
  }
56
260
  var ShipeasyProvider = class {
57
- constructor(client) {
58
- this.client = client;
59
- }
60
- client;
61
261
  metadata = { name: "shipeasy" };
62
262
  runsOn = "server";
263
+ client;
264
+ /**
265
+ * Construct the provider. The **global form** (no argument) resolves the
266
+ * engine built by `configure({ apiKey })`, so the docs build it after
267
+ * configuration without ever naming the `Engine`:
268
+ *
269
+ * ```ts
270
+ * configure({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
271
+ * await OpenFeature.setProviderAndWait(new ShipeasyProvider());
272
+ * ```
273
+ *
274
+ * Passing an explicit `Engine` stays supported for advanced/back-compat use.
275
+ * Throws if no engine is passed and `configure()` has not run.
276
+ */
277
+ constructor(client) {
278
+ const resolved = client ?? getShipeasyServerClient();
279
+ if (!resolved) {
280
+ throw new Error(
281
+ "[shipeasy] new ShipeasyProvider() resolves the configured global engine \u2014 call configure({ apiKey }) first, or pass an Engine explicitly."
282
+ );
283
+ }
284
+ this.client = resolved;
285
+ }
63
286
  /** Fetch the rules blob once. The SDK fires `Ready` when this resolves. */
64
287
  async initialize() {
65
288
  await this.client.initOnce();
@@ -1,5 +1,7 @@
1
1
  import { Provider, EvaluationContext, Logger, ResolutionDetails, JsonValue } from '@openfeature/web-sdk';
2
2
 
3
+ type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
4
+
3
5
  type SeeExtras = Record<string, string | number | boolean | null | undefined>;
4
6
  type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
5
7
  /** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
@@ -131,6 +133,14 @@ interface EngineOptions {
131
133
  disableTelemetry?: boolean;
132
134
  /** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
133
135
  telemetryUrl?: string;
136
+ /**
137
+ * How chatty the SDK is on `console` when it catches an error internally.
138
+ * Every public runtime method (getFlag/getConfig/getExperiment/getKillswitch/
139
+ * track/logExposure/see) fails silently — returning a safe default instead of
140
+ * throwing — and surfaces the swallowed error through this level. Ordering:
141
+ * `silent` < `error` < `warn` < `info` < `debug`. Defaults to `"warn"`.
142
+ */
143
+ logLevel?: LogLevel;
134
144
  /**
135
145
  * Suppress automatic exposure logging in `getExperiment` (Statsig's
136
146
  * `disableExposureLogging`). Default false — reading an enrolled variant
@@ -1,5 +1,7 @@
1
1
  import { Provider, EvaluationContext, Logger, ResolutionDetails, JsonValue } from '@openfeature/web-sdk';
2
2
 
3
+ type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
4
+
3
5
  type SeeExtras = Record<string, string | number | boolean | null | undefined>;
4
6
  type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
5
7
  /** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
@@ -131,6 +133,14 @@ interface EngineOptions {
131
133
  disableTelemetry?: boolean;
132
134
  /** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
133
135
  telemetryUrl?: string;
136
+ /**
137
+ * How chatty the SDK is on `console` when it catches an error internally.
138
+ * Every public runtime method (getFlag/getConfig/getExperiment/getKillswitch/
139
+ * track/logExposure/see) fails silently — returning a safe default instead of
140
+ * throwing — and surfaces the swallowed error through this level. Ordering:
141
+ * `silent` < `error` < `warn` < `info` < `debug`. Defaults to `"warn"`.
142
+ */
143
+ logLevel?: LogLevel;
134
144
  /**
135
145
  * Suppress automatic exposure logging in `getExperiment` (Statsig's
136
146
  * `disableExposureLogging`). Default false — reading an enrolled variant