@statoss/sdk 0.0.0-stage → 0.1.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.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +411 -2
  3. package/dist/cjs/client.d.ts +50 -0
  4. package/dist/cjs/client.js +462 -0
  5. package/dist/cjs/dsn.d.ts +25 -0
  6. package/dist/cjs/dsn.js +62 -0
  7. package/dist/cjs/envelope.d.ts +36 -0
  8. package/dist/cjs/envelope.js +55 -0
  9. package/dist/cjs/event.d.ts +9 -0
  10. package/dist/cjs/event.js +128 -0
  11. package/dist/cjs/hub.d.ts +37 -0
  12. package/dist/cjs/hub.js +119 -0
  13. package/dist/cjs/index.d.ts +38 -0
  14. package/dist/cjs/index.js +107 -0
  15. package/dist/cjs/integrations/browser.d.ts +7 -0
  16. package/dist/cjs/integrations/browser.js +124 -0
  17. package/dist/cjs/integrations/console.d.ts +3 -0
  18. package/dist/cjs/integrations/console.js +106 -0
  19. package/dist/cjs/integrations/dedupe.d.ts +7 -0
  20. package/dist/cjs/integrations/dedupe.js +32 -0
  21. package/dist/cjs/integrations/fetch.d.ts +3 -0
  22. package/dist/cjs/integrations/fetch.js +69 -0
  23. package/dist/cjs/integrations/handlers.d.ts +9 -0
  24. package/dist/cjs/integrations/handlers.js +192 -0
  25. package/dist/cjs/levels.d.ts +20 -0
  26. package/dist/cjs/levels.js +77 -0
  27. package/dist/cjs/logger.d.ts +18 -0
  28. package/dist/cjs/logger.js +38 -0
  29. package/dist/cjs/package.json +1 -0
  30. package/dist/cjs/ratelimit.d.ts +11 -0
  31. package/dist/cjs/ratelimit.js +50 -0
  32. package/dist/cjs/runtime.d.ts +42 -0
  33. package/dist/cjs/runtime.js +188 -0
  34. package/dist/cjs/scope.d.ts +30 -0
  35. package/dist/cjs/scope.js +127 -0
  36. package/dist/cjs/stack.d.ts +9 -0
  37. package/dist/cjs/stack.js +129 -0
  38. package/dist/cjs/transport.d.ts +40 -0
  39. package/dist/cjs/transport.js +218 -0
  40. package/dist/cjs/types.d.ts +180 -0
  41. package/dist/cjs/types.js +2 -0
  42. package/dist/cjs/utils.d.ts +27 -0
  43. package/dist/cjs/utils.js +227 -0
  44. package/dist/cjs/version.d.ts +3 -0
  45. package/dist/cjs/version.js +7 -0
  46. package/dist/esm/client.d.ts +50 -0
  47. package/dist/esm/client.js +458 -0
  48. package/dist/esm/dsn.d.ts +25 -0
  49. package/dist/esm/dsn.js +55 -0
  50. package/dist/esm/envelope.d.ts +36 -0
  51. package/dist/esm/envelope.js +49 -0
  52. package/dist/esm/event.d.ts +9 -0
  53. package/dist/esm/event.js +124 -0
  54. package/dist/esm/hub.d.ts +37 -0
  55. package/dist/esm/hub.js +111 -0
  56. package/dist/esm/index.d.ts +38 -0
  57. package/dist/esm/index.js +88 -0
  58. package/dist/esm/integrations/browser.d.ts +7 -0
  59. package/dist/esm/integrations/browser.js +119 -0
  60. package/dist/esm/integrations/console.d.ts +3 -0
  61. package/dist/esm/integrations/console.js +103 -0
  62. package/dist/esm/integrations/dedupe.d.ts +7 -0
  63. package/dist/esm/integrations/dedupe.js +29 -0
  64. package/dist/esm/integrations/fetch.d.ts +3 -0
  65. package/dist/esm/integrations/fetch.js +66 -0
  66. package/dist/esm/integrations/handlers.d.ts +9 -0
  67. package/dist/esm/integrations/handlers.js +188 -0
  68. package/dist/esm/levels.d.ts +20 -0
  69. package/dist/esm/levels.js +69 -0
  70. package/dist/esm/logger.d.ts +18 -0
  71. package/dist/esm/logger.js +33 -0
  72. package/dist/esm/ratelimit.d.ts +11 -0
  73. package/dist/esm/ratelimit.js +45 -0
  74. package/dist/esm/runtime.d.ts +42 -0
  75. package/dist/esm/runtime.js +181 -0
  76. package/dist/esm/scope.d.ts +30 -0
  77. package/dist/esm/scope.js +123 -0
  78. package/dist/esm/stack.d.ts +9 -0
  79. package/dist/esm/stack.js +125 -0
  80. package/dist/esm/transport.d.ts +40 -0
  81. package/dist/esm/transport.js +213 -0
  82. package/dist/esm/types.d.ts +180 -0
  83. package/dist/esm/types.js +1 -0
  84. package/dist/esm/utils.d.ts +27 -0
  85. package/dist/esm/utils.js +211 -0
  86. package/dist/esm/version.d.ts +3 -0
  87. package/dist/esm/version.js +4 -0
  88. package/package.json +48 -4
