@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.d.mts
CHANGED
|
@@ -1,4 +1,85 @@
|
|
|
1
|
-
|
|
1
|
+
type SeeExtras = Record<string, string | number | boolean | null | undefined>;
|
|
2
|
+
type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
|
|
3
|
+
/** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
|
|
4
|
+
interface Consequence {
|
|
5
|
+
readonly __seConsequence: true;
|
|
6
|
+
readonly subject: string;
|
|
7
|
+
readonly outcome: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Non-exception problem, built by `violation(name)`. A plain branded object
|
|
11
|
+
* (not an Error subclass) so `.message()` can be a builder method without
|
|
12
|
+
* colliding with `Error.prototype.message`.
|
|
13
|
+
*/
|
|
14
|
+
interface Violation {
|
|
15
|
+
readonly __seViolation: true;
|
|
16
|
+
readonly violationName: string;
|
|
17
|
+
readonly violationMessage?: string;
|
|
18
|
+
/** Attach free-form detail. Variable data goes HERE (or in extras), never in the name. */
|
|
19
|
+
message(msg: string): Violation;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Identity of a problem that see() already reported, carried on the wire as
|
|
23
|
+
* the `caused_by` of a later occurrence. Holds exactly the fields the worker's
|
|
24
|
+
* fingerprint function consumes (raw `message`/`stack` — the server normalizes
|
|
25
|
+
* them) so the backend can recompute the prior issue's fingerprint and link
|
|
26
|
+
* the two issues. See `findCausedBy` for how the link is discovered.
|
|
27
|
+
*/
|
|
28
|
+
interface SeeCausedBy {
|
|
29
|
+
error_type: string;
|
|
30
|
+
message: string;
|
|
31
|
+
stack?: string;
|
|
32
|
+
subject: string;
|
|
33
|
+
outcome: string;
|
|
34
|
+
}
|
|
35
|
+
/** Wire shape — the `type:"error"` RawEvent variant accepted by POST /collect. */
|
|
36
|
+
interface SeeErrorEvent {
|
|
37
|
+
type: "error";
|
|
38
|
+
kind: SeeKind;
|
|
39
|
+
/** Error class/name (e.g. "TypeError") or the violation name. */
|
|
40
|
+
error_type: string;
|
|
41
|
+
message: string;
|
|
42
|
+
stack?: string;
|
|
43
|
+
/** Consequence: "<error_type> causes the <subject> to <outcome>". */
|
|
44
|
+
subject: string;
|
|
45
|
+
outcome: string;
|
|
46
|
+
extras?: Record<string, string | number | boolean>;
|
|
47
|
+
url?: string;
|
|
48
|
+
user_id?: string;
|
|
49
|
+
anonymous_id?: string;
|
|
50
|
+
side: "client" | "server";
|
|
51
|
+
env?: string;
|
|
52
|
+
sdk_version: string;
|
|
53
|
+
ts: number;
|
|
54
|
+
/**
|
|
55
|
+
* The earlier reported problem this occurrence descends from — present when
|
|
56
|
+
* the same error was caught + reported at an inner boundary and then
|
|
57
|
+
* re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
|
|
58
|
+
* Lets the backend stitch the two issues into a cause chain instead of
|
|
59
|
+
* double-counting them as unrelated.
|
|
60
|
+
*/
|
|
61
|
+
caused_by?: SeeCausedBy;
|
|
62
|
+
}
|
|
63
|
+
interface SeeExtrasTail {
|
|
64
|
+
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
|
65
|
+
extras(extras: SeeExtras): SeeExtrasTail;
|
|
66
|
+
}
|
|
67
|
+
interface SeeOutcomeStep {
|
|
68
|
+
/** The user-visible impact: `.causes_the("checkout").to("use cached prices")`. */
|
|
69
|
+
to(outcome: string): SeeExtrasTail;
|
|
70
|
+
}
|
|
71
|
+
interface SeeChain {
|
|
72
|
+
/** Start the consequence sentence — the product surface affected. */
|
|
73
|
+
causes_the(subject: string): SeeOutcomeStep;
|
|
74
|
+
/** camelCase alias of {@link SeeChain.causes_the}. */
|
|
75
|
+
causesThe(subject: string): SeeOutcomeStep;
|
|
76
|
+
}
|
|
77
|
+
interface SeeViolationChain extends SeeChain {
|
|
78
|
+
/** Free-form detail. Variable data goes here (or extras), never in the name. */
|
|
79
|
+
message(msg: string): SeeViolationChain;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
declare const version = "4.0.0";
|
|
2
83
|
interface User {
|
|
3
84
|
user_id?: string;
|
|
4
85
|
anonymous_id?: string;
|
|
@@ -68,6 +149,7 @@ declare class FlagsClient {
|
|
|
68
149
|
private readonly baseUrl;
|
|
69
150
|
private readonly env;
|
|
70
151
|
private readonly telemetry;
|
|
152
|
+
private readonly seeLimiter;
|
|
71
153
|
private flagsBlob;
|
|
72
154
|
private expsBlob;
|
|
73
155
|
private flagsEtag;
|
|
@@ -87,6 +169,12 @@ declare class FlagsClient {
|
|
|
87
169
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
88
170
|
getExperiment<P extends Record<string, unknown>>(name: string, user: User, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
|
|
89
171
|
track(userId: string, eventName: string, props?: Record<string, unknown>): void;
|
|
172
|
+
/**
|
|
173
|
+
* Report a structured error into the errors primitive. Fire-and-forget —
|
|
174
|
+
* never blocks or throws into the request path. Spam-guarded by a 30s
|
|
175
|
+
* dedup window + per-process cap.
|
|
176
|
+
*/
|
|
177
|
+
reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind): void;
|
|
90
178
|
/**
|
|
91
179
|
* Evaluate all flags, configs, and experiments for a user against the locally
|
|
92
180
|
* cached blob (no network call). Applies ?se_ks_* / ?se_cf_* / ?se_exp_*
|
|
@@ -229,5 +317,49 @@ declare const flags: {
|
|
|
229
317
|
*/
|
|
230
318
|
evaluate(user: User, rawUrl?: string): BootstrapPayload;
|
|
231
319
|
};
|
|
320
|
+
interface SeeApi {
|
|
321
|
+
/**
|
|
322
|
+
* Report a handled problem and its product consequence:
|
|
323
|
+
*
|
|
324
|
+
* ```ts
|
|
325
|
+
* import { see } from "@shipeasy/sdk/server";
|
|
326
|
+
*
|
|
327
|
+
* try {
|
|
328
|
+
* await chargeCard(order);
|
|
329
|
+
* } catch (e) {
|
|
330
|
+
* see(e).causes_the("payment").to("use the backup processor").extras({ order_id: order.id });
|
|
331
|
+
* await chargeViaBackup(order);
|
|
332
|
+
* }
|
|
333
|
+
* ```
|
|
334
|
+
*
|
|
335
|
+
* The chain dispatches on the next microtask — fire-and-forget into the
|
|
336
|
+
* errors primitive (grouped by fingerprint, near-real-time timeseries).
|
|
337
|
+
* If you don't know the consequence of an exception, don't catch it.
|
|
338
|
+
*/
|
|
339
|
+
(problem: unknown): SeeChain;
|
|
340
|
+
/**
|
|
341
|
+
* Report a non-exception problem. Prefer passing a caught Error to `see()`
|
|
342
|
+
* when one exists. The name is a stable identifier (it participates in the
|
|
343
|
+
* issue fingerprint) — variable data goes in `.message()` or `.extras()`.
|
|
344
|
+
*
|
|
345
|
+
* ```ts
|
|
346
|
+
* if (results.length > LIMIT) {
|
|
347
|
+
* see.Violation("large query").causes_the("search results").to("be trimmed");
|
|
348
|
+
* }
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
Violation(name: string): SeeViolationChain;
|
|
352
|
+
/**
|
|
353
|
+
* Mark an exception as expected control flow — documents the expectation and
|
|
354
|
+
* reports nothing. The reason must start with "because".
|
|
355
|
+
*/
|
|
356
|
+
ControlFlowException(err: unknown, because: string): void;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Structured error reporter — the whole grammar hangs off this one import.
|
|
360
|
+
* Safe to import anywhere; a call before `shipeasy({ serverKey })` warns and
|
|
361
|
+
* drops (never throws).
|
|
362
|
+
*/
|
|
363
|
+
declare const see: SeeApi;
|
|
232
364
|
|
|
233
|
-
export { type BootstrapHtmlOptions, type BootstrapPayload, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, shipeasy, version };
|
|
365
|
+
export { type BootstrapHtmlOptions, type BootstrapPayload, type Consequence, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, type Violation, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, see, shipeasy, version };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,85 @@
|
|
|
1
|
-
|
|
1
|
+
type SeeExtras = Record<string, string | number | boolean | null | undefined>;
|
|
2
|
+
type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
|
|
3
|
+
/** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
|
|
4
|
+
interface Consequence {
|
|
5
|
+
readonly __seConsequence: true;
|
|
6
|
+
readonly subject: string;
|
|
7
|
+
readonly outcome: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Non-exception problem, built by `violation(name)`. A plain branded object
|
|
11
|
+
* (not an Error subclass) so `.message()` can be a builder method without
|
|
12
|
+
* colliding with `Error.prototype.message`.
|
|
13
|
+
*/
|
|
14
|
+
interface Violation {
|
|
15
|
+
readonly __seViolation: true;
|
|
16
|
+
readonly violationName: string;
|
|
17
|
+
readonly violationMessage?: string;
|
|
18
|
+
/** Attach free-form detail. Variable data goes HERE (or in extras), never in the name. */
|
|
19
|
+
message(msg: string): Violation;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Identity of a problem that see() already reported, carried on the wire as
|
|
23
|
+
* the `caused_by` of a later occurrence. Holds exactly the fields the worker's
|
|
24
|
+
* fingerprint function consumes (raw `message`/`stack` — the server normalizes
|
|
25
|
+
* them) so the backend can recompute the prior issue's fingerprint and link
|
|
26
|
+
* the two issues. See `findCausedBy` for how the link is discovered.
|
|
27
|
+
*/
|
|
28
|
+
interface SeeCausedBy {
|
|
29
|
+
error_type: string;
|
|
30
|
+
message: string;
|
|
31
|
+
stack?: string;
|
|
32
|
+
subject: string;
|
|
33
|
+
outcome: string;
|
|
34
|
+
}
|
|
35
|
+
/** Wire shape — the `type:"error"` RawEvent variant accepted by POST /collect. */
|
|
36
|
+
interface SeeErrorEvent {
|
|
37
|
+
type: "error";
|
|
38
|
+
kind: SeeKind;
|
|
39
|
+
/** Error class/name (e.g. "TypeError") or the violation name. */
|
|
40
|
+
error_type: string;
|
|
41
|
+
message: string;
|
|
42
|
+
stack?: string;
|
|
43
|
+
/** Consequence: "<error_type> causes the <subject> to <outcome>". */
|
|
44
|
+
subject: string;
|
|
45
|
+
outcome: string;
|
|
46
|
+
extras?: Record<string, string | number | boolean>;
|
|
47
|
+
url?: string;
|
|
48
|
+
user_id?: string;
|
|
49
|
+
anonymous_id?: string;
|
|
50
|
+
side: "client" | "server";
|
|
51
|
+
env?: string;
|
|
52
|
+
sdk_version: string;
|
|
53
|
+
ts: number;
|
|
54
|
+
/**
|
|
55
|
+
* The earlier reported problem this occurrence descends from — present when
|
|
56
|
+
* the same error was caught + reported at an inner boundary and then
|
|
57
|
+
* re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
|
|
58
|
+
* Lets the backend stitch the two issues into a cause chain instead of
|
|
59
|
+
* double-counting them as unrelated.
|
|
60
|
+
*/
|
|
61
|
+
caused_by?: SeeCausedBy;
|
|
62
|
+
}
|
|
63
|
+
interface SeeExtrasTail {
|
|
64
|
+
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
|
65
|
+
extras(extras: SeeExtras): SeeExtrasTail;
|
|
66
|
+
}
|
|
67
|
+
interface SeeOutcomeStep {
|
|
68
|
+
/** The user-visible impact: `.causes_the("checkout").to("use cached prices")`. */
|
|
69
|
+
to(outcome: string): SeeExtrasTail;
|
|
70
|
+
}
|
|
71
|
+
interface SeeChain {
|
|
72
|
+
/** Start the consequence sentence — the product surface affected. */
|
|
73
|
+
causes_the(subject: string): SeeOutcomeStep;
|
|
74
|
+
/** camelCase alias of {@link SeeChain.causes_the}. */
|
|
75
|
+
causesThe(subject: string): SeeOutcomeStep;
|
|
76
|
+
}
|
|
77
|
+
interface SeeViolationChain extends SeeChain {
|
|
78
|
+
/** Free-form detail. Variable data goes here (or extras), never in the name. */
|
|
79
|
+
message(msg: string): SeeViolationChain;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
declare const version = "4.0.0";
|
|
2
83
|
interface User {
|
|
3
84
|
user_id?: string;
|
|
4
85
|
anonymous_id?: string;
|
|
@@ -68,6 +149,7 @@ declare class FlagsClient {
|
|
|
68
149
|
private readonly baseUrl;
|
|
69
150
|
private readonly env;
|
|
70
151
|
private readonly telemetry;
|
|
152
|
+
private readonly seeLimiter;
|
|
71
153
|
private flagsBlob;
|
|
72
154
|
private expsBlob;
|
|
73
155
|
private flagsEtag;
|
|
@@ -87,6 +169,12 @@ declare class FlagsClient {
|
|
|
87
169
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
88
170
|
getExperiment<P extends Record<string, unknown>>(name: string, user: User, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
|
|
89
171
|
track(userId: string, eventName: string, props?: Record<string, unknown>): void;
|
|
172
|
+
/**
|
|
173
|
+
* Report a structured error into the errors primitive. Fire-and-forget —
|
|
174
|
+
* never blocks or throws into the request path. Spam-guarded by a 30s
|
|
175
|
+
* dedup window + per-process cap.
|
|
176
|
+
*/
|
|
177
|
+
reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind): void;
|
|
90
178
|
/**
|
|
91
179
|
* Evaluate all flags, configs, and experiments for a user against the locally
|
|
92
180
|
* cached blob (no network call). Applies ?se_ks_* / ?se_cf_* / ?se_exp_*
|
|
@@ -229,5 +317,49 @@ declare const flags: {
|
|
|
229
317
|
*/
|
|
230
318
|
evaluate(user: User, rawUrl?: string): BootstrapPayload;
|
|
231
319
|
};
|
|
320
|
+
interface SeeApi {
|
|
321
|
+
/**
|
|
322
|
+
* Report a handled problem and its product consequence:
|
|
323
|
+
*
|
|
324
|
+
* ```ts
|
|
325
|
+
* import { see } from "@shipeasy/sdk/server";
|
|
326
|
+
*
|
|
327
|
+
* try {
|
|
328
|
+
* await chargeCard(order);
|
|
329
|
+
* } catch (e) {
|
|
330
|
+
* see(e).causes_the("payment").to("use the backup processor").extras({ order_id: order.id });
|
|
331
|
+
* await chargeViaBackup(order);
|
|
332
|
+
* }
|
|
333
|
+
* ```
|
|
334
|
+
*
|
|
335
|
+
* The chain dispatches on the next microtask — fire-and-forget into the
|
|
336
|
+
* errors primitive (grouped by fingerprint, near-real-time timeseries).
|
|
337
|
+
* If you don't know the consequence of an exception, don't catch it.
|
|
338
|
+
*/
|
|
339
|
+
(problem: unknown): SeeChain;
|
|
340
|
+
/**
|
|
341
|
+
* Report a non-exception problem. Prefer passing a caught Error to `see()`
|
|
342
|
+
* when one exists. The name is a stable identifier (it participates in the
|
|
343
|
+
* issue fingerprint) — variable data goes in `.message()` or `.extras()`.
|
|
344
|
+
*
|
|
345
|
+
* ```ts
|
|
346
|
+
* if (results.length > LIMIT) {
|
|
347
|
+
* see.Violation("large query").causes_the("search results").to("be trimmed");
|
|
348
|
+
* }
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
Violation(name: string): SeeViolationChain;
|
|
352
|
+
/**
|
|
353
|
+
* Mark an exception as expected control flow — documents the expectation and
|
|
354
|
+
* reports nothing. The reason must start with "because".
|
|
355
|
+
*/
|
|
356
|
+
ControlFlowException(err: unknown, because: string): void;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Structured error reporter — the whole grammar hangs off this one import.
|
|
360
|
+
* Safe to import anywhere; a call before `shipeasy({ serverKey })` warns and
|
|
361
|
+
* drops (never throws).
|
|
362
|
+
*/
|
|
363
|
+
declare const see: SeeApi;
|
|
232
364
|
|
|
233
|
-
export { type BootstrapHtmlOptions, type BootstrapPayload, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, shipeasy, version };
|
|
365
|
+
export { type BootstrapHtmlOptions, type BootstrapPayload, type Consequence, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, type Violation, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, see, shipeasy, version };
|
package/dist/server/index.js
CHANGED
|
@@ -38,6 +38,7 @@ __export(server_exports, {
|
|
|
38
38
|
getBootstrapHtml: () => getBootstrapHtml,
|
|
39
39
|
getShipeasyServerClient: () => getShipeasyServerClient,
|
|
40
40
|
i18n: () => i18n,
|
|
41
|
+
see: () => see,
|
|
41
42
|
shipeasy: () => shipeasy,
|
|
42
43
|
version: () => version
|
|
43
44
|
});
|
|
@@ -103,8 +104,247 @@ function send(url) {
|
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
// src/see/core.ts
|
|
108
|
+
var SEE_MAX_MESSAGE = 500;
|
|
109
|
+
var SEE_MAX_STACK = 8e3;
|
|
110
|
+
var SEE_MAX_SUBJECT = 200;
|
|
111
|
+
var SEE_MAX_EXTRA_VALUE = 200;
|
|
112
|
+
var SEE_MAX_EXTRA_KEYS = 20;
|
|
113
|
+
var SEE_DEDUP_WINDOW_MS = 3e4;
|
|
114
|
+
var SEE_MAX_PER_SESSION = 25;
|
|
115
|
+
function causesThe(subject) {
|
|
116
|
+
return {
|
|
117
|
+
to(outcome) {
|
|
118
|
+
return {
|
|
119
|
+
__seConsequence: true,
|
|
120
|
+
subject: truncate(String(subject), SEE_MAX_SUBJECT),
|
|
121
|
+
outcome: truncate(String(outcome), SEE_MAX_SUBJECT)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function violation(name) {
|
|
127
|
+
const make = (msg) => ({
|
|
128
|
+
__seViolation: true,
|
|
129
|
+
violationName: String(name),
|
|
130
|
+
...msg !== void 0 ? { violationMessage: msg } : {},
|
|
131
|
+
message(m) {
|
|
132
|
+
return make(String(m));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return make();
|
|
136
|
+
}
|
|
137
|
+
function isViolation(p) {
|
|
138
|
+
return typeof p === "object" && p !== null && p.__seViolation === true;
|
|
139
|
+
}
|
|
140
|
+
var EXPECTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-expected");
|
|
141
|
+
function markExpected(err, because) {
|
|
142
|
+
if (typeof err !== "object" || err === null) return;
|
|
143
|
+
try {
|
|
144
|
+
Object.defineProperty(err, EXPECTED_SYM, {
|
|
145
|
+
value: String(because),
|
|
146
|
+
enumerable: false,
|
|
147
|
+
configurable: true
|
|
148
|
+
});
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
153
|
+
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
154
|
+
function readReportStamp(err) {
|
|
155
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
156
|
+
const v = err[REPORTED_SYM];
|
|
157
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
158
|
+
}
|
|
159
|
+
function findCausedBy(problem) {
|
|
160
|
+
let cur = problem;
|
|
161
|
+
const seen = /* @__PURE__ */ new Set();
|
|
162
|
+
for (let depth = 0; depth < SEE_MAX_CAUSE_DEPTH; depth++) {
|
|
163
|
+
if (typeof cur !== "object" || cur === null || seen.has(cur)) break;
|
|
164
|
+
seen.add(cur);
|
|
165
|
+
const stamp = readReportStamp(cur);
|
|
166
|
+
if (stamp) return stamp;
|
|
167
|
+
cur = cur.cause;
|
|
168
|
+
}
|
|
169
|
+
return void 0;
|
|
170
|
+
}
|
|
171
|
+
function markReported(problem, ev) {
|
|
172
|
+
if (!(problem instanceof Error)) return;
|
|
173
|
+
const stamp = {
|
|
174
|
+
error_type: ev.error_type,
|
|
175
|
+
message: ev.message,
|
|
176
|
+
subject: ev.subject,
|
|
177
|
+
outcome: ev.outcome
|
|
178
|
+
};
|
|
179
|
+
if (ev.stack !== void 0) stamp.stack = ev.stack;
|
|
180
|
+
try {
|
|
181
|
+
Object.defineProperty(problem, REPORTED_SYM, {
|
|
182
|
+
value: Object.freeze(stamp),
|
|
183
|
+
enumerable: false,
|
|
184
|
+
configurable: true,
|
|
185
|
+
writable: true
|
|
186
|
+
});
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function truncate(s, max) {
|
|
191
|
+
return s.length > max ? s.slice(0, max) : s;
|
|
192
|
+
}
|
|
193
|
+
function sanitizeExtras(extras) {
|
|
194
|
+
if (!extras || typeof extras !== "object") return void 0;
|
|
195
|
+
const out = {};
|
|
196
|
+
let n = 0;
|
|
197
|
+
for (const [k, v] of Object.entries(extras)) {
|
|
198
|
+
if (v === null || v === void 0) continue;
|
|
199
|
+
if (n >= SEE_MAX_EXTRA_KEYS) break;
|
|
200
|
+
if (typeof v === "string") out[k] = truncate(v, SEE_MAX_EXTRA_VALUE);
|
|
201
|
+
else if (typeof v === "number" && Number.isFinite(v)) out[k] = v;
|
|
202
|
+
else if (typeof v === "boolean") out[k] = v;
|
|
203
|
+
else continue;
|
|
204
|
+
n += 1;
|
|
205
|
+
}
|
|
206
|
+
return n > 0 ? out : void 0;
|
|
207
|
+
}
|
|
208
|
+
function captureCallsiteStack() {
|
|
209
|
+
const raw = new Error().stack;
|
|
210
|
+
if (!raw) return void 0;
|
|
211
|
+
const lines = raw.split("\n");
|
|
212
|
+
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
213
|
+
return kept.length ? kept.join("\n") : void 0;
|
|
214
|
+
}
|
|
215
|
+
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
216
|
+
let errorType;
|
|
217
|
+
let message;
|
|
218
|
+
let stack;
|
|
219
|
+
let kind;
|
|
220
|
+
if (isViolation(problem)) {
|
|
221
|
+
errorType = problem.violationName;
|
|
222
|
+
message = problem.violationMessage ?? problem.violationName;
|
|
223
|
+
stack = captureCallsiteStack();
|
|
224
|
+
kind = kindOverride ?? "violation";
|
|
225
|
+
} else if (problem instanceof Error) {
|
|
226
|
+
errorType = problem.name || "Error";
|
|
227
|
+
message = problem.message || String(problem);
|
|
228
|
+
stack = problem.stack ?? void 0;
|
|
229
|
+
kind = kindOverride ?? "caught";
|
|
230
|
+
} else {
|
|
231
|
+
errorType = "Error";
|
|
232
|
+
message = typeof problem === "string" ? problem : safeString(problem);
|
|
233
|
+
stack = captureCallsiteStack();
|
|
234
|
+
kind = kindOverride ?? "caught";
|
|
235
|
+
}
|
|
236
|
+
const ev = {
|
|
237
|
+
type: "error",
|
|
238
|
+
kind,
|
|
239
|
+
error_type: truncate(errorType, SEE_MAX_SUBJECT),
|
|
240
|
+
message: truncate(message, SEE_MAX_MESSAGE),
|
|
241
|
+
subject: consequence.subject,
|
|
242
|
+
outcome: consequence.outcome,
|
|
243
|
+
side: ctx.side,
|
|
244
|
+
sdk_version: ctx.sdkVersion,
|
|
245
|
+
ts: Date.now()
|
|
246
|
+
};
|
|
247
|
+
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
248
|
+
const causedBy = findCausedBy(problem);
|
|
249
|
+
if (causedBy) ev.caused_by = causedBy;
|
|
250
|
+
const cleanExtras = sanitizeExtras(extras);
|
|
251
|
+
if (cleanExtras) ev.extras = cleanExtras;
|
|
252
|
+
if (ctx.url) ev.url = truncate(ctx.url, SEE_MAX_SUBJECT);
|
|
253
|
+
if (ctx.userId) ev.user_id = ctx.userId;
|
|
254
|
+
if (ctx.anonId) ev.anonymous_id = ctx.anonId;
|
|
255
|
+
if (ctx.env) ev.env = ctx.env;
|
|
256
|
+
markReported(problem, ev);
|
|
257
|
+
return ev;
|
|
258
|
+
}
|
|
259
|
+
function safeString(v) {
|
|
260
|
+
try {
|
|
261
|
+
return typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
262
|
+
} catch {
|
|
263
|
+
return String(v);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
var scheduleMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : (cb) => {
|
|
267
|
+
void Promise.resolve().then(cb);
|
|
268
|
+
};
|
|
269
|
+
function startSeeChain(getProblem, dispatch) {
|
|
270
|
+
let subject;
|
|
271
|
+
let outcome;
|
|
272
|
+
let collected;
|
|
273
|
+
let flushed = false;
|
|
274
|
+
scheduleMicrotask(() => {
|
|
275
|
+
if (flushed) return;
|
|
276
|
+
flushed = true;
|
|
277
|
+
dispatch(
|
|
278
|
+
getProblem(),
|
|
279
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
280
|
+
// leading article would double up ("causes the the app").
|
|
281
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
282
|
+
collected
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
const tail = {
|
|
286
|
+
extras(x) {
|
|
287
|
+
if (x && typeof x === "object") collected = { ...collected, ...x };
|
|
288
|
+
return tail;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
const step = {
|
|
292
|
+
to(o) {
|
|
293
|
+
outcome = String(o);
|
|
294
|
+
return tail;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const start = (s) => {
|
|
298
|
+
subject = String(s);
|
|
299
|
+
return step;
|
|
300
|
+
};
|
|
301
|
+
return { causes_the: start, causesThe: start };
|
|
302
|
+
}
|
|
303
|
+
function startSeeViolationChain(name, dispatch) {
|
|
304
|
+
let msg;
|
|
305
|
+
const base = startSeeChain(
|
|
306
|
+
() => msg !== void 0 ? violation(name).message(msg) : violation(name),
|
|
307
|
+
dispatch
|
|
308
|
+
);
|
|
309
|
+
const chain = {
|
|
310
|
+
...base,
|
|
311
|
+
message(m) {
|
|
312
|
+
msg = String(m);
|
|
313
|
+
return chain;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
return chain;
|
|
317
|
+
}
|
|
318
|
+
function topStackLine(stack) {
|
|
319
|
+
if (!stack) return "";
|
|
320
|
+
for (const line of stack.split("\n")) {
|
|
321
|
+
if (/^\s*at |@|:\d+:\d+/.test(line)) return line.trim().slice(0, 200);
|
|
322
|
+
}
|
|
323
|
+
return "";
|
|
324
|
+
}
|
|
325
|
+
var SeeLimiter = class {
|
|
326
|
+
constructor(maxPerSession = SEE_MAX_PER_SESSION, dedupWindowMs = SEE_DEDUP_WINDOW_MS) {
|
|
327
|
+
this.maxPerSession = maxPerSession;
|
|
328
|
+
this.dedupWindowMs = dedupWindowMs;
|
|
329
|
+
}
|
|
330
|
+
maxPerSession;
|
|
331
|
+
dedupWindowMs;
|
|
332
|
+
lastSent = /* @__PURE__ */ new Map();
|
|
333
|
+
sent = 0;
|
|
334
|
+
shouldSend(ev) {
|
|
335
|
+
if (this.sent >= this.maxPerSession) return false;
|
|
336
|
+
const key = `${ev.kind}|${ev.error_type}|${ev.message.slice(0, 200)}|${topStackLine(ev.stack)}`;
|
|
337
|
+
const now = Date.now();
|
|
338
|
+
const prev = this.lastSent.get(key);
|
|
339
|
+
if (prev !== void 0 && now - prev < this.dedupWindowMs) return false;
|
|
340
|
+
this.lastSent.set(key, now);
|
|
341
|
+
this.sent += 1;
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
106
346
|
// src/server/index.ts
|
|
107
|
-
var version = "
|
|
347
|
+
var version = "4.0.0";
|
|
108
348
|
var C1 = 3432918353;
|
|
109
349
|
var C2 = 461845907;
|
|
110
350
|
function murmur3(key) {
|
|
@@ -254,6 +494,7 @@ var FlagsClient = class {
|
|
|
254
494
|
baseUrl;
|
|
255
495
|
env;
|
|
256
496
|
telemetry;
|
|
497
|
+
seeLimiter = new SeeLimiter();
|
|
257
498
|
flagsBlob = null;
|
|
258
499
|
expsBlob = null;
|
|
259
500
|
flagsEtag = null;
|
|
@@ -406,6 +647,27 @@ var FlagsClient = class {
|
|
|
406
647
|
body
|
|
407
648
|
}).catch((err) => console.warn("[shipeasy] track failed:", String(err)));
|
|
408
649
|
}
|
|
650
|
+
/**
|
|
651
|
+
* Report a structured error into the errors primitive. Fire-and-forget —
|
|
652
|
+
* never blocks or throws into the request path. Spam-guarded by a 30s
|
|
653
|
+
* dedup window + per-process cap.
|
|
654
|
+
*/
|
|
655
|
+
reportError(problem, consequence, extras, kind) {
|
|
656
|
+
try {
|
|
657
|
+
const ev = buildSeeEvent(problem, consequence, extras, {
|
|
658
|
+
side: "server",
|
|
659
|
+
sdkVersion: version,
|
|
660
|
+
env: this.env
|
|
661
|
+
}, kind);
|
|
662
|
+
if (!this.seeLimiter.shouldSend(ev)) return;
|
|
663
|
+
globalThis.fetch(`${this.baseUrl}/collect`, {
|
|
664
|
+
method: "POST",
|
|
665
|
+
headers: { "X-SDK-Key": this.apiKey, "Content-Type": "text/plain" },
|
|
666
|
+
body: JSON.stringify({ events: [ev] })
|
|
667
|
+
}).catch((err) => console.warn("[shipeasy] see() send failed:", String(err)));
|
|
668
|
+
} catch {
|
|
669
|
+
}
|
|
670
|
+
}
|
|
409
671
|
/**
|
|
410
672
|
* Evaluate all flags, configs, and experiments for a user against the locally
|
|
411
673
|
* cached blob (no network call). Applies ?se_ks_* / ?se_cf_* / ?se_exp_*
|
|
@@ -714,6 +976,20 @@ var flags = {
|
|
|
714
976
|
};
|
|
715
977
|
}
|
|
716
978
|
};
|
|
979
|
+
function dispatchSee(problem, consequence, extras, kind) {
|
|
980
|
+
if (!_server) {
|
|
981
|
+
console.warn("[shipeasy] see() called before shipeasy({ serverKey }) \u2014 error dropped");
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
_server.reportError(problem, consequence, extras, kind);
|
|
985
|
+
}
|
|
986
|
+
var see = Object.assign(
|
|
987
|
+
(problem) => startSeeChain(() => problem, dispatchSee),
|
|
988
|
+
{
|
|
989
|
+
Violation: (name) => startSeeViolationChain(name, dispatchSee),
|
|
990
|
+
ControlFlowException: markExpected
|
|
991
|
+
}
|
|
992
|
+
);
|
|
717
993
|
// Annotate the CommonJS export names for ESM import in node:
|
|
718
994
|
0 && (module.exports = {
|
|
719
995
|
FlagsClient,
|
|
@@ -724,6 +1000,7 @@ var flags = {
|
|
|
724
1000
|
getBootstrapHtml,
|
|
725
1001
|
getShipeasyServerClient,
|
|
726
1002
|
i18n,
|
|
1003
|
+
see,
|
|
727
1004
|
shipeasy,
|
|
728
1005
|
version
|
|
729
1006
|
});
|