@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.
- package/README.md +78 -351
- package/dist/client/index.d.mts +34 -1
- package/dist/client/index.d.ts +34 -1
- package/dist/client/index.js +142 -50
- package/dist/client/index.mjs +141 -50
- package/dist/openfeature-server/index.d.mts +27 -2
- package/dist/openfeature-server/index.d.ts +27 -2
- package/dist/openfeature-server/index.js +227 -4
- package/dist/openfeature-server/index.mjs +227 -4
- package/dist/openfeature-web/index.d.mts +10 -0
- package/dist/openfeature-web/index.d.ts +10 -0
- package/dist/server/index.d.mts +122 -1
- package/dist/server/index.d.ts +122 -1
- package/dist/server/index.js +214 -37
- package/dist/server/index.mjs +206 -37
- package/dist/skill-cli.js +56 -0
- package/docs/skill/SKILL.md +145 -0
- package/package.json +13 -6
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|