@@ -0,0 +1,213 @@
1
+ import { itemCategory, serializeEnvelope } from "./envelope.js";
2
+ import { isRateLimited, updateRateLimits, } from "./ratelimit.js";
3
+ import { safely, unref } from "./utils.js";
4
+ export const QUEUE_LIMIT = 30;
5
+ export const CONCURRENCY = 5;
6
+ const REQUEST_TIMEOUT_MS = 30000;
7
+ // Browsers refuse keepalive requests past 64 KiB in flight in total.
8
+ const KEEPALIVE_BYTES = 60000;
9
+ const KEEPALIVE_REQUESTS = 15;
10
+ /** The default transport: fetch, called on globalThis (Workers refuse any other this). */
11
+ export function fetchTransport(browserLike) {
12
+ return async (request) => {
13
+ const fetchFn = globalThis.fetch;
14
+ if (typeof fetchFn !== "function")
15
+ throw new Error("fetch is not available");
16
+ const controller = typeof AbortController === "function" ? new AbortController() : undefined;
17
+ const timer = controller
18
+ ? setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS)
19
+ : undefined;
20
+ unref(timer);
21
+ // Workers reject RequestInit fields they do not implement, so only browsers get the extra ones.
22
+ const init = {
23
+ method: "POST",
24
+ headers: request.headers,
25
+ body: request.body,
26
+ };
27
+ if (controller)
28
+ init.signal = controller.signal;
29
+ if (browserLike) {
30
+ init.keepalive = request.keepalive;
31
+ init.referrerPolicy = "strict-origin";
32
+ }
33
+ try {
34
+ const response = await fetchFn.call(globalThis, request.url, init);
35
+ // Free the connection: nobody reads the answer.
36
+ safely(() => void response.body?.cancel?.().catch(() => undefined));
37
+ return { status: response.status, headers: response.headers };
38
+ }
39
+ finally {
40
+ if (timer !== undefined)
41
+ clearTimeout(timer);
42
+ }
43
+ };
44
+ }
45
+ function header(response, name) {
46
+ const headers = response.headers;
47
+ if (!headers)
48
+ return null;
49
+ if (typeof headers.get === "function") {
50
+ return headers.get(name);
51
+ }
52
+ for (const [key, value] of Object.entries(headers)) {
53
+ if (key.toLowerCase() === name)
54
+ return value ?? null;
55
+ }
56
+ return null;
57
+ }
58
+ /**
59
+ * Sends envelopes in order of arrival, a few at a time, keeping at most 30
60
+ * waiting or in flight. Whatever cannot be sent is counted for client reports.
61
+ */
62
+ export class Sender {
63
+ constructor(options) {
64
+ this.options = options;
65
+ this.queue = [];
66
+ this.active = 0;
67
+ this.limits = {};
68
+ this.waiters = [];
69
+ this.keepaliveBytes = 0;
70
+ this.keepaliveCount = 0;
71
+ }
72
+ send(envelope) {
73
+ const allowed = this.withoutLimited(envelope);
74
+ if (!allowed)
75
+ return;
76
+ if (this.queue.length + this.active >= QUEUE_LIMIT) {
77
+ this.options.logger.warn("Dropped an envelope: the send queue is full.");
78
+ this.lose(allowed, "queue_overflow");
79
+ return;
80
+ }
81
+ this.queue.push(allowed);
82
+ this.pump();
83
+ }
84
+ /** Resolves true once nothing waits or is in flight, false if timeoutMs passes first. */
85
+ flush(timeoutMs) {
86
+ if (this.idle())
87
+ return Promise.resolve(true);
88
+ return new Promise((resolve) => {
89
+ let settled = false;
90
+ const timer = setTimeout(() => {
91
+ if (!settled) {
92
+ settled = true;
93
+ resolve(false);
94
+ }
95
+ }, timeoutMs);
96
+ unref(timer);
97
+ this.waiters.push(() => {
98
+ if (!settled) {
99
+ settled = true;
100
+ clearTimeout(timer);
101
+ resolve(true);
102
+ }
103
+ });
104
+ });
105
+ }
106
+ /** @internal for tests */
107
+ get rateLimits() {
108
+ return this.limits;
109
+ }
110
+ idle() {
111
+ return this.active === 0 && this.queue.length === 0;
112
+ }
113
+ notify() {
114
+ if (!this.idle())
115
+ return;
116
+ const waiters = this.waiters;
117
+ this.waiters = [];
118
+ for (const waiter of waiters)
119
+ waiter();
120
+ }
121
+ /** Drops the items whose category is rate limited; undefined when none is left. */
122
+ withoutLimited(envelope) {
123
+ const now = Date.now();
124
+ const items = envelope[1].filter((item) => {
125
+ const category = itemCategory(item);
126
+ if (!isRateLimited(this.limits, category, now))
127
+ return true;
128
+ if (category !== "internal")
129
+ this.options.recordLoss("ratelimit_backoff", category);
130
+ return false;
131
+ });
132
+ if (items.length < envelope[1].length) {
133
+ this.options.logger.warn("Held back by a rate limit from the server.");
134
+ }
135
+ return items.length ? [envelope[0], items] : undefined;
136
+ }
137
+ pump() {
138
+ while (this.active < CONCURRENCY && this.queue.length > 0) {
139
+ // A 429 may have come in while it waited.
140
+ const next = this.withoutLimited(this.queue.shift());
141
+ if (!next)
142
+ continue;
143
+ this.active++;
144
+ const done = () => {
145
+ this.active--;
146
+ this.pump();
147
+ };
148
+ void this.deliver(next).then(done, done);
149
+ }
150
+ this.notify();
151
+ }
152
+ async deliver(envelope) {
153
+ const { logger } = this.options;
154
+ let body;
155
+ try {
156
+ body = serializeEnvelope(envelope);
157
+ }
158
+ catch (error) {
159
+ logger.error("Could not serialise an envelope:", error);
160
+ this.lose(envelope, "internal_sdk_error");
161
+ return;
162
+ }
163
+ const keepalive = this.options.browserLike &&
164
+ this.keepaliveBytes + body.length <= KEEPALIVE_BYTES &&
165
+ this.keepaliveCount < KEEPALIVE_REQUESTS;
166
+ if (keepalive) {
167
+ this.keepaliveBytes += body.length;
168
+ this.keepaliveCount++;
169
+ }
170
+ try {
171
+ const response = await this.options.transport({
172
+ url: this.options.url,
173
+ headers: this.options.headers,
174
+ body,
175
+ keepalive,
176
+ });
177
+ const status = typeof response?.status === "number" ? response.status : 200;
178
+ this.limits = updateRateLimits(this.limits, status, header(response, "x-sentry-rate-limits"), header(response, "retry-after"), Date.now());
179
+ if (status === 413 || status >= 500) {
180
+ // The server did not keep it.
181
+ logger.warn(`The server answered ${status}; the envelope was not stored.`);
182
+ this.lose(envelope, "send_error");
183
+ }
184
+ else if (status === 429) {
185
+ logger.warn("The server is rate limiting this app; waiting before sending again.");
186
+ }
187
+ else if (status < 200 || status >= 300) {
188
+ logger.warn(`The server answered ${status}.`);
189
+ }
190
+ else {
191
+ logger.log(`Sent an envelope (${envelope[1].map((i) => i[0].type).join(", ")}).`);
192
+ }
193
+ }
194
+ catch (error) {
195
+ logger.warn("Could not reach the server:", error);
196
+ this.lose(envelope, "network_error");
197
+ }
198
+ finally {
199
+ if (keepalive) {
200
+ this.keepaliveBytes -= body.length;
201
+ this.keepaliveCount--;
202
+ }
203
+ }
204
+ }
205
+ lose(envelope, reason) {
206
+ for (const item of envelope[1]) {
207
+ const category = itemCategory(item);
208
+ // A lost client report is not reported again.
209
+ if (category !== "internal")
210
+ this.options.recordLoss(reason, category);
211
+ }
212
+ }
213
+ }
@@ -0,0 +1,180 @@
1
+ /** The five levels Sentry knows. */
2
+ export type SentryLevel = "fatal" | "error" | "warning" | "info" | "debug";
3
+ /**
4
+ * A Sentry level, or a name of your own such as "payment-failed". Custom
5
+ * names are sent as the Sentry level they map to in `levels`, with the name
6
+ * in the `statoss.level` tag.
7
+ */
8
+ export type Level = SentryLevel | (string & {});
9
+ export type Primitive = string | number | boolean | bigint | null | undefined;
10
+ export interface User {
11
+ id?: string | number;
12
+ email?: string;
13
+ username?: string;
14
+ ip_address?: string;
15
+ [key: string]: unknown;
16
+ }
17
+ export interface Breadcrumb {
18
+ type?: string;
19
+ category?: string;
20
+ message?: string;
21
+ level?: SentryLevel;
22
+ data?: Record<string, unknown>;
23
+ /** Seconds since the epoch; set for you when left out. */
24
+ timestamp?: number;
25
+ }
26
+ export type BreadcrumbHint = Record<string, unknown>;
27
+ export interface StackFrame {
28
+ filename?: string;
29
+ abs_path?: string;
30
+ function?: string;
31
+ lineno?: number;
32
+ colno?: number;
33
+ in_app?: boolean;
34
+ }
35
+ export interface Mechanism {
36
+ type: string;
37
+ handled: boolean;
38
+ synthetic?: boolean;
39
+ source?: string;
40
+ exception_id?: number;
41
+ parent_id?: number;
42
+ }
43
+ export interface Exception {
44
+ type: string;
45
+ value: string;
46
+ mechanism?: Mechanism;
47
+ stacktrace?: {
48
+ frames: StackFrame[];
49
+ };
50
+ }
51
+ export interface RequestInfo {
52
+ url?: string;
53
+ method?: string;
54
+ headers?: Record<string, string>;
55
+ query_string?: string;
56
+ data?: unknown;
57
+ }
58
+ export interface SdkInfo {
59
+ name: string;
60
+ version: string;
61
+ integrations?: string[];
62
+ packages?: {
63
+ name: string;
64
+ version: string;
65
+ }[];
66
+ }
67
+ /** An event in Sentry's schema, as the SDK sends it. */
68
+ export interface Event {
69
+ event_id?: string;
70
+ /** Seconds since the epoch, with fractions. */
71
+ timestamp?: number;
72
+ platform?: string;
73
+ level?: Level;
74
+ logger?: string;
75
+ transaction?: string;
76
+ server_name?: string;
77
+ environment?: string;
78
+ release?: string;
79
+ dist?: string;
80
+ message?: string;
81
+ exception?: {
82
+ values: Exception[];
83
+ };
84
+ breadcrumbs?: {
85
+ values: Breadcrumb[];
86
+ };
87
+ tags?: Record<string, string>;
88
+ extra?: Record<string, unknown>;
89
+ contexts?: Record<string, Record<string, unknown>>;
90
+ user?: User;
91
+ request?: RequestInfo;
92
+ fingerprint?: string[];
93
+ sdk?: SdkInfo;
94
+ [key: string]: unknown;
95
+ }
96
+ /** What `captureException`, `captureMessage` and `captureEvent` take as context. */
97
+ export interface CaptureContext {
98
+ /** A Sentry level or one of your own names (see `levels` in init). */
99
+ level?: Level;
100
+ tags?: Record<string, Primitive>;
101
+ extra?: Record<string, unknown>;
102
+ contexts?: Record<string, Record<string, unknown>>;
103
+ user?: User;
104
+ fingerprint?: string[];
105
+ /** Sent as the `statoss.category` tag. */
106
+ category?: string;
107
+ }
108
+ export interface EventHint {
109
+ event_id: string;
110
+ originalException?: unknown;
111
+ }
112
+ export interface ScopeData {
113
+ tags?: Record<string, Primitive>;
114
+ extra?: Record<string, unknown>;
115
+ contexts?: Record<string, Record<string, unknown>>;
116
+ user?: User;
117
+ level?: Level;
118
+ fingerprint?: string[];
119
+ }
120
+ /** Each defaults to true; integrations that do not apply to a runtime are skipped. */
121
+ export interface IntegrationToggles {
122
+ /** Uncaught errors and unhandled rejections. */
123
+ globalHandlers?: boolean;
124
+ /** Breadcrumbs for console calls. */
125
+ console?: boolean;
126
+ /** Breadcrumbs for fetch requests. */
127
+ fetch?: boolean;
128
+ /** Breadcrumbs for history changes, in browsers. */
129
+ navigation?: boolean;
130
+ /** Breadcrumbs for clicks, in browsers. */
131
+ clicks?: boolean;
132
+ /** Drops an event identical to the one before it. */
133
+ dedupe?: boolean;
134
+ }
135
+ export interface TransportRequest {
136
+ url: string;
137
+ headers: Record<string, string>;
138
+ body: string;
139
+ /** True in browsers for small envelopes, so they survive a page unload. */
140
+ keepalive: boolean;
141
+ }
142
+ export interface TransportResponse {
143
+ status: number;
144
+ headers?: {
145
+ get(name: string): string | null;
146
+ } | Record<string, string | null | undefined>;
147
+ }
148
+ /** Sends one envelope. The default uses fetch; tests can pass their own. */
149
+ export type Transport = (request: TransportRequest) => Promise<TransportResponse>;
150
+ export interface InitOptions {
151
+ /** https://<key>@ingest.statoss.com/<app number>. Without one the SDK stays off. */
152
+ dsn?: string;
153
+ /** Defaults to "production". */
154
+ environment?: string;
155
+ release?: string;
156
+ dist?: string;
157
+ /** Sent as server_name; defaults to the host name in Node and Bun. */
158
+ serverName?: string;
159
+ /** Share of events to send, 0 to 1. Defaults to 1. */
160
+ sampleRate?: number;
161
+ /** Change or drop (return null) an event before it is sent. May be async. */
162
+ beforeSend?: (event: Event, hint: EventHint) => Event | null | PromiseLike<Event | null>;
163
+ /** Change or drop (return null) a breadcrumb before it is kept. */
164
+ beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => Breadcrumb | null;
165
+ /** Defaults to 100, which is also the most kept. */
166
+ maxBreadcrumbs?: number;
167
+ /** When true, browsers ask the server to record the sender's IP address. Defaults to false. */
168
+ sendDefaultPii?: boolean;
169
+ /** Tags on every event. */
170
+ tags?: Record<string, Primitive>;
171
+ initialScope?: ScopeData;
172
+ /** Defaults to true. */
173
+ enabled?: boolean;
174
+ /** Logs what the SDK does to the console. Defaults to false. */
175
+ debug?: boolean;
176
+ transport?: Transport;
177
+ integrations?: IntegrationToggles;
178
+ /** Your own level names and the Sentry level each is sent as, such as { "payment-failed": "error" }. */
179
+ levels?: Record<string, SentryLevel>;
180
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ export declare const MAX_STRING = 8192;
2
+ export declare function now(): number;
3
+ export declare function uuid4(): string;
4
+ export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
5
+ export declare function isObject(value: unknown): value is Record<string, unknown>;
6
+ export declare function toStringTag(value: unknown): string;
7
+ export declare function isErrorLike(value: unknown): value is Error;
8
+ export declare function safeString(value: unknown): string;
9
+ export declare function truncate(value: string, max?: number): string;
10
+ /** Runs fn and swallows anything it throws: the SDK must not break the app. */
11
+ export declare function safely<T>(fn: () => T, fallback?: T): T | undefined;
12
+ /** Lets Node exit while a timer is pending. */
13
+ export declare function unref(timer: unknown): void;
14
+ /**
15
+ * Buffers, typed arrays, DataViews, ArrayBuffers, Maps and Sets as their type
16
+ * and size, such as [Uint8Array(30000000)], without reading their contents.
17
+ */
18
+ export declare function summarize(obj: object): string | undefined;
19
+ /**
20
+ * Makes any value safe to put in JSON: cycles, functions, bigints, errors and
21
+ * deep or wide structures become short strings, and long strings are cut.
22
+ * Arrays and objects are read only up to `breadth` entries, and binary data,
23
+ * Maps and Sets are summarised, so a huge value costs no more than what is kept.
24
+ */
25
+ export declare function normalize(input: unknown, depth?: number, maxString?: number, breadth?: number): unknown;
26
+ /** Removes credentials, the fragment and secret-looking query values from a URL. */
27
+ export declare function scrubUrl(url: string): string;
@@ -0,0 +1,211 @@
1
+ export const MAX_STRING = 8192;
2
+ export function now() {
3
+ return Date.now() / 1000;
4
+ }
5
+ export function uuid4() {
6
+ const c = globalThis.crypto;
7
+ try {
8
+ if (c && typeof c.randomUUID === "function") {
9
+ return c.randomUUID().replace(/-/g, "");
10
+ }
11
+ }
12
+ catch {
13
+ // randomUUID needs a secure context in browsers
14
+ }
15
+ const bytes = new Uint8Array(16);
16
+ try {
17
+ if (c && typeof c.getRandomValues === "function")
18
+ c.getRandomValues(bytes);
19
+ else
20
+ throw new Error("no crypto");
21
+ }
22
+ catch {
23
+ for (let i = 0; i < 16; i++)
24
+ bytes[i] = Math.floor(Math.random() * 256);
25
+ }
26
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
27
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
28
+ let out = "";
29
+ for (const b of bytes)
30
+ out += (b < 16 ? "0" : "") + b.toString(16);
31
+ return out;
32
+ }
33
+ export function isThenable(value) {
34
+ return (value !== null &&
35
+ (typeof value === "object" || typeof value === "function") &&
36
+ typeof value.then === "function");
37
+ }
38
+ export function isObject(value) {
39
+ return value !== null && typeof value === "object";
40
+ }
41
+ export function toStringTag(value) {
42
+ try {
43
+ return Object.prototype.toString.call(value);
44
+ }
45
+ catch {
46
+ return "";
47
+ }
48
+ }
49
+ export function isErrorLike(value) {
50
+ if (!isObject(value))
51
+ return false;
52
+ switch (toStringTag(value)) {
53
+ case "[object Error]":
54
+ case "[object Exception]":
55
+ case "[object DOMException]":
56
+ case "[object DOMError]":
57
+ return true;
58
+ }
59
+ return value instanceof Error;
60
+ }
61
+ export function safeString(value) {
62
+ try {
63
+ return String(value);
64
+ }
65
+ catch {
66
+ return "[unprintable]";
67
+ }
68
+ }
69
+ export function truncate(value, max = MAX_STRING) {
70
+ return value.length > max ? `${value.slice(0, max)}...` : value;
71
+ }
72
+ /** Runs fn and swallows anything it throws: the SDK must not break the app. */
73
+ export function safely(fn, fallback) {
74
+ try {
75
+ return fn();
76
+ }
77
+ catch {
78
+ return fallback;
79
+ }
80
+ }
81
+ /** Lets Node exit while a timer is pending. */
82
+ export function unref(timer) {
83
+ const t = timer;
84
+ if (t && typeof t.unref === "function")
85
+ safely(() => t.unref());
86
+ }
87
+ /**
88
+ * Buffers, typed arrays, DataViews, ArrayBuffers, Maps and Sets as their type
89
+ * and size, such as [Uint8Array(30000000)], without reading their contents.
90
+ */
91
+ export function summarize(obj) {
92
+ const name = safely(() => obj.constructor?.name);
93
+ const tag = toStringTag(obj);
94
+ const label = (fallback) => typeof name === "string" && name ? name : fallback;
95
+ if (typeof ArrayBuffer === "function" && ArrayBuffer.isView(obj)) {
96
+ const view = obj;
97
+ const size = typeof view.length === "number" ? view.length : view.byteLength;
98
+ return `[${label("TypedArray")}(${size})]`;
99
+ }
100
+ if (tag === "[object ArrayBuffer]" || tag === "[object SharedArrayBuffer]") {
101
+ return `[${label("ArrayBuffer")}(${obj.byteLength})]`;
102
+ }
103
+ if (tag === "[object Map]" || tag === "[object Set]") {
104
+ const size = safely(() => obj.size);
105
+ return `[${label(tag.slice(8, -1))}(${typeof size === "number" ? size : "?"})]`;
106
+ }
107
+ return undefined;
108
+ }
109
+ /**
110
+ * Makes any value safe to put in JSON: cycles, functions, bigints, errors and
111
+ * deep or wide structures become short strings, and long strings are cut.
112
+ * Arrays and objects are read only up to `breadth` entries, and binary data,
113
+ * Maps and Sets are summarised, so a huge value costs no more than what is kept.
114
+ */
115
+ export function normalize(input, depth = 5, maxString = MAX_STRING, breadth = 1000) {
116
+ const ancestors = [];
117
+ function walk(value, remaining) {
118
+ if (value === null || value === undefined || typeof value === "boolean")
119
+ return value;
120
+ if (typeof value === "number")
121
+ return Number.isFinite(value) ? value : `[${value}]`;
122
+ if (typeof value === "string")
123
+ return truncate(value, maxString);
124
+ if (typeof value === "bigint")
125
+ return `[BigInt: ${value.toString()}]`;
126
+ if (typeof value === "symbol")
127
+ return `[${safeString(value)}]`;
128
+ if (typeof value === "function")
129
+ return `[Function: ${value.name || "<anonymous>"}]`;
130
+ const obj = value;
131
+ if (ancestors.includes(obj))
132
+ return "[Circular ~]";
133
+ if (isErrorLike(obj)) {
134
+ return walk({ name: obj.name, message: obj.message, stack: obj.stack }, remaining);
135
+ }
136
+ if (typeof obj.nodeType === "number" && typeof obj.nodeName === "string") {
137
+ return `[Node: ${obj.nodeName.toLowerCase()}]`;
138
+ }
139
+ // Before toJSON: Buffer.toJSON would copy every byte into an array.
140
+ const summary = summarize(obj);
141
+ if (summary)
142
+ return summary;
143
+ if (typeof obj.toJSON === "function") {
144
+ const json = safely(() => obj.toJSON());
145
+ if (json !== obj)
146
+ return walk(json, remaining);
147
+ }
148
+ if (remaining <= 0)
149
+ return Array.isArray(obj) ? "[Array]" : "[Object]";
150
+ ancestors.push(obj);
151
+ let out;
152
+ // Nothing past `breadth` entries is ever read or copied.
153
+ if (Array.isArray(obj)) {
154
+ const list = [];
155
+ const n = Math.min(obj.length, breadth);
156
+ for (let i = 0; i < n; i++)
157
+ list.push(walk(obj[i], remaining - 1));
158
+ if (obj.length > breadth)
159
+ list.push("[MaxProperties ~]");
160
+ out = list;
161
+ }
162
+ else {
163
+ const map = {};
164
+ let count = 0;
165
+ for (const key in obj) {
166
+ if (!Object.prototype.hasOwnProperty.call(obj, key))
167
+ continue;
168
+ if (count++ >= breadth) {
169
+ map[key] = "[MaxProperties ~]";
170
+ break;
171
+ }
172
+ map[key] = walk(safely(() => obj[key], "[Unreadable]"), remaining - 1);
173
+ }
174
+ out = map;
175
+ }
176
+ ancestors.pop();
177
+ return out;
178
+ }
179
+ try {
180
+ return walk(input, depth);
181
+ }
182
+ catch {
183
+ return "[Unserializable]";
184
+ }
185
+ }
186
+ const SECRET_KEY = /pass|secret|token|auth|cookie|session|csrf|xsrf|key|sig|jwt|credential|code|otp/i;
187
+ /** Removes credentials, the fragment and secret-looking query values from a URL. */
188
+ export function scrubUrl(url) {
189
+ try {
190
+ let out = url.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^@/?#]*@/i, "$1");
191
+ const hash = out.indexOf("#");
192
+ if (hash >= 0)
193
+ out = out.slice(0, hash);
194
+ const q = out.indexOf("?");
195
+ if (q < 0)
196
+ return out;
197
+ const query = out
198
+ .slice(q + 1)
199
+ .split("&")
200
+ .map((pair) => {
201
+ const eq = pair.indexOf("=");
202
+ const key = eq >= 0 ? pair.slice(0, eq) : pair;
203
+ return eq >= 0 && SECRET_KEY.test(key) ? `${key}=[Filtered]` : pair;
204
+ })
205
+ .join("&");
206
+ return `${out.slice(0, q + 1)}${query}`;
207
+ }
208
+ catch {
209
+ return "";
210
+ }
211
+ }
@@ -0,0 +1,3 @@
1
+ export declare const SDK_NAME = "statoss.javascript";
2
+ export declare const SDK_VERSION = "0.1.1";
3
+ export declare const SDK_PACKAGE = "npm:@statoss/sdk";
@@ -0,0 +1,4 @@
1
+ export const SDK_NAME = "statoss.javascript";
2
+ // Kept equal to package.json by a test.
3
+ export const SDK_VERSION = "0.1.1";
4
+ export const SDK_PACKAGE = "npm:@statoss/sdk";