@shipeasy/sdk 3.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/dist/client/index.d.mts +171 -7
- package/dist/client/index.d.ts +171 -7
- package/dist/client/index.js +454 -50
- package/dist/client/index.mjs +453 -50
- package/dist/server/index.d.mts +134 -2
- package/dist/server/index.d.ts +134 -2
- package/dist/server/index.js +278 -1
- package/dist/server/index.mjs +277 -1
- package/package.json +1 -1
package/dist/server/index.mjs
CHANGED
|
@@ -60,8 +60,247 @@ function send(url) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// src/see/core.ts
|
|
64
|
+
var SEE_MAX_MESSAGE = 500;
|
|
65
|
+
var SEE_MAX_STACK = 8e3;
|
|
66
|
+
var SEE_MAX_SUBJECT = 200;
|
|
67
|
+
var SEE_MAX_EXTRA_VALUE = 200;
|
|
68
|
+
var SEE_MAX_EXTRA_KEYS = 20;
|
|
69
|
+
var SEE_DEDUP_WINDOW_MS = 3e4;
|
|
70
|
+
var SEE_MAX_PER_SESSION = 25;
|
|
71
|
+
function causesThe(subject) {
|
|
72
|
+
return {
|
|
73
|
+
to(outcome) {
|
|
74
|
+
return {
|
|
75
|
+
__seConsequence: true,
|
|
76
|
+
subject: truncate(String(subject), SEE_MAX_SUBJECT),
|
|
77
|
+
outcome: truncate(String(outcome), SEE_MAX_SUBJECT)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function violation(name) {
|
|
83
|
+
const make = (msg) => ({
|
|
84
|
+
__seViolation: true,
|
|
85
|
+
violationName: String(name),
|
|
86
|
+
...msg !== void 0 ? { violationMessage: msg } : {},
|
|
87
|
+
message(m) {
|
|
88
|
+
return make(String(m));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return make();
|
|
92
|
+
}
|
|
93
|
+
function isViolation(p) {
|
|
94
|
+
return typeof p === "object" && p !== null && p.__seViolation === true;
|
|
95
|
+
}
|
|
96
|
+
var EXPECTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-expected");
|
|
97
|
+
function markExpected(err, because) {
|
|
98
|
+
if (typeof err !== "object" || err === null) return;
|
|
99
|
+
try {
|
|
100
|
+
Object.defineProperty(err, EXPECTED_SYM, {
|
|
101
|
+
value: String(because),
|
|
102
|
+
enumerable: false,
|
|
103
|
+
configurable: true
|
|
104
|
+
});
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
109
|
+
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
110
|
+
function readReportStamp(err) {
|
|
111
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
112
|
+
const v = err[REPORTED_SYM];
|
|
113
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
114
|
+
}
|
|
115
|
+
function findCausedBy(problem) {
|
|
116
|
+
let cur = problem;
|
|
117
|
+
const seen = /* @__PURE__ */ new Set();
|
|
118
|
+
for (let depth = 0; depth < SEE_MAX_CAUSE_DEPTH; depth++) {
|
|
119
|
+
if (typeof cur !== "object" || cur === null || seen.has(cur)) break;
|
|
120
|
+
seen.add(cur);
|
|
121
|
+
const stamp = readReportStamp(cur);
|
|
122
|
+
if (stamp) return stamp;
|
|
123
|
+
cur = cur.cause;
|
|
124
|
+
}
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
function markReported(problem, ev) {
|
|
128
|
+
if (!(problem instanceof Error)) return;
|
|
129
|
+
const stamp = {
|
|
130
|
+
error_type: ev.error_type,
|
|
131
|
+
message: ev.message,
|
|
132
|
+
subject: ev.subject,
|
|
133
|
+
outcome: ev.outcome
|
|
134
|
+
};
|
|
135
|
+
if (ev.stack !== void 0) stamp.stack = ev.stack;
|
|
136
|
+
try {
|
|
137
|
+
Object.defineProperty(problem, REPORTED_SYM, {
|
|
138
|
+
value: Object.freeze(stamp),
|
|
139
|
+
enumerable: false,
|
|
140
|
+
configurable: true,
|
|
141
|
+
writable: true
|
|
142
|
+
});
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function truncate(s, max) {
|
|
147
|
+
return s.length > max ? s.slice(0, max) : s;
|
|
148
|
+
}
|
|
149
|
+
function sanitizeExtras(extras) {
|
|
150
|
+
if (!extras || typeof extras !== "object") return void 0;
|
|
151
|
+
const out = {};
|
|
152
|
+
let n = 0;
|
|
153
|
+
for (const [k, v] of Object.entries(extras)) {
|
|
154
|
+
if (v === null || v === void 0) continue;
|
|
155
|
+
if (n >= SEE_MAX_EXTRA_KEYS) break;
|
|
156
|
+
if (typeof v === "string") out[k] = truncate(v, SEE_MAX_EXTRA_VALUE);
|
|
157
|
+
else if (typeof v === "number" && Number.isFinite(v)) out[k] = v;
|
|
158
|
+
else if (typeof v === "boolean") out[k] = v;
|
|
159
|
+
else continue;
|
|
160
|
+
n += 1;
|
|
161
|
+
}
|
|
162
|
+
return n > 0 ? out : void 0;
|
|
163
|
+
}
|
|
164
|
+
function captureCallsiteStack() {
|
|
165
|
+
const raw = new Error().stack;
|
|
166
|
+
if (!raw) return void 0;
|
|
167
|
+
const lines = raw.split("\n");
|
|
168
|
+
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
169
|
+
return kept.length ? kept.join("\n") : void 0;
|
|
170
|
+
}
|
|
171
|
+
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
172
|
+
let errorType;
|
|
173
|
+
let message;
|
|
174
|
+
let stack;
|
|
175
|
+
let kind;
|
|
176
|
+
if (isViolation(problem)) {
|
|
177
|
+
errorType = problem.violationName;
|
|
178
|
+
message = problem.violationMessage ?? problem.violationName;
|
|
179
|
+
stack = captureCallsiteStack();
|
|
180
|
+
kind = kindOverride ?? "violation";
|
|
181
|
+
} else if (problem instanceof Error) {
|
|
182
|
+
errorType = problem.name || "Error";
|
|
183
|
+
message = problem.message || String(problem);
|
|
184
|
+
stack = problem.stack ?? void 0;
|
|
185
|
+
kind = kindOverride ?? "caught";
|
|
186
|
+
} else {
|
|
187
|
+
errorType = "Error";
|
|
188
|
+
message = typeof problem === "string" ? problem : safeString(problem);
|
|
189
|
+
stack = captureCallsiteStack();
|
|
190
|
+
kind = kindOverride ?? "caught";
|
|
191
|
+
}
|
|
192
|
+
const ev = {
|
|
193
|
+
type: "error",
|
|
194
|
+
kind,
|
|
195
|
+
error_type: truncate(errorType, SEE_MAX_SUBJECT),
|
|
196
|
+
message: truncate(message, SEE_MAX_MESSAGE),
|
|
197
|
+
subject: consequence.subject,
|
|
198
|
+
outcome: consequence.outcome,
|
|
199
|
+
side: ctx.side,
|
|
200
|
+
sdk_version: ctx.sdkVersion,
|
|
201
|
+
ts: Date.now()
|
|
202
|
+
};
|
|
203
|
+
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
204
|
+
const causedBy = findCausedBy(problem);
|
|
205
|
+
if (causedBy) ev.caused_by = causedBy;
|
|
206
|
+
const cleanExtras = sanitizeExtras(extras);
|
|
207
|
+
if (cleanExtras) ev.extras = cleanExtras;
|
|
208
|
+
if (ctx.url) ev.url = truncate(ctx.url, SEE_MAX_SUBJECT);
|
|
209
|
+
if (ctx.userId) ev.user_id = ctx.userId;
|
|
210
|
+
if (ctx.anonId) ev.anonymous_id = ctx.anonId;
|
|
211
|
+
if (ctx.env) ev.env = ctx.env;
|
|
212
|
+
markReported(problem, ev);
|
|
213
|
+
return ev;
|
|
214
|
+
}
|
|
215
|
+
function safeString(v) {
|
|
216
|
+
try {
|
|
217
|
+
return typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
218
|
+
} catch {
|
|
219
|
+
return String(v);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
var scheduleMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : (cb) => {
|
|
223
|
+
void Promise.resolve().then(cb);
|
|
224
|
+
};
|
|
225
|
+
function startSeeChain(getProblem, dispatch) {
|
|
226
|
+
let subject;
|
|
227
|
+
let outcome;
|
|
228
|
+
let collected;
|
|
229
|
+
let flushed = false;
|
|
230
|
+
scheduleMicrotask(() => {
|
|
231
|
+
if (flushed) return;
|
|
232
|
+
flushed = true;
|
|
233
|
+
dispatch(
|
|
234
|
+
getProblem(),
|
|
235
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
236
|
+
// leading article would double up ("causes the the app").
|
|
237
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
238
|
+
collected
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
const tail = {
|
|
242
|
+
extras(x) {
|
|
243
|
+
if (x && typeof x === "object") collected = { ...collected, ...x };
|
|
244
|
+
return tail;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
const step = {
|
|
248
|
+
to(o) {
|
|
249
|
+
outcome = String(o);
|
|
250
|
+
return tail;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const start = (s) => {
|
|
254
|
+
subject = String(s);
|
|
255
|
+
return step;
|
|
256
|
+
};
|
|
257
|
+
return { causes_the: start, causesThe: start };
|
|
258
|
+
}
|
|
259
|
+
function startSeeViolationChain(name, dispatch) {
|
|
260
|
+
let msg;
|
|
261
|
+
const base = startSeeChain(
|
|
262
|
+
() => msg !== void 0 ? violation(name).message(msg) : violation(name),
|
|
263
|
+
dispatch
|
|
264
|
+
);
|
|
265
|
+
const chain = {
|
|
266
|
+
...base,
|
|
267
|
+
message(m) {
|
|
268
|
+
msg = String(m);
|
|
269
|
+
return chain;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
return chain;
|
|
273
|
+
}
|
|
274
|
+
function topStackLine(stack) {
|
|
275
|
+
if (!stack) return "";
|
|
276
|
+
for (const line of stack.split("\n")) {
|
|
277
|
+
if (/^\s*at |@|:\d+:\d+/.test(line)) return line.trim().slice(0, 200);
|
|
278
|
+
}
|
|
279
|
+
return "";
|
|
280
|
+
}
|
|
281
|
+
var SeeLimiter = class {
|
|
282
|
+
constructor(maxPerSession = SEE_MAX_PER_SESSION, dedupWindowMs = SEE_DEDUP_WINDOW_MS) {
|
|
283
|
+
this.maxPerSession = maxPerSession;
|
|
284
|
+
this.dedupWindowMs = dedupWindowMs;
|
|
285
|
+
}
|
|
286
|
+
maxPerSession;
|
|
287
|
+
dedupWindowMs;
|
|
288
|
+
lastSent = /* @__PURE__ */ new Map();
|
|
289
|
+
sent = 0;
|
|
290
|
+
shouldSend(ev) {
|
|
291
|
+
if (this.sent >= this.maxPerSession) return false;
|
|
292
|
+
const key = `${ev.kind}|${ev.error_type}|${ev.message.slice(0, 200)}|${topStackLine(ev.stack)}`;
|
|
293
|
+
const now = Date.now();
|
|
294
|
+
const prev = this.lastSent.get(key);
|
|
295
|
+
if (prev !== void 0 && now - prev < this.dedupWindowMs) return false;
|
|
296
|
+
this.lastSent.set(key, now);
|
|
297
|
+
this.sent += 1;
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
63
302
|
// src/server/index.ts
|
|
64
|
-
var version = "
|
|
303
|
+
var version = "4.0.0";
|
|
65
304
|
var C1 = 3432918353;
|
|
66
305
|
var C2 = 461845907;
|
|
67
306
|
function murmur3(key) {
|
|
@@ -211,6 +450,7 @@ var FlagsClient = class {
|
|
|
211
450
|
baseUrl;
|
|
212
451
|
env;
|
|
213
452
|
telemetry;
|
|
453
|
+
seeLimiter = new SeeLimiter();
|
|
214
454
|
flagsBlob = null;
|
|
215
455
|
expsBlob = null;
|
|
216
456
|
flagsEtag = null;
|
|
@@ -363,6 +603,27 @@ var FlagsClient = class {
|
|
|
363
603
|
body
|
|
364
604
|
}).catch((err) => console.warn("[shipeasy] track failed:", String(err)));
|
|
365
605
|
}
|
|
606
|
+
/**
|
|
607
|
+
* Report a structured error into the errors primitive. Fire-and-forget —
|
|
608
|
+
* never blocks or throws into the request path. Spam-guarded by a 30s
|
|
609
|
+
* dedup window + per-process cap.
|
|
610
|
+
*/
|
|
611
|
+
reportError(problem, consequence, extras, kind) {
|
|
612
|
+
try {
|
|
613
|
+
const ev = buildSeeEvent(problem, consequence, extras, {
|
|
614
|
+
side: "server",
|
|
615
|
+
sdkVersion: version,
|
|
616
|
+
env: this.env
|
|
617
|
+
}, kind);
|
|
618
|
+
if (!this.seeLimiter.shouldSend(ev)) return;
|
|
619
|
+
globalThis.fetch(`${this.baseUrl}/collect`, {
|
|
620
|
+
method: "POST",
|
|
621
|
+
headers: { "X-SDK-Key": this.apiKey, "Content-Type": "text/plain" },
|
|
622
|
+
body: JSON.stringify({ events: [ev] })
|
|
623
|
+
}).catch((err) => console.warn("[shipeasy] see() send failed:", String(err)));
|
|
624
|
+
} catch {
|
|
625
|
+
}
|
|
626
|
+
}
|
|
366
627
|
/**
|
|
367
628
|
* Evaluate all flags, configs, and experiments for a user against the locally
|
|
368
629
|
* cached blob (no network call). Applies ?se_ks_* / ?se_cf_* / ?se_exp_*
|
|
@@ -671,6 +932,20 @@ var flags = {
|
|
|
671
932
|
};
|
|
672
933
|
}
|
|
673
934
|
};
|
|
935
|
+
function dispatchSee(problem, consequence, extras, kind) {
|
|
936
|
+
if (!_server) {
|
|
937
|
+
console.warn("[shipeasy] see() called before shipeasy({ serverKey }) \u2014 error dropped");
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
_server.reportError(problem, consequence, extras, kind);
|
|
941
|
+
}
|
|
942
|
+
var see = Object.assign(
|
|
943
|
+
(problem) => startSeeChain(() => problem, dispatchSee),
|
|
944
|
+
{
|
|
945
|
+
Violation: (name) => startSeeViolationChain(name, dispatchSee),
|
|
946
|
+
ControlFlowException: markExpected
|
|
947
|
+
}
|
|
948
|
+
);
|
|
674
949
|
export {
|
|
675
950
|
FlagsClient,
|
|
676
951
|
_resetShipeasyServerForTests,
|
|
@@ -680,6 +955,7 @@ export {
|
|
|
680
955
|
getBootstrapHtml,
|
|
681
956
|
getShipeasyServerClient,
|
|
682
957
|
i18n,
|
|
958
|
+
see,
|
|
683
959
|
shipeasy,
|
|
684
960
|
version
|
|
685
961
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipeasy/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Shipeasy SDK — feature gates, runtime configs, experiments, and metrics for the Shipeasy hosted service.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"homepage": "https://shipeasy.ai",
|