@shipeasy/sdk 6.2.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/dist/client/index.d.mts +20 -1
- package/dist/client/index.d.ts +20 -1
- package/dist/client/index.js +126 -52
- package/dist/client/index.mjs +125 -52
- package/dist/openfeature-server/index.d.mts +12 -0
- package/dist/openfeature-server/index.d.ts +12 -0
- package/dist/openfeature-server/index.js +34 -1
- package/dist/openfeature-server/index.mjs +34 -1
- package/dist/openfeature-web/index.d.mts +10 -0
- package/dist/openfeature-web/index.d.ts +10 -0
- package/dist/server/index.d.mts +22 -1
- package/dist/server/index.d.ts +22 -1
- package/dist/server/index.js +117 -37
- package/dist/server/index.mjs +116 -37
- package/docs/skill/SKILL.md +16 -16
- package/package.json +7 -6
package/dist/server/index.js
CHANGED
|
@@ -34,6 +34,7 @@ __export(server_exports, {
|
|
|
34
34
|
Client: () => Client,
|
|
35
35
|
Engine: () => Engine,
|
|
36
36
|
FLAG_REASONS: () => FLAG_REASONS,
|
|
37
|
+
LOG_LEVELS: () => LOG_LEVELS,
|
|
37
38
|
_murmur3ForTests: () => _murmur3ForTests,
|
|
38
39
|
_resetConfigureForTests: () => _resetConfigureForTests,
|
|
39
40
|
_resetShipeasyServerForTests: () => _resetShipeasyServerForTests,
|
|
@@ -121,6 +122,53 @@ function send(url) {
|
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
// src/logger.ts
|
|
126
|
+
var LOG_LEVELS = ["silent", "error", "warn", "info", "debug"];
|
|
127
|
+
var RANK = {
|
|
128
|
+
silent: 0,
|
|
129
|
+
error: 1,
|
|
130
|
+
warn: 2,
|
|
131
|
+
info: 3,
|
|
132
|
+
debug: 4
|
|
133
|
+
};
|
|
134
|
+
var currentLevel = "warn";
|
|
135
|
+
function setLogLevel(level) {
|
|
136
|
+
if (typeof level === "string" && Object.prototype.hasOwnProperty.call(RANK, level)) {
|
|
137
|
+
currentLevel = level;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function write(method, args) {
|
|
141
|
+
try {
|
|
142
|
+
const c = globalThis.console;
|
|
143
|
+
if (!c) return;
|
|
144
|
+
const fn = c[method] ?? c.log;
|
|
145
|
+
if (typeof fn === "function") fn.call(c, ...args);
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
var logger = {
|
|
150
|
+
error(...args) {
|
|
151
|
+
if (RANK[currentLevel] >= RANK.error) write("error", args);
|
|
152
|
+
},
|
|
153
|
+
warn(...args) {
|
|
154
|
+
if (RANK[currentLevel] >= RANK.warn) write("warn", args);
|
|
155
|
+
},
|
|
156
|
+
info(...args) {
|
|
157
|
+
if (RANK[currentLevel] >= RANK.info) write("info", args);
|
|
158
|
+
},
|
|
159
|
+
debug(...args) {
|
|
160
|
+
if (RANK[currentLevel] >= RANK.debug) write("debug", args);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
function safeRun(label, fallback, fn) {
|
|
164
|
+
try {
|
|
165
|
+
return fn();
|
|
166
|
+
} catch (err) {
|
|
167
|
+
logger.error(`[shipeasy] ${label} failed \u2014 returning safe default:`, String(err));
|
|
168
|
+
return fallback;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
124
172
|
// src/see/core.ts
|
|
125
173
|
var SEE_MAX_MESSAGE = 500;
|
|
126
174
|
var SEE_MAX_STACK = 8e3;
|
|
@@ -582,6 +630,7 @@ var Engine = class _Engine {
|
|
|
582
630
|
// 304). Never fired in testMode/offline (no polling happens there).
|
|
583
631
|
changeListeners = /* @__PURE__ */ new Set();
|
|
584
632
|
constructor(opts) {
|
|
633
|
+
setLogLevel(opts.logLevel);
|
|
585
634
|
this.apiKey = opts.apiKey;
|
|
586
635
|
this.baseUrl = (opts.baseUrl ?? "https://cdn.shipeasy.ai").replace(/\/$/, "");
|
|
587
636
|
this.env = opts.env ?? "prod";
|
|
@@ -705,14 +754,14 @@ var Engine = class _Engine {
|
|
|
705
754
|
try {
|
|
706
755
|
l();
|
|
707
756
|
} catch (err) {
|
|
708
|
-
|
|
757
|
+
logger.warn("[shipeasy] onChange listener threw:", String(err));
|
|
709
758
|
}
|
|
710
759
|
}
|
|
711
760
|
}
|
|
712
761
|
startPoll() {
|
|
713
762
|
this.timer = setInterval(() => {
|
|
714
763
|
this.fetchAll(true).catch(
|
|
715
|
-
(err) =>
|
|
764
|
+
(err) => logger.warn("[shipeasy] background poll failed:", String(err))
|
|
716
765
|
);
|
|
717
766
|
}, this.pollInterval * 1e3);
|
|
718
767
|
}
|
|
@@ -791,7 +840,12 @@ var Engine = class _Engine {
|
|
|
791
840
|
return "defaultValue" in opts ? opts.defaultValue : void 0;
|
|
792
841
|
}
|
|
793
842
|
if (!opts.decode) return raw;
|
|
794
|
-
|
|
843
|
+
try {
|
|
844
|
+
return opts.decode(raw);
|
|
845
|
+
} catch (err) {
|
|
846
|
+
logger.warn(`[shipeasy] getConfig('${name}') decode failed:`, String(err));
|
|
847
|
+
return "defaultValue" in opts ? opts.defaultValue : void 0;
|
|
848
|
+
}
|
|
795
849
|
}
|
|
796
850
|
getExperiment(name, user, defaultParams, decode) {
|
|
797
851
|
this.telemetry.emit("experiment", name);
|
|
@@ -806,7 +860,7 @@ var Engine = class _Engine {
|
|
|
806
860
|
try {
|
|
807
861
|
return { inExperiment: true, group: ov.group, params: decode(ov.params) };
|
|
808
862
|
} catch (err) {
|
|
809
|
-
|
|
863
|
+
logger.warn(`[shipeasy] getExperiment('${name}') override decode failed:`, String(err));
|
|
810
864
|
return notIn;
|
|
811
865
|
}
|
|
812
866
|
}
|
|
@@ -831,7 +885,7 @@ var Engine = class _Engine {
|
|
|
831
885
|
try {
|
|
832
886
|
return { inExperiment: true, group: g.name, params: decode(g.params) };
|
|
833
887
|
} catch (err) {
|
|
834
|
-
|
|
888
|
+
logger.warn(`[shipeasy] getExperiment('${name}') decode failed:`, String(err));
|
|
835
889
|
return notIn;
|
|
836
890
|
}
|
|
837
891
|
};
|
|
@@ -883,7 +937,7 @@ var Engine = class _Engine {
|
|
|
883
937
|
method: "POST",
|
|
884
938
|
headers: { "X-SDK-Key": this.apiKey, "Content-Type": "text/plain" },
|
|
885
939
|
body
|
|
886
|
-
}).catch((err) =>
|
|
940
|
+
}).catch((err) => logger.warn("[shipeasy] track failed:", String(err)));
|
|
887
941
|
}
|
|
888
942
|
/**
|
|
889
943
|
* Emit an exposure event for an experiment at the server-side decision point
|
|
@@ -914,7 +968,7 @@ var Engine = class _Engine {
|
|
|
914
968
|
method: "POST",
|
|
915
969
|
headers: { "X-SDK-Key": this.apiKey, "Content-Type": "text/plain" },
|
|
916
970
|
body
|
|
917
|
-
}).catch((err) =>
|
|
971
|
+
}).catch((err) => logger.warn("[shipeasy] logExposure failed:", String(err)));
|
|
918
972
|
}
|
|
919
973
|
/**
|
|
920
974
|
* Report a structured error into the errors primitive. Fire-and-forget —
|
|
@@ -934,7 +988,7 @@ var Engine = class _Engine {
|
|
|
934
988
|
method: "POST",
|
|
935
989
|
headers: { "X-SDK-Key": this.apiKey, "Content-Type": "text/plain" },
|
|
936
990
|
body: JSON.stringify({ events: [ev] })
|
|
937
|
-
}).catch((err) =>
|
|
991
|
+
}).catch((err) => logger.warn("[shipeasy] see() send failed:", String(err)));
|
|
938
992
|
} catch {
|
|
939
993
|
}
|
|
940
994
|
}
|
|
@@ -1121,7 +1175,7 @@ function _resetShipeasyServerForTests() {
|
|
|
1121
1175
|
async function shipeasy(opts) {
|
|
1122
1176
|
const serverKey = opts.serverKey ?? "";
|
|
1123
1177
|
if (!serverKey) {
|
|
1124
|
-
|
|
1178
|
+
logger.error(
|
|
1125
1179
|
"[shipeasy] No server key \u2014 flags, experiments and SSR i18n skipped. Pass `serverKey` to shipeasy() from @shipeasy/sdk/server with your server key (SHIPEASY_SERVER_KEY). Set it as a Worker secret with `wrangler secret put SHIPEASY_SERVER_KEY` (or add it to .env for local dev). Do not pass a client key here \u2014 the server entrypoint only accepts the server key."
|
|
1126
1180
|
);
|
|
1127
1181
|
}
|
|
@@ -1129,7 +1183,8 @@ async function shipeasy(opts) {
|
|
|
1129
1183
|
flags.configure({
|
|
1130
1184
|
apiKey: serverKey,
|
|
1131
1185
|
disableTelemetry: opts.disableTelemetry,
|
|
1132
|
-
privateAttributes: opts.privateAttributes
|
|
1186
|
+
privateAttributes: opts.privateAttributes,
|
|
1187
|
+
logLevel: opts.logLevel
|
|
1133
1188
|
});
|
|
1134
1189
|
let resolvedUrlOverrides = opts.urlOverrides;
|
|
1135
1190
|
if (!resolvedUrlOverrides) {
|
|
@@ -1255,21 +1310,30 @@ var flags = {
|
|
|
1255
1310
|
_server?.destroy();
|
|
1256
1311
|
},
|
|
1257
1312
|
get(name, user, defaultValue = false) {
|
|
1258
|
-
return _server?.getFlag(name, user, defaultValue) ?? defaultValue;
|
|
1313
|
+
return safeRun("flags.get", defaultValue, () => _server?.getFlag(name, user, defaultValue) ?? defaultValue);
|
|
1259
1314
|
},
|
|
1260
1315
|
/** Evaluate a gate and report why (value + reason). See {@link FlagDetail}. */
|
|
1261
1316
|
getDetail(name, user) {
|
|
1262
|
-
return
|
|
1317
|
+
return safeRun(
|
|
1318
|
+
"flags.getDetail",
|
|
1319
|
+
{ value: false, reason: "CLIENT_NOT_READY" },
|
|
1320
|
+
() => _server?.getFlagDetail(name, user) ?? { value: false, reason: "CLIENT_NOT_READY" }
|
|
1321
|
+
);
|
|
1263
1322
|
},
|
|
1264
1323
|
getConfig(name, decodeOrOpts) {
|
|
1265
|
-
return
|
|
1324
|
+
return safeRun(
|
|
1325
|
+
"flags.getConfig",
|
|
1326
|
+
void 0,
|
|
1327
|
+
() => _server?.getConfig(name, decodeOrOpts)
|
|
1328
|
+
);
|
|
1266
1329
|
},
|
|
1267
1330
|
getExperiment(name, user, defaultParams, decode) {
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1331
|
+
const notIn = { inExperiment: false, group: "control", params: defaultParams };
|
|
1332
|
+
return safeRun(
|
|
1333
|
+
"flags.getExperiment",
|
|
1334
|
+
notIn,
|
|
1335
|
+
() => _server?.getExperiment(name, user, defaultParams, decode) ?? notIn
|
|
1336
|
+
);
|
|
1273
1337
|
},
|
|
1274
1338
|
/**
|
|
1275
1339
|
* Read a killswitch. Without `switchKey`, returns true when the whole
|
|
@@ -1277,15 +1341,15 @@ var flags = {
|
|
|
1277
1341
|
* switch is on. Unknown killswitches / switches return false.
|
|
1278
1342
|
*/
|
|
1279
1343
|
ks(name, switchKey) {
|
|
1280
|
-
return _server?.getKillswitch(name, switchKey) ?? false;
|
|
1344
|
+
return safeRun("flags.ks", false, () => _server?.getKillswitch(name, switchKey) ?? false);
|
|
1281
1345
|
},
|
|
1282
1346
|
track(userId, eventName, props) {
|
|
1283
|
-
_server?.track(userId, eventName, props);
|
|
1347
|
+
safeRun("flags.track", void 0, () => _server?.track(userId, eventName, props));
|
|
1284
1348
|
},
|
|
1285
1349
|
/** Emit an exposure for an enrolled experiment at the decision point. See
|
|
1286
1350
|
* {@link Engine.logExposure}. No-op before configure(). */
|
|
1287
1351
|
logExposure(user, name) {
|
|
1288
|
-
_server?.logExposure(user, name);
|
|
1352
|
+
safeRun("flags.logExposure", void 0, () => _server?.logExposure(user, name));
|
|
1289
1353
|
},
|
|
1290
1354
|
/**
|
|
1291
1355
|
* Evaluate all flags / configs / experiments for a user against the locally
|
|
@@ -1293,12 +1357,8 @@ var flags = {
|
|
|
1293
1357
|
* overrides. Returns an empty payload when the blob hasn't been fetched yet.
|
|
1294
1358
|
*/
|
|
1295
1359
|
evaluate(user, rawUrl) {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
configs: {},
|
|
1299
|
-
experiments: {},
|
|
1300
|
-
killswitches: {}
|
|
1301
|
-
};
|
|
1360
|
+
const empty = { flags: {}, configs: {}, experiments: {}, killswitches: {} };
|
|
1361
|
+
return safeRun("flags.evaluate", empty, () => _server?.evaluate(user, rawUrl) ?? empty);
|
|
1302
1362
|
}
|
|
1303
1363
|
};
|
|
1304
1364
|
var _identityAttributes = (user) => user && typeof user === "object" ? user : {};
|
|
@@ -1387,20 +1447,37 @@ var Client = class {
|
|
|
1387
1447
|
this.attributes = _attributes(user);
|
|
1388
1448
|
}
|
|
1389
1449
|
getFlag(name, defaultValue = false) {
|
|
1390
|
-
return
|
|
1450
|
+
return safeRun(
|
|
1451
|
+
"Client.getFlag",
|
|
1452
|
+
defaultValue,
|
|
1453
|
+
() => this.engine.getFlag(name, this.attributes, defaultValue)
|
|
1454
|
+
);
|
|
1391
1455
|
}
|
|
1392
1456
|
getFlagDetail(name) {
|
|
1393
|
-
return
|
|
1457
|
+
return safeRun(
|
|
1458
|
+
"Client.getFlagDetail",
|
|
1459
|
+
{ value: false, reason: "CLIENT_NOT_READY" },
|
|
1460
|
+
() => this.engine.getFlagDetail(name, this.attributes)
|
|
1461
|
+
);
|
|
1394
1462
|
}
|
|
1395
1463
|
getConfig(name, decodeOrOpts) {
|
|
1396
|
-
return
|
|
1464
|
+
return safeRun(
|
|
1465
|
+
"Client.getConfig",
|
|
1466
|
+
void 0,
|
|
1467
|
+
() => this.engine.getConfig(name, decodeOrOpts)
|
|
1468
|
+
);
|
|
1397
1469
|
}
|
|
1398
1470
|
getExperiment(name, defaultParams, decode) {
|
|
1399
|
-
|
|
1471
|
+
const notIn = { inExperiment: false, group: "control", params: defaultParams };
|
|
1472
|
+
return safeRun(
|
|
1473
|
+
"Client.getExperiment",
|
|
1474
|
+
notIn,
|
|
1475
|
+
() => this.engine.getExperiment(name, this.attributes, defaultParams, decode)
|
|
1476
|
+
);
|
|
1400
1477
|
}
|
|
1401
1478
|
/** Read a killswitch (not user-bound; mirrors {@link Engine.getKillswitch}). */
|
|
1402
1479
|
getKillswitch(name, switchKey) {
|
|
1403
|
-
return this.engine.getKillswitch(name, switchKey);
|
|
1480
|
+
return safeRun("Client.getKillswitch", false, () => this.engine.getKillswitch(name, switchKey));
|
|
1404
1481
|
}
|
|
1405
1482
|
/**
|
|
1406
1483
|
* Record a conversion/metric event for the bound user. Derives the unit from
|
|
@@ -1410,9 +1487,11 @@ var Client = class {
|
|
|
1410
1487
|
* mode.
|
|
1411
1488
|
*/
|
|
1412
1489
|
track(eventName, props) {
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1490
|
+
safeRun("Client.track", void 0, () => {
|
|
1491
|
+
const id = this.attributes.user_id ?? this.attributes.anonymous_id;
|
|
1492
|
+
if (id === void 0) return;
|
|
1493
|
+
this.engine.track(String(id), eventName, props);
|
|
1494
|
+
});
|
|
1416
1495
|
}
|
|
1417
1496
|
/**
|
|
1418
1497
|
* Emit an exposure event for `name` at this server-side decision point for the
|
|
@@ -1421,12 +1500,12 @@ var Client = class {
|
|
|
1421
1500
|
* in test mode).
|
|
1422
1501
|
*/
|
|
1423
1502
|
logExposure(name) {
|
|
1424
|
-
this.engine.logExposure(this.attributes, name);
|
|
1503
|
+
safeRun("Client.logExposure", void 0, () => this.engine.logExposure(this.attributes, name));
|
|
1425
1504
|
}
|
|
1426
1505
|
};
|
|
1427
1506
|
function dispatchSee(problem, consequence, extras, kind) {
|
|
1428
1507
|
if (!_server) {
|
|
1429
|
-
|
|
1508
|
+
logger.warn("[shipeasy] see() called before shipeasy({ serverKey }) \u2014 error dropped");
|
|
1430
1509
|
return;
|
|
1431
1510
|
}
|
|
1432
1511
|
_server.reportError(problem, consequence, extras, kind);
|
|
@@ -1444,6 +1523,7 @@ var see = Object.assign(
|
|
|
1444
1523
|
Client,
|
|
1445
1524
|
Engine,
|
|
1446
1525
|
FLAG_REASONS,
|
|
1526
|
+
LOG_LEVELS,
|
|
1447
1527
|
_murmur3ForTests,
|
|
1448
1528
|
_resetConfigureForTests,
|
|
1449
1529
|
_resetShipeasyServerForTests,
|
package/dist/server/index.mjs
CHANGED
|
@@ -67,6 +67,53 @@ function send(url) {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// src/logger.ts
|
|
71
|
+
var LOG_LEVELS = ["silent", "error", "warn", "info", "debug"];
|
|
72
|
+
var RANK = {
|
|
73
|
+
silent: 0,
|
|
74
|
+
error: 1,
|
|
75
|
+
warn: 2,
|
|
76
|
+
info: 3,
|
|
77
|
+
debug: 4
|
|
78
|
+
};
|
|
79
|
+
var currentLevel = "warn";
|
|
80
|
+
function setLogLevel(level) {
|
|
81
|
+
if (typeof level === "string" && Object.prototype.hasOwnProperty.call(RANK, level)) {
|
|
82
|
+
currentLevel = level;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function write(method, args) {
|
|
86
|
+
try {
|
|
87
|
+
const c = globalThis.console;
|
|
88
|
+
if (!c) return;
|
|
89
|
+
const fn = c[method] ?? c.log;
|
|
90
|
+
if (typeof fn === "function") fn.call(c, ...args);
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
var logger = {
|
|
95
|
+
error(...args) {
|
|
96
|
+
if (RANK[currentLevel] >= RANK.error) write("error", args);
|
|
97
|
+
},
|
|
98
|
+
warn(...args) {
|
|
99
|
+
if (RANK[currentLevel] >= RANK.warn) write("warn", args);
|
|
100
|
+
},
|
|
101
|
+
info(...args) {
|
|
102
|
+
if (RANK[currentLevel] >= RANK.info) write("info", args);
|
|
103
|
+
},
|
|
104
|
+
debug(...args) {
|
|
105
|
+
if (RANK[currentLevel] >= RANK.debug) write("debug", args);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
function safeRun(label, fallback, fn) {
|
|
109
|
+
try {
|
|
110
|
+
return fn();
|
|
111
|
+
} catch (err) {
|
|
112
|
+
logger.error(`[shipeasy] ${label} failed \u2014 returning safe default:`, String(err));
|
|
113
|
+
return fallback;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
70
117
|
// src/see/core.ts
|
|
71
118
|
var SEE_MAX_MESSAGE = 500;
|
|
72
119
|
var SEE_MAX_STACK = 8e3;
|
|
@@ -528,6 +575,7 @@ var Engine = class _Engine {
|
|
|
528
575
|
// 304). Never fired in testMode/offline (no polling happens there).
|
|
529
576
|
changeListeners = /* @__PURE__ */ new Set();
|
|
530
577
|
constructor(opts) {
|
|
578
|
+
setLogLevel(opts.logLevel);
|
|
531
579
|
this.apiKey = opts.apiKey;
|
|
532
580
|
this.baseUrl = (opts.baseUrl ?? "https://cdn.shipeasy.ai").replace(/\/$/, "");
|
|
533
581
|
this.env = opts.env ?? "prod";
|
|
@@ -651,14 +699,14 @@ var Engine = class _Engine {
|
|
|
651
699
|
try {
|
|
652
700
|
l();
|
|
653
701
|
} catch (err) {
|
|
654
|
-
|
|
702
|
+
logger.warn("[shipeasy] onChange listener threw:", String(err));
|
|
655
703
|
}
|
|
656
704
|
}
|
|
657
705
|
}
|
|
658
706
|
startPoll() {
|
|
659
707
|
this.timer = setInterval(() => {
|
|
660
708
|
this.fetchAll(true).catch(
|
|
661
|
-
(err) =>
|
|
709
|
+
(err) => logger.warn("[shipeasy] background poll failed:", String(err))
|
|
662
710
|
);
|
|
663
711
|
}, this.pollInterval * 1e3);
|
|
664
712
|
}
|
|
@@ -737,7 +785,12 @@ var Engine = class _Engine {
|
|
|
737
785
|
return "defaultValue" in opts ? opts.defaultValue : void 0;
|
|
738
786
|
}
|
|
739
787
|
if (!opts.decode) return raw;
|
|
740
|
-
|
|
788
|
+
try {
|
|
789
|
+
return opts.decode(raw);
|
|
790
|
+
} catch (err) {
|
|
791
|
+
logger.warn(`[shipeasy] getConfig('${name}') decode failed:`, String(err));
|
|
792
|
+
return "defaultValue" in opts ? opts.defaultValue : void 0;
|
|
793
|
+
}
|
|
741
794
|
}
|
|
742
795
|
getExperiment(name, user, defaultParams, decode) {
|
|
743
796
|
this.telemetry.emit("experiment", name);
|
|
@@ -752,7 +805,7 @@ var Engine = class _Engine {
|
|
|
752
805
|
try {
|
|
753
806
|
return { inExperiment: true, group: ov.group, params: decode(ov.params) };
|
|
754
807
|
} catch (err) {
|
|
755
|
-
|
|
808
|
+
logger.warn(`[shipeasy] getExperiment('${name}') override decode failed:`, String(err));
|
|
756
809
|
return notIn;
|
|
757
810
|
}
|
|
758
811
|
}
|
|
@@ -777,7 +830,7 @@ var Engine = class _Engine {
|
|
|
777
830
|
try {
|
|
778
831
|
return { inExperiment: true, group: g.name, params: decode(g.params) };
|
|
779
832
|
} catch (err) {
|
|
780
|
-
|
|
833
|
+
logger.warn(`[shipeasy] getExperiment('${name}') decode failed:`, String(err));
|
|
781
834
|
return notIn;
|
|
782
835
|
}
|
|
783
836
|
};
|
|
@@ -829,7 +882,7 @@ var Engine = class _Engine {
|
|
|
829
882
|
method: "POST",
|
|
830
883
|
headers: { "X-SDK-Key": this.apiKey, "Content-Type": "text/plain" },
|
|
831
884
|
body
|
|
832
|
-
}).catch((err) =>
|
|
885
|
+
}).catch((err) => logger.warn("[shipeasy] track failed:", String(err)));
|
|
833
886
|
}
|
|
834
887
|
/**
|
|
835
888
|
* Emit an exposure event for an experiment at the server-side decision point
|
|
@@ -860,7 +913,7 @@ var Engine = class _Engine {
|
|
|
860
913
|
method: "POST",
|
|
861
914
|
headers: { "X-SDK-Key": this.apiKey, "Content-Type": "text/plain" },
|
|
862
915
|
body
|
|
863
|
-
}).catch((err) =>
|
|
916
|
+
}).catch((err) => logger.warn("[shipeasy] logExposure failed:", String(err)));
|
|
864
917
|
}
|
|
865
918
|
/**
|
|
866
919
|
* Report a structured error into the errors primitive. Fire-and-forget —
|
|
@@ -880,7 +933,7 @@ var Engine = class _Engine {
|
|
|
880
933
|
method: "POST",
|
|
881
934
|
headers: { "X-SDK-Key": this.apiKey, "Content-Type": "text/plain" },
|
|
882
935
|
body: JSON.stringify({ events: [ev] })
|
|
883
|
-
}).catch((err) =>
|
|
936
|
+
}).catch((err) => logger.warn("[shipeasy] see() send failed:", String(err)));
|
|
884
937
|
} catch {
|
|
885
938
|
}
|
|
886
939
|
}
|
|
@@ -1067,7 +1120,7 @@ function _resetShipeasyServerForTests() {
|
|
|
1067
1120
|
async function shipeasy(opts) {
|
|
1068
1121
|
const serverKey = opts.serverKey ?? "";
|
|
1069
1122
|
if (!serverKey) {
|
|
1070
|
-
|
|
1123
|
+
logger.error(
|
|
1071
1124
|
"[shipeasy] No server key \u2014 flags, experiments and SSR i18n skipped. Pass `serverKey` to shipeasy() from @shipeasy/sdk/server with your server key (SHIPEASY_SERVER_KEY). Set it as a Worker secret with `wrangler secret put SHIPEASY_SERVER_KEY` (or add it to .env for local dev). Do not pass a client key here \u2014 the server entrypoint only accepts the server key."
|
|
1072
1125
|
);
|
|
1073
1126
|
}
|
|
@@ -1075,7 +1128,8 @@ async function shipeasy(opts) {
|
|
|
1075
1128
|
flags.configure({
|
|
1076
1129
|
apiKey: serverKey,
|
|
1077
1130
|
disableTelemetry: opts.disableTelemetry,
|
|
1078
|
-
privateAttributes: opts.privateAttributes
|
|
1131
|
+
privateAttributes: opts.privateAttributes,
|
|
1132
|
+
logLevel: opts.logLevel
|
|
1079
1133
|
});
|
|
1080
1134
|
let resolvedUrlOverrides = opts.urlOverrides;
|
|
1081
1135
|
if (!resolvedUrlOverrides) {
|
|
@@ -1201,21 +1255,30 @@ var flags = {
|
|
|
1201
1255
|
_server?.destroy();
|
|
1202
1256
|
},
|
|
1203
1257
|
get(name, user, defaultValue = false) {
|
|
1204
|
-
return _server?.getFlag(name, user, defaultValue) ?? defaultValue;
|
|
1258
|
+
return safeRun("flags.get", defaultValue, () => _server?.getFlag(name, user, defaultValue) ?? defaultValue);
|
|
1205
1259
|
},
|
|
1206
1260
|
/** Evaluate a gate and report why (value + reason). See {@link FlagDetail}. */
|
|
1207
1261
|
getDetail(name, user) {
|
|
1208
|
-
return
|
|
1262
|
+
return safeRun(
|
|
1263
|
+
"flags.getDetail",
|
|
1264
|
+
{ value: false, reason: "CLIENT_NOT_READY" },
|
|
1265
|
+
() => _server?.getFlagDetail(name, user) ?? { value: false, reason: "CLIENT_NOT_READY" }
|
|
1266
|
+
);
|
|
1209
1267
|
},
|
|
1210
1268
|
getConfig(name, decodeOrOpts) {
|
|
1211
|
-
return
|
|
1269
|
+
return safeRun(
|
|
1270
|
+
"flags.getConfig",
|
|
1271
|
+
void 0,
|
|
1272
|
+
() => _server?.getConfig(name, decodeOrOpts)
|
|
1273
|
+
);
|
|
1212
1274
|
},
|
|
1213
1275
|
getExperiment(name, user, defaultParams, decode) {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1276
|
+
const notIn = { inExperiment: false, group: "control", params: defaultParams };
|
|
1277
|
+
return safeRun(
|
|
1278
|
+
"flags.getExperiment",
|
|
1279
|
+
notIn,
|
|
1280
|
+
() => _server?.getExperiment(name, user, defaultParams, decode) ?? notIn
|
|
1281
|
+
);
|
|
1219
1282
|
},
|
|
1220
1283
|
/**
|
|
1221
1284
|
* Read a killswitch. Without `switchKey`, returns true when the whole
|
|
@@ -1223,15 +1286,15 @@ var flags = {
|
|
|
1223
1286
|
* switch is on. Unknown killswitches / switches return false.
|
|
1224
1287
|
*/
|
|
1225
1288
|
ks(name, switchKey) {
|
|
1226
|
-
return _server?.getKillswitch(name, switchKey) ?? false;
|
|
1289
|
+
return safeRun("flags.ks", false, () => _server?.getKillswitch(name, switchKey) ?? false);
|
|
1227
1290
|
},
|
|
1228
1291
|
track(userId, eventName, props) {
|
|
1229
|
-
_server?.track(userId, eventName, props);
|
|
1292
|
+
safeRun("flags.track", void 0, () => _server?.track(userId, eventName, props));
|
|
1230
1293
|
},
|
|
1231
1294
|
/** Emit an exposure for an enrolled experiment at the decision point. See
|
|
1232
1295
|
* {@link Engine.logExposure}. No-op before configure(). */
|
|
1233
1296
|
logExposure(user, name) {
|
|
1234
|
-
_server?.logExposure(user, name);
|
|
1297
|
+
safeRun("flags.logExposure", void 0, () => _server?.logExposure(user, name));
|
|
1235
1298
|
},
|
|
1236
1299
|
/**
|
|
1237
1300
|
* Evaluate all flags / configs / experiments for a user against the locally
|
|
@@ -1239,12 +1302,8 @@ var flags = {
|
|
|
1239
1302
|
* overrides. Returns an empty payload when the blob hasn't been fetched yet.
|
|
1240
1303
|
*/
|
|
1241
1304
|
evaluate(user, rawUrl) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
configs: {},
|
|
1245
|
-
experiments: {},
|
|
1246
|
-
killswitches: {}
|
|
1247
|
-
};
|
|
1305
|
+
const empty = { flags: {}, configs: {}, experiments: {}, killswitches: {} };
|
|
1306
|
+
return safeRun("flags.evaluate", empty, () => _server?.evaluate(user, rawUrl) ?? empty);
|
|
1248
1307
|
}
|
|
1249
1308
|
};
|
|
1250
1309
|
var _identityAttributes = (user) => user && typeof user === "object" ? user : {};
|
|
@@ -1333,20 +1392,37 @@ var Client = class {
|
|
|
1333
1392
|
this.attributes = _attributes(user);
|
|
1334
1393
|
}
|
|
1335
1394
|
getFlag(name, defaultValue = false) {
|
|
1336
|
-
return
|
|
1395
|
+
return safeRun(
|
|
1396
|
+
"Client.getFlag",
|
|
1397
|
+
defaultValue,
|
|
1398
|
+
() => this.engine.getFlag(name, this.attributes, defaultValue)
|
|
1399
|
+
);
|
|
1337
1400
|
}
|
|
1338
1401
|
getFlagDetail(name) {
|
|
1339
|
-
return
|
|
1402
|
+
return safeRun(
|
|
1403
|
+
"Client.getFlagDetail",
|
|
1404
|
+
{ value: false, reason: "CLIENT_NOT_READY" },
|
|
1405
|
+
() => this.engine.getFlagDetail(name, this.attributes)
|
|
1406
|
+
);
|
|
1340
1407
|
}
|
|
1341
1408
|
getConfig(name, decodeOrOpts) {
|
|
1342
|
-
return
|
|
1409
|
+
return safeRun(
|
|
1410
|
+
"Client.getConfig",
|
|
1411
|
+
void 0,
|
|
1412
|
+
() => this.engine.getConfig(name, decodeOrOpts)
|
|
1413
|
+
);
|
|
1343
1414
|
}
|
|
1344
1415
|
getExperiment(name, defaultParams, decode) {
|
|
1345
|
-
|
|
1416
|
+
const notIn = { inExperiment: false, group: "control", params: defaultParams };
|
|
1417
|
+
return safeRun(
|
|
1418
|
+
"Client.getExperiment",
|
|
1419
|
+
notIn,
|
|
1420
|
+
() => this.engine.getExperiment(name, this.attributes, defaultParams, decode)
|
|
1421
|
+
);
|
|
1346
1422
|
}
|
|
1347
1423
|
/** Read a killswitch (not user-bound; mirrors {@link Engine.getKillswitch}). */
|
|
1348
1424
|
getKillswitch(name, switchKey) {
|
|
1349
|
-
return this.engine.getKillswitch(name, switchKey);
|
|
1425
|
+
return safeRun("Client.getKillswitch", false, () => this.engine.getKillswitch(name, switchKey));
|
|
1350
1426
|
}
|
|
1351
1427
|
/**
|
|
1352
1428
|
* Record a conversion/metric event for the bound user. Derives the unit from
|
|
@@ -1356,9 +1432,11 @@ var Client = class {
|
|
|
1356
1432
|
* mode.
|
|
1357
1433
|
*/
|
|
1358
1434
|
track(eventName, props) {
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1435
|
+
safeRun("Client.track", void 0, () => {
|
|
1436
|
+
const id = this.attributes.user_id ?? this.attributes.anonymous_id;
|
|
1437
|
+
if (id === void 0) return;
|
|
1438
|
+
this.engine.track(String(id), eventName, props);
|
|
1439
|
+
});
|
|
1362
1440
|
}
|
|
1363
1441
|
/**
|
|
1364
1442
|
* Emit an exposure event for `name` at this server-side decision point for the
|
|
@@ -1367,12 +1445,12 @@ var Client = class {
|
|
|
1367
1445
|
* in test mode).
|
|
1368
1446
|
*/
|
|
1369
1447
|
logExposure(name) {
|
|
1370
|
-
this.engine.logExposure(this.attributes, name);
|
|
1448
|
+
safeRun("Client.logExposure", void 0, () => this.engine.logExposure(this.attributes, name));
|
|
1371
1449
|
}
|
|
1372
1450
|
};
|
|
1373
1451
|
function dispatchSee(problem, consequence, extras, kind) {
|
|
1374
1452
|
if (!_server) {
|
|
1375
|
-
|
|
1453
|
+
logger.warn("[shipeasy] see() called before shipeasy({ serverKey }) \u2014 error dropped");
|
|
1376
1454
|
return;
|
|
1377
1455
|
}
|
|
1378
1456
|
_server.reportError(problem, consequence, extras, kind);
|
|
@@ -1389,6 +1467,7 @@ export {
|
|
|
1389
1467
|
Client,
|
|
1390
1468
|
Engine,
|
|
1391
1469
|
FLAG_REASONS,
|
|
1470
|
+
LOG_LEVELS,
|
|
1392
1471
|
_murmur3ForTests,
|
|
1393
1472
|
_resetConfigureForTests,
|
|
1394
1473
|
_resetShipeasyServerForTests,
|