@squasher-ai/nextjs 0.3.0 → 0.4.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/dist/_vendor/node-sdk/actionable-telemetry.d.ts +4 -0
- package/dist/_vendor/node-sdk/actionable-telemetry.js +32 -0
- package/dist/_vendor/node-sdk/aws-lambda.d.ts +19 -0
- package/dist/_vendor/node-sdk/aws-lambda.js +81 -0
- package/dist/_vendor/node-sdk/client.d.ts +54 -0
- package/dist/_vendor/node-sdk/client.js +479 -0
- package/dist/_vendor/node-sdk/delivery-stats.d.ts +40 -0
- package/dist/_vendor/node-sdk/delivery-stats.js +57 -0
- package/dist/_vendor/node-sdk/http.d.ts +22 -0
- package/dist/_vendor/node-sdk/http.js +139 -0
- package/dist/_vendor/node-sdk/index.d.ts +36 -0
- package/dist/_vendor/node-sdk/index.js +84 -0
- package/dist/_vendor/node-sdk/local-events/sink.d.ts +6 -0
- package/dist/_vendor/node-sdk/local-events/sink.js +43 -0
- package/dist/_vendor/node-sdk/trace-context.d.ts +8 -0
- package/dist/_vendor/node-sdk/trace-context.js +13 -0
- package/dist/_vendor/node-sdk/types.d.ts +65 -0
- package/dist/_vendor/node-sdk/types.js +1 -0
- package/dist/_vendor/sdk-runtime/batching/index.d.ts +42 -0
- package/dist/_vendor/sdk-runtime/batching/index.js +48 -0
- package/dist/_vendor/sdk-runtime/errors/headers.d.ts +6 -0
- package/dist/_vendor/sdk-runtime/errors/headers.js +44 -0
- package/dist/_vendor/sdk-runtime/errors/index.d.ts +95 -0
- package/dist/_vendor/sdk-runtime/errors/index.js +157 -0
- package/dist/_vendor/sdk-runtime/headers.d.ts +48 -0
- package/dist/_vendor/sdk-runtime/headers.js +67 -0
- package/dist/_vendor/sdk-runtime/platform.d.ts +33 -0
- package/dist/_vendor/sdk-runtime/platform.js +173 -0
- package/dist/_vendor/sdk-runtime/retry.d.ts +50 -0
- package/dist/_vendor/sdk-runtime/retry.js +104 -0
- package/dist/_vendor/sdk-runtime/runtime/actionable-error.d.ts +37 -0
- package/dist/_vendor/sdk-runtime/runtime/actionable-error.js +123 -0
- package/dist/_vendor/sdk-runtime/runtime/environment.d.ts +18 -0
- package/dist/_vendor/sdk-runtime/runtime/environment.js +81 -0
- package/dist/_vendor/sdk-runtime/runtime/public-sdk-runtime.d.ts +12 -0
- package/dist/_vendor/sdk-runtime/runtime/public-sdk-runtime.js +38 -0
- package/dist/_vendor/sdk-runtime/runtime/redaction.d.ts +14 -0
- package/dist/_vendor/sdk-runtime/runtime/redaction.js +120 -0
- package/dist/_vendor/sdk-runtime/runtime/release.d.ts +35 -0
- package/dist/_vendor/sdk-runtime/runtime/release.js +120 -0
- package/dist/_vendor/sdk-runtime/sampling/index.d.ts +43 -0
- package/dist/_vendor/sdk-runtime/sampling/index.js +70 -0
- package/dist/api-handler.d.ts +1 -1
- package/dist/api-handler.js +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/middleware.js +1 -1
- package/package.json +1 -4
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry primitives shared across Squasher SDKs.
|
|
3
|
+
*
|
|
4
|
+
* Server retry hints take precedence over exponential backoff with downward
|
|
5
|
+
* jitter. Aborts are never retried; network failures, timeouts, 408, 429, and
|
|
6
|
+
* 5xx responses are retryable by default.
|
|
7
|
+
*/
|
|
8
|
+
import { readHeader } from "./errors/headers.js";
|
|
9
|
+
import { SquasherConnectionError, SquasherUserAbortError } from "./errors/index.js";
|
|
10
|
+
// 408 = Request Timeout, 429 = Too Many Requests. 409 (Conflict) is
|
|
11
|
+
// intentionally NOT included — for Squasher's ingest path, 409 means
|
|
12
|
+
// "duplicate event detected by server dedup", and retrying creates extra
|
|
13
|
+
// writes that re-trip the dedup. Servers that genuinely want a transient
|
|
14
|
+
// 409 retried (e.g. optimistic-locking conflicts) can opt back in via
|
|
15
|
+
// the `X-Should-Retry: true` response header.
|
|
16
|
+
const RETRYABLE_STATUSES = new Set([408, 429]);
|
|
17
|
+
/**
|
|
18
|
+
* Decide whether a response (or thrown error) should trigger a retry.
|
|
19
|
+
*
|
|
20
|
+
* Aborts always opt out — `AbortController.abort()` is user intent, not a
|
|
21
|
+
* transient failure. Connection errors always opt in. Status-code logic
|
|
22
|
+
* mirrors Stainless: 408/409/429 plus any 5xx, with `X-Should-Retry`
|
|
23
|
+
* override.
|
|
24
|
+
*/
|
|
25
|
+
export function shouldRetry(input) {
|
|
26
|
+
if (input.thrown instanceof SquasherUserAbortError)
|
|
27
|
+
return false;
|
|
28
|
+
if (input.thrown instanceof SquasherConnectionError)
|
|
29
|
+
return true;
|
|
30
|
+
if (input.thrown && input.status === undefined) {
|
|
31
|
+
// Unknown thrown value with no response — treat as transport failure.
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
const override = readHeader(input.headers, "x-should-retry");
|
|
35
|
+
if (override === "true")
|
|
36
|
+
return true;
|
|
37
|
+
if (override === "false")
|
|
38
|
+
return false;
|
|
39
|
+
if (input.status === undefined)
|
|
40
|
+
return false;
|
|
41
|
+
if (RETRYABLE_STATUSES.has(input.status))
|
|
42
|
+
return true;
|
|
43
|
+
if (input.status >= 500)
|
|
44
|
+
return true;
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const DEFAULT_MAX_SLEEP_MS = 8_000;
|
|
48
|
+
/**
|
|
49
|
+
* Compute the delay in milliseconds before the next attempt. Server-supplied
|
|
50
|
+
* hints win when present, otherwise falls back to exponential backoff with
|
|
51
|
+
* downward jitter.
|
|
52
|
+
*
|
|
53
|
+
* Returned value is always non-negative. Capped at `maxSleepMs` (default 8s)
|
|
54
|
+
* even when the server requests a longer retry — long sleeps almost always
|
|
55
|
+
* indicate the caller should reach for queueing instead of in-process retries.
|
|
56
|
+
*/
|
|
57
|
+
export function nextBackoffMs(opts) {
|
|
58
|
+
const max = opts.maxSleepMs ?? DEFAULT_MAX_SLEEP_MS;
|
|
59
|
+
const random = opts.random ?? Math.random;
|
|
60
|
+
const retryAfterMsHeader = readHeader(opts.headers, "retry-after-ms");
|
|
61
|
+
if (retryAfterMsHeader !== undefined) {
|
|
62
|
+
const ms = Number.parseFloat(retryAfterMsHeader);
|
|
63
|
+
if (Number.isFinite(ms) && ms >= 0)
|
|
64
|
+
return Math.min(ms, max);
|
|
65
|
+
}
|
|
66
|
+
const retryAfterHeader = readHeader(opts.headers, "retry-after");
|
|
67
|
+
if (retryAfterHeader !== undefined) {
|
|
68
|
+
const seconds = Number.parseFloat(retryAfterHeader);
|
|
69
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
70
|
+
return Math.min(seconds * 1000, max);
|
|
71
|
+
}
|
|
72
|
+
// HTTP-date form (RFC 7231)
|
|
73
|
+
const dateMs = Date.parse(retryAfterHeader);
|
|
74
|
+
if (!Number.isNaN(dateMs)) {
|
|
75
|
+
const now = opts.now ? opts.now() : Date.now();
|
|
76
|
+
const delta = dateMs - now;
|
|
77
|
+
if (delta > 0)
|
|
78
|
+
return Math.min(delta, max);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Exponential backoff with downward jitter.
|
|
83
|
+
const baseSeconds = Math.min(0.5 * Math.pow(2, opts.attempt), max / 1000);
|
|
84
|
+
const jitter = 1 - random() * 0.25;
|
|
85
|
+
return Math.min(baseSeconds * 1000 * jitter, max);
|
|
86
|
+
}
|
|
87
|
+
/** Promise-friendly setTimeout. Resolves after `ms` milliseconds. */
|
|
88
|
+
export function sleep(ms, signal) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
if (signal?.aborted) {
|
|
91
|
+
reject(new SquasherUserAbortError());
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const timer = setTimeout(() => {
|
|
95
|
+
signal?.removeEventListener("abort", onAbort);
|
|
96
|
+
resolve();
|
|
97
|
+
}, ms);
|
|
98
|
+
const onAbort = () => {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
reject(new SquasherUserAbortError());
|
|
101
|
+
};
|
|
102
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SquasherError } from "../errors/index.js";
|
|
2
|
+
export type ActionableErrorValue = boolean | null | number | string;
|
|
3
|
+
export type ActionableErrorInternal = Record<string, ActionableErrorValue | ActionableErrorValue[]>;
|
|
4
|
+
interface ActionableErrorJson extends ActionableErrorContext {
|
|
5
|
+
message: string;
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ActionableErrorContext {
|
|
9
|
+
status?: number | string;
|
|
10
|
+
why?: string;
|
|
11
|
+
fix?: string;
|
|
12
|
+
link?: string;
|
|
13
|
+
internal?: ActionableErrorInternal;
|
|
14
|
+
}
|
|
15
|
+
export interface ActionableErrorOptions extends ActionableErrorContext {
|
|
16
|
+
cause?: Error;
|
|
17
|
+
name?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ActionableErrorTelemetry {
|
|
20
|
+
attributes: Record<string, number | string>;
|
|
21
|
+
extra: {
|
|
22
|
+
squasher_error: ActionableErrorContext;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare class ActionableError extends SquasherError {
|
|
26
|
+
readonly status: number | string | undefined;
|
|
27
|
+
readonly why: string | undefined;
|
|
28
|
+
readonly fix: string | undefined;
|
|
29
|
+
readonly link: string | undefined;
|
|
30
|
+
readonly internal: ActionableErrorInternal | undefined;
|
|
31
|
+
constructor(message: string, options?: ActionableErrorOptions);
|
|
32
|
+
toJSON(): ActionableErrorJson;
|
|
33
|
+
}
|
|
34
|
+
export declare function createActionableError(message: string, options?: ActionableErrorOptions): ActionableError;
|
|
35
|
+
export declare function getActionableErrorContext(error: Error): ActionableErrorContext | undefined;
|
|
36
|
+
export declare function getActionableErrorTelemetry(error: Error): ActionableErrorTelemetry | undefined;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { SquasherError } from "../errors/index.js";
|
|
2
|
+
export class ActionableError extends SquasherError {
|
|
3
|
+
status;
|
|
4
|
+
why;
|
|
5
|
+
fix;
|
|
6
|
+
link;
|
|
7
|
+
constructor(message, options = {}) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = options.name ?? "ActionableError";
|
|
10
|
+
this.status = options.status;
|
|
11
|
+
this.why = options.why;
|
|
12
|
+
this.fix = options.fix;
|
|
13
|
+
this.link = options.link;
|
|
14
|
+
if (options.cause) {
|
|
15
|
+
Object.defineProperty(this, "cause", { value: options.cause });
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(this, "internal", {
|
|
18
|
+
enumerable: false,
|
|
19
|
+
value: readInternal(options.internal),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
toJSON() {
|
|
23
|
+
const result = { message: this.message, name: this.name };
|
|
24
|
+
if (this.status !== undefined)
|
|
25
|
+
result.status = this.status;
|
|
26
|
+
if (this.why)
|
|
27
|
+
result.why = this.why;
|
|
28
|
+
if (this.fix)
|
|
29
|
+
result.fix = this.fix;
|
|
30
|
+
if (this.link)
|
|
31
|
+
result.link = this.link;
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function createActionableError(message, options = {}) {
|
|
36
|
+
return new ActionableError(message, options);
|
|
37
|
+
}
|
|
38
|
+
export function getActionableErrorContext(error) {
|
|
39
|
+
const internal = readInternal(readProperty(error, "internal"));
|
|
40
|
+
const context = {
|
|
41
|
+
status: readStatus(readProperty(error, "status")),
|
|
42
|
+
why: readText(readProperty(error, "why")),
|
|
43
|
+
fix: readText(readProperty(error, "fix")),
|
|
44
|
+
link: readText(readProperty(error, "link")),
|
|
45
|
+
internal,
|
|
46
|
+
};
|
|
47
|
+
return context.status !== undefined || context.why || context.fix || context.link || internal
|
|
48
|
+
? context
|
|
49
|
+
: undefined;
|
|
50
|
+
}
|
|
51
|
+
export function getActionableErrorTelemetry(error) {
|
|
52
|
+
const context = getActionableErrorContext(error);
|
|
53
|
+
if (!context)
|
|
54
|
+
return undefined;
|
|
55
|
+
const attributes = {};
|
|
56
|
+
if (context.status !== undefined)
|
|
57
|
+
attributes["error.status"] = context.status;
|
|
58
|
+
if (context.why)
|
|
59
|
+
attributes["error.why"] = context.why;
|
|
60
|
+
if (context.fix)
|
|
61
|
+
attributes["error.fix"] = context.fix;
|
|
62
|
+
if (context.link)
|
|
63
|
+
attributes["error.link"] = context.link;
|
|
64
|
+
return { attributes, extra: { squasher_error: context } };
|
|
65
|
+
}
|
|
66
|
+
function readProperty(error, name) {
|
|
67
|
+
try {
|
|
68
|
+
const owner = Object(error);
|
|
69
|
+
let cursor = owner;
|
|
70
|
+
while (cursor) {
|
|
71
|
+
const descriptor = Object.getOwnPropertyDescriptor(cursor, name);
|
|
72
|
+
if (descriptor) {
|
|
73
|
+
return "value" in descriptor ? descriptor.value : descriptor.get?.call(owner);
|
|
74
|
+
}
|
|
75
|
+
cursor = Object.getPrototypeOf(cursor);
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function readText(value) {
|
|
84
|
+
if (Object.prototype.toString.call(value) !== "[object String]")
|
|
85
|
+
return undefined;
|
|
86
|
+
const text = String(value).trim();
|
|
87
|
+
return text.length > 0 ? text : undefined;
|
|
88
|
+
}
|
|
89
|
+
function readStatus(value) {
|
|
90
|
+
const tag = Object.prototype.toString.call(value);
|
|
91
|
+
if (tag === "[object Number]") {
|
|
92
|
+
const number = Number(value);
|
|
93
|
+
return Number.isFinite(number) ? number : undefined;
|
|
94
|
+
}
|
|
95
|
+
if (tag === "[object String]")
|
|
96
|
+
return readText(value);
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
function readInternal(value) {
|
|
100
|
+
if (Object.prototype.toString.call(value) !== "[object Object]")
|
|
101
|
+
return undefined;
|
|
102
|
+
const internal = {};
|
|
103
|
+
for (const [key, entry] of Object.entries(Object(value))) {
|
|
104
|
+
const parsed = readInternalValue(entry);
|
|
105
|
+
if (parsed !== undefined)
|
|
106
|
+
internal[key] = parsed;
|
|
107
|
+
}
|
|
108
|
+
return Object.keys(internal).length > 0 ? internal : undefined;
|
|
109
|
+
}
|
|
110
|
+
function readInternalValue(value) {
|
|
111
|
+
if (Array.isArray(value)) {
|
|
112
|
+
const values = value.filter(isActionableValue);
|
|
113
|
+
return values.length > 0 ? values : undefined;
|
|
114
|
+
}
|
|
115
|
+
return isActionableValue(value) ? value : undefined;
|
|
116
|
+
}
|
|
117
|
+
function isActionableValue(value) {
|
|
118
|
+
const tag = Object.prototype.toString.call(value);
|
|
119
|
+
return (value === null ||
|
|
120
|
+
tag === "[object Boolean]" ||
|
|
121
|
+
tag === "[object Number]" ||
|
|
122
|
+
tag === "[object String]");
|
|
123
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type RuntimePrimitive = boolean | null | number | string;
|
|
2
|
+
export type RuntimeValue = RuntimePrimitive | RuntimeValue[] | RuntimeRecord | undefined;
|
|
3
|
+
export interface RuntimeRecord {
|
|
4
|
+
[key: string]: RuntimeValue;
|
|
5
|
+
}
|
|
6
|
+
export interface RuntimeMethod<TResult> {
|
|
7
|
+
(): TResult;
|
|
8
|
+
}
|
|
9
|
+
export interface RuntimeEnvironment {
|
|
10
|
+
[name: string]: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
export declare function readGlobalProperty(name: string): RuntimeValue;
|
|
13
|
+
export declare function readRuntimeProperty<T>(value: T, name: string): RuntimeValue;
|
|
14
|
+
export declare function readRuntimeString<T>(value: T): string | undefined;
|
|
15
|
+
export declare function readRuntimeNumber<T>(value: T): number | undefined;
|
|
16
|
+
export declare function readRuntimeMethod<TResult, T = RuntimeValue>(value: T, name: PropertyKey): RuntimeMethod<TResult> | undefined;
|
|
17
|
+
export declare function readProcessEnvironment(): RuntimeEnvironment;
|
|
18
|
+
export declare function readGlobalEnvironmentValue(name: string): string | undefined;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export function readGlobalProperty(name) {
|
|
2
|
+
try {
|
|
3
|
+
return readRuntimeProperty(globalThis, name);
|
|
4
|
+
}
|
|
5
|
+
catch {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function readRuntimeProperty(value, name) {
|
|
10
|
+
try {
|
|
11
|
+
if (value === null || value === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
const owner = Object(value);
|
|
14
|
+
let cursor = owner;
|
|
15
|
+
while (cursor) {
|
|
16
|
+
const descriptor = Object.getOwnPropertyDescriptor(cursor, name);
|
|
17
|
+
if (descriptor) {
|
|
18
|
+
if ("value" in descriptor)
|
|
19
|
+
return descriptor.value;
|
|
20
|
+
return descriptor.get?.call(owner);
|
|
21
|
+
}
|
|
22
|
+
cursor = Object.getPrototypeOf(cursor);
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function readRuntimeString(value) {
|
|
31
|
+
return Object.prototype.toString.call(value) === "[object String]" ? String(value) : undefined;
|
|
32
|
+
}
|
|
33
|
+
export function readRuntimeNumber(value) {
|
|
34
|
+
if (Object.prototype.toString.call(value) !== "[object Number]")
|
|
35
|
+
return undefined;
|
|
36
|
+
const number = Number(value);
|
|
37
|
+
return Number.isFinite(number) ? number : undefined;
|
|
38
|
+
}
|
|
39
|
+
export function readRuntimeMethod(value, name) {
|
|
40
|
+
try {
|
|
41
|
+
if (value === null || value === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
const owner = Object(value);
|
|
44
|
+
let cursor = owner;
|
|
45
|
+
while (cursor) {
|
|
46
|
+
const descriptor = Object.getOwnPropertyDescriptor(cursor, name);
|
|
47
|
+
if (descriptor) {
|
|
48
|
+
const candidate = "value" in descriptor ? descriptor.value : descriptor.get?.call(owner);
|
|
49
|
+
return Object.prototype.toString.call(candidate) === "[object Function]"
|
|
50
|
+
? candidate
|
|
51
|
+
: undefined;
|
|
52
|
+
}
|
|
53
|
+
cursor = Object.getPrototypeOf(cursor);
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function readProcessEnvironment() {
|
|
62
|
+
const value = readRuntimeProperty(readGlobalProperty("process"), "env");
|
|
63
|
+
if (Object.prototype.toString.call(value) !== "[object Object]")
|
|
64
|
+
return {};
|
|
65
|
+
const environment = {};
|
|
66
|
+
for (const [name, entry] of Object.entries(Object(value))) {
|
|
67
|
+
environment[name] = readRuntimeString(entry);
|
|
68
|
+
}
|
|
69
|
+
return environment;
|
|
70
|
+
}
|
|
71
|
+
export function readGlobalEnvironmentValue(name) {
|
|
72
|
+
const value = readGlobalProperty(name);
|
|
73
|
+
const tag = Object.prototype.toString.call(value);
|
|
74
|
+
if (tag === "[object String]")
|
|
75
|
+
return String(value);
|
|
76
|
+
if (tag === "[object Boolean]")
|
|
77
|
+
return value ? "1" : "0";
|
|
78
|
+
if (tag === "[object Number]")
|
|
79
|
+
return String(value);
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const DEFAULT_INGEST_ENDPOINT = "https://ingest.squasher.ai";
|
|
2
|
+
export { buildBatches, type BuildBatchesOptions, type BuiltBatch, MAX_BATCH_BODY_BYTES, } from "../batching/index.js";
|
|
3
|
+
export { ActionableError, type ActionableErrorContext, type ActionableErrorOptions, type ActionableErrorTelemetry, type ActionableErrorValue, createActionableError, getActionableErrorContext, getActionableErrorTelemetry, } from "./actionable-error.js";
|
|
4
|
+
export { readGlobalEnvironmentValue, readGlobalProperty, readProcessEnvironment, readRuntimeMethod, readRuntimeNumber, readRuntimeProperty, readRuntimeString, type RuntimeEnvironment, type RuntimeMethod, type RuntimePrimitive, type RuntimeRecord, type RuntimeValue, } from "./environment.js";
|
|
5
|
+
export { type DefaultHeadersOptions, getDefaultHeaders, getRequestHeaders, getUserAgent, type RequestHeaderOptions, } from "../headers.js";
|
|
6
|
+
export { type BackoffOptions, type HeadersLike, nextBackoffMs, type RetryDecisionInput, shouldRetry, sleep, } from "../retry.js";
|
|
7
|
+
export { createRedactor, type RedactableValue, type Redactor, type RedactorKeyMask, type RedactorOptions, type RedactorPathMask, type RedactorPathSegment, } from "./redaction.js";
|
|
8
|
+
export { detectRelease, isValidRelease, parseRelease, type ParsedRelease } from "./release.js";
|
|
9
|
+
export { type ResolvedTelemetrySampling, resolveTelemetrySampling, shouldSampleTelemetry, } from "../sampling/index.js";
|
|
10
|
+
export declare function resolveEnvFlag(name: string): boolean;
|
|
11
|
+
export declare function resolveDebugFlag(configDebug: boolean | undefined): boolean;
|
|
12
|
+
export declare function readEnvValue(name: string): string | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const DEFAULT_INGEST_ENDPOINT = "https://ingest.squasher.ai";
|
|
2
|
+
export { buildBatches, MAX_BATCH_BODY_BYTES, } from "../batching/index.js";
|
|
3
|
+
export { ActionableError, createActionableError, getActionableErrorContext, getActionableErrorTelemetry, } from "./actionable-error.js";
|
|
4
|
+
export { readGlobalEnvironmentValue, readGlobalProperty, readProcessEnvironment, readRuntimeMethod, readRuntimeNumber, readRuntimeProperty, readRuntimeString, } from "./environment.js";
|
|
5
|
+
export { getDefaultHeaders, getRequestHeaders, getUserAgent, } from "../headers.js";
|
|
6
|
+
export { nextBackoffMs, shouldRetry, sleep, } from "../retry.js";
|
|
7
|
+
export { createRedactor, } from "./redaction.js";
|
|
8
|
+
export { detectRelease, isValidRelease, parseRelease } from "./release.js";
|
|
9
|
+
export { resolveTelemetrySampling, shouldSampleTelemetry, } from "../sampling/index.js";
|
|
10
|
+
import { readGlobalEnvironmentValue, readProcessEnvironment } from "./environment.js";
|
|
11
|
+
export function resolveEnvFlag(name) {
|
|
12
|
+
const envName = `SQUASHER_${name}`;
|
|
13
|
+
const raw = readEnvValue(envName);
|
|
14
|
+
if (raw === undefined || raw === null)
|
|
15
|
+
return false;
|
|
16
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
17
|
+
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
18
|
+
}
|
|
19
|
+
export function resolveDebugFlag(configDebug) {
|
|
20
|
+
if (configDebug !== undefined)
|
|
21
|
+
return configDebug;
|
|
22
|
+
return resolveEnvFlag("DEBUG");
|
|
23
|
+
}
|
|
24
|
+
export function readEnvValue(name) {
|
|
25
|
+
try {
|
|
26
|
+
const value = readProcessEnvironment()[name];
|
|
27
|
+
if (value !== undefined && value !== null && value !== "")
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
try {
|
|
32
|
+
const value = readGlobalEnvironmentValue(name);
|
|
33
|
+
if (value !== undefined && value !== "")
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
catch { }
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type RedactableValue = boolean | null | number | string | RedactableValue[] | {
|
|
2
|
+
[key: string]: RedactableValue;
|
|
3
|
+
};
|
|
4
|
+
export type RedactorKeyMask = RegExp | string;
|
|
5
|
+
export type RedactorPathSegment = number | string;
|
|
6
|
+
export type RedactorPathMask = readonly RedactorPathSegment[] | string;
|
|
7
|
+
export interface RedactorOptions {
|
|
8
|
+
mask?: string;
|
|
9
|
+
keyMasks?: readonly RedactorKeyMask[];
|
|
10
|
+
pathMasks?: readonly RedactorPathMask[];
|
|
11
|
+
patternMasks?: readonly RegExp[];
|
|
12
|
+
}
|
|
13
|
+
export type Redactor = (value: RedactableValue) => RedactableValue;
|
|
14
|
+
export declare function createRedactor(options?: RedactorOptions): Redactor;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const DEFAULT_MASK = "[REDACTED]";
|
|
2
|
+
const EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
3
|
+
const BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+\b/gi;
|
|
4
|
+
const JWT_PATTERN = /\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\b/g;
|
|
5
|
+
const API_KEY_PATTERN = /\b(?:sk|pk|sq|squasher)_(?:pk|sk|live|test|dev|prod)?_?[A-Za-z0-9_-]{16,}\b/g;
|
|
6
|
+
const SENSITIVE_KEY_PARTS = [
|
|
7
|
+
"apikey",
|
|
8
|
+
"authorization",
|
|
9
|
+
"authtoken",
|
|
10
|
+
"bearertoken",
|
|
11
|
+
"clientsecret",
|
|
12
|
+
"cookie",
|
|
13
|
+
"idtoken",
|
|
14
|
+
"password",
|
|
15
|
+
"passwd",
|
|
16
|
+
"privatekey",
|
|
17
|
+
"refreshtoken",
|
|
18
|
+
"secret",
|
|
19
|
+
"sessiontoken",
|
|
20
|
+
"squasherkey",
|
|
21
|
+
"token",
|
|
22
|
+
];
|
|
23
|
+
export function createRedactor(options = {}) {
|
|
24
|
+
const config = {
|
|
25
|
+
mask: options.mask ?? DEFAULT_MASK,
|
|
26
|
+
keyMasks: options.keyMasks ?? [],
|
|
27
|
+
pathMasks: options.pathMasks ?? [],
|
|
28
|
+
patternMasks: options.patternMasks ?? [],
|
|
29
|
+
};
|
|
30
|
+
return (value) => redactValue(value, [], config);
|
|
31
|
+
}
|
|
32
|
+
function redactValue(value, path, config) {
|
|
33
|
+
if (Object.prototype.toString.call(value) === "[object String]") {
|
|
34
|
+
return redactString(String(value), config);
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
return value.map((item, index) => redactValue(item, [...path, index], config));
|
|
38
|
+
}
|
|
39
|
+
if (!isRedactableRecord(value))
|
|
40
|
+
return value;
|
|
41
|
+
const result = {};
|
|
42
|
+
for (const [key, item] of Object.entries(value)) {
|
|
43
|
+
const itemPath = [...path, key];
|
|
44
|
+
result[key] = shouldMaskField(key, itemPath, config)
|
|
45
|
+
? config.mask
|
|
46
|
+
: redactValue(item, itemPath, config);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
function isRedactableRecord(value) {
|
|
51
|
+
if (value === null)
|
|
52
|
+
return false;
|
|
53
|
+
try {
|
|
54
|
+
const prototype = Object.getPrototypeOf(Object(value));
|
|
55
|
+
return prototype === null || Object.getPrototypeOf(prototype) === null;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function redactString(value, config) {
|
|
62
|
+
let redacted = value
|
|
63
|
+
.replace(EMAIL_PATTERN, config.mask)
|
|
64
|
+
.replace(BEARER_PATTERN, `Bearer ${config.mask}`)
|
|
65
|
+
.replace(JWT_PATTERN, config.mask)
|
|
66
|
+
.replace(API_KEY_PATTERN, config.mask);
|
|
67
|
+
for (const pattern of config.patternMasks) {
|
|
68
|
+
redacted = redacted.replace(globalPattern(pattern), config.mask);
|
|
69
|
+
}
|
|
70
|
+
return redacted;
|
|
71
|
+
}
|
|
72
|
+
function shouldMaskField(key, path, config) {
|
|
73
|
+
if (isSensitiveKey(key))
|
|
74
|
+
return true;
|
|
75
|
+
if (config.keyMasks.some((mask) => matchesKeyMask(mask, key)))
|
|
76
|
+
return true;
|
|
77
|
+
return config.pathMasks.some((mask) => matchesPathMask(mask, path));
|
|
78
|
+
}
|
|
79
|
+
function isSensitiveKey(key) {
|
|
80
|
+
const normalized = normalizeSegment(key);
|
|
81
|
+
return SENSITIVE_KEY_PARTS.some((part) => normalized.includes(part));
|
|
82
|
+
}
|
|
83
|
+
function matchesKeyMask(mask, key) {
|
|
84
|
+
if (isRegularExpression(mask)) {
|
|
85
|
+
mask.lastIndex = 0;
|
|
86
|
+
return RegExp.prototype.test.call(mask, key);
|
|
87
|
+
}
|
|
88
|
+
return normalizeSegment(String(mask)) === normalizeSegment(key);
|
|
89
|
+
}
|
|
90
|
+
function matchesPathMask(mask, path) {
|
|
91
|
+
const maskPath = Array.isArray(mask) ? mask : splitPath(String(mask));
|
|
92
|
+
if (maskPath.length !== path.length)
|
|
93
|
+
return false;
|
|
94
|
+
for (let index = 0; index < maskPath.length; index += 1) {
|
|
95
|
+
const segment = maskPath[index];
|
|
96
|
+
if (segment === "*")
|
|
97
|
+
continue;
|
|
98
|
+
if (normalizePathSegment(segment) !== normalizePathSegment(path[index]))
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
function isRegularExpression(mask) {
|
|
104
|
+
return Object.prototype.toString.call(mask) === "[object RegExp]";
|
|
105
|
+
}
|
|
106
|
+
function splitPath(path) {
|
|
107
|
+
return path.split(".").filter((segment) => segment.length > 0);
|
|
108
|
+
}
|
|
109
|
+
function normalizePathSegment(segment) {
|
|
110
|
+
return Object.prototype.toString.call(segment) === "[object Number]"
|
|
111
|
+
? String(segment)
|
|
112
|
+
: normalizeSegment(String(segment ?? ""));
|
|
113
|
+
}
|
|
114
|
+
function normalizeSegment(segment) {
|
|
115
|
+
return segment.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
|
|
116
|
+
}
|
|
117
|
+
function globalPattern(pattern) {
|
|
118
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
119
|
+
return new RegExp(pattern.source, flags);
|
|
120
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type RuntimeEnvironment } from "./environment.js";
|
|
2
|
+
/**
|
|
3
|
+
* `canonical` is what every consumer should display / store: v-prefix
|
|
4
|
+
* stripped, `service@version+build` form rebuilt from the parsed components,
|
|
5
|
+
* so `v1.2.3` and `1.2.3` (or two inputs with different whitespace) compare
|
|
6
|
+
* equal by `canonical`.
|
|
7
|
+
*/
|
|
8
|
+
export interface ParsedRelease {
|
|
9
|
+
canonical: string;
|
|
10
|
+
service: string | null;
|
|
11
|
+
version: string;
|
|
12
|
+
build: string | null;
|
|
13
|
+
isSemver: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type ReleaseInput = string | null | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Parse + validate a release string. Returns `null` for empty input, junk
|
|
18
|
+
* sentinels, values over 200 chars, or strings with whitespace / control chars.
|
|
19
|
+
* Accepts: `1.2.3`, `v1.2.3`, `svc@1.2.3`, `svc@1.2.3+sha`, `1.2.3+sha`, and
|
|
20
|
+
* bare git shas (non-semver but valid).
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseRelease(input: ReleaseInput): ParsedRelease | null;
|
|
23
|
+
/**
|
|
24
|
+
* True when `parseRelease(input)` would accept the value. Pure boolean — use
|
|
25
|
+
* this at validation boundaries that don't need the parsed pieces (e.g. zod
|
|
26
|
+
* `.refine()`, ingest pre-filters).
|
|
27
|
+
*/
|
|
28
|
+
export declare function isValidRelease(input: ReleaseInput): input is string;
|
|
29
|
+
/**
|
|
30
|
+
* Auto-detect a release from common CI/host env vars so customers on
|
|
31
|
+
* Vercel / Railway / Render / Fly / GitHub Actions get a useful release tag
|
|
32
|
+
* without configuring anything. Explicit `SQUASHER_RELEASE` always wins;
|
|
33
|
+
* values that fail `parseRelease` are skipped and detection continues.
|
|
34
|
+
*/
|
|
35
|
+
export declare function detectRelease(env?: Readonly<RuntimeEnvironment>): string | null;
|