@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.
Files changed (50) hide show
  1. package/dist/_vendor/node-sdk/actionable-telemetry.d.ts +4 -0
  2. package/dist/_vendor/node-sdk/actionable-telemetry.js +32 -0
  3. package/dist/_vendor/node-sdk/aws-lambda.d.ts +19 -0
  4. package/dist/_vendor/node-sdk/aws-lambda.js +81 -0
  5. package/dist/_vendor/node-sdk/client.d.ts +54 -0
  6. package/dist/_vendor/node-sdk/client.js +479 -0
  7. package/dist/_vendor/node-sdk/delivery-stats.d.ts +40 -0
  8. package/dist/_vendor/node-sdk/delivery-stats.js +57 -0
  9. package/dist/_vendor/node-sdk/http.d.ts +22 -0
  10. package/dist/_vendor/node-sdk/http.js +139 -0
  11. package/dist/_vendor/node-sdk/index.d.ts +36 -0
  12. package/dist/_vendor/node-sdk/index.js +84 -0
  13. package/dist/_vendor/node-sdk/local-events/sink.d.ts +6 -0
  14. package/dist/_vendor/node-sdk/local-events/sink.js +43 -0
  15. package/dist/_vendor/node-sdk/trace-context.d.ts +8 -0
  16. package/dist/_vendor/node-sdk/trace-context.js +13 -0
  17. package/dist/_vendor/node-sdk/types.d.ts +65 -0
  18. package/dist/_vendor/node-sdk/types.js +1 -0
  19. package/dist/_vendor/sdk-runtime/batching/index.d.ts +42 -0
  20. package/dist/_vendor/sdk-runtime/batching/index.js +48 -0
  21. package/dist/_vendor/sdk-runtime/errors/headers.d.ts +6 -0
  22. package/dist/_vendor/sdk-runtime/errors/headers.js +44 -0
  23. package/dist/_vendor/sdk-runtime/errors/index.d.ts +95 -0
  24. package/dist/_vendor/sdk-runtime/errors/index.js +157 -0
  25. package/dist/_vendor/sdk-runtime/headers.d.ts +48 -0
  26. package/dist/_vendor/sdk-runtime/headers.js +67 -0
  27. package/dist/_vendor/sdk-runtime/platform.d.ts +33 -0
  28. package/dist/_vendor/sdk-runtime/platform.js +173 -0
  29. package/dist/_vendor/sdk-runtime/retry.d.ts +50 -0
  30. package/dist/_vendor/sdk-runtime/retry.js +104 -0
  31. package/dist/_vendor/sdk-runtime/runtime/actionable-error.d.ts +37 -0
  32. package/dist/_vendor/sdk-runtime/runtime/actionable-error.js +123 -0
  33. package/dist/_vendor/sdk-runtime/runtime/environment.d.ts +18 -0
  34. package/dist/_vendor/sdk-runtime/runtime/environment.js +81 -0
  35. package/dist/_vendor/sdk-runtime/runtime/public-sdk-runtime.d.ts +12 -0
  36. package/dist/_vendor/sdk-runtime/runtime/public-sdk-runtime.js +38 -0
  37. package/dist/_vendor/sdk-runtime/runtime/redaction.d.ts +14 -0
  38. package/dist/_vendor/sdk-runtime/runtime/redaction.js +120 -0
  39. package/dist/_vendor/sdk-runtime/runtime/release.d.ts +35 -0
  40. package/dist/_vendor/sdk-runtime/runtime/release.js +120 -0
  41. package/dist/_vendor/sdk-runtime/sampling/index.d.ts +43 -0
  42. package/dist/_vendor/sdk-runtime/sampling/index.js +70 -0
  43. package/dist/api-handler.d.ts +1 -1
  44. package/dist/api-handler.js +1 -1
  45. package/dist/client.d.ts +1 -1
  46. package/dist/client.js +1 -1
  47. package/dist/index.d.ts +2 -2
  48. package/dist/index.js +1 -1
  49. package/dist/middleware.js +1 -1
  50. package/package.json +1 -4
@@ -0,0 +1,40 @@
1
+ export type DeliveryOutcomeCategory = "sent" | "network_error" | "unauthorized" | "rate_limited" | "rejected" | "server_error";
2
+ export interface DeliveryOutcome {
3
+ readonly category: DeliveryOutcomeCategory;
4
+ readonly statusCode: number | null;
5
+ }
6
+ export interface DeliveryStats {
7
+ readonly queueDepth: number;
8
+ readonly queueHighWaterMark: number;
9
+ readonly attemptedBatches: number;
10
+ readonly attemptedEvents: number;
11
+ readonly sentBatches: number;
12
+ readonly sentEvents: number;
13
+ readonly retriedBatches: number;
14
+ readonly retriedEvents: number;
15
+ readonly droppedBatches: number;
16
+ readonly droppedEvents: number;
17
+ readonly lastOutcome: DeliveryOutcome | null;
18
+ readonly lastFlushDurationMs: number | null;
19
+ }
20
+ export declare class DeliveryStatsTracker {
21
+ private queueHighWaterMark;
22
+ private attemptedBatches;
23
+ private attemptedEvents;
24
+ private sentBatches;
25
+ private sentEvents;
26
+ private retriedBatches;
27
+ private retriedEvents;
28
+ private droppedBatches;
29
+ private droppedEvents;
30
+ private lastOutcome;
31
+ private lastFlushDurationMs;
32
+ recordQueueDepth(queueDepth: number): void;
33
+ recordAttempt(eventCount: number): void;
34
+ recordRetry(eventCount: number): void;
35
+ recordSent(eventCount: number, statusCode: number): void;
36
+ recordDropped(eventCount: number, category: Exclude<DeliveryOutcomeCategory, "sent">, statusCode: number | null): void;
37
+ recordFlushDuration(durationMs: number): void;
38
+ snapshot(queueDepth: number): DeliveryStats;
39
+ private setOutcome;
40
+ }
@@ -0,0 +1,57 @@
1
+ export class DeliveryStatsTracker {
2
+ queueHighWaterMark = 0;
3
+ attemptedBatches = 0;
4
+ attemptedEvents = 0;
5
+ sentBatches = 0;
6
+ sentEvents = 0;
7
+ retriedBatches = 0;
8
+ retriedEvents = 0;
9
+ droppedBatches = 0;
10
+ droppedEvents = 0;
11
+ lastOutcome = null;
12
+ lastFlushDurationMs = null;
13
+ recordQueueDepth(queueDepth) {
14
+ this.queueHighWaterMark = Math.max(this.queueHighWaterMark, queueDepth);
15
+ }
16
+ recordAttempt(eventCount) {
17
+ this.attemptedBatches++;
18
+ this.attemptedEvents += eventCount;
19
+ }
20
+ recordRetry(eventCount) {
21
+ this.retriedBatches++;
22
+ this.retriedEvents += eventCount;
23
+ }
24
+ recordSent(eventCount, statusCode) {
25
+ this.sentBatches++;
26
+ this.sentEvents += eventCount;
27
+ this.setOutcome("sent", statusCode);
28
+ }
29
+ recordDropped(eventCount, category, statusCode) {
30
+ this.droppedBatches++;
31
+ this.droppedEvents += eventCount;
32
+ this.setOutcome(category, statusCode);
33
+ }
34
+ recordFlushDuration(durationMs) {
35
+ this.lastFlushDurationMs = Math.max(0, durationMs);
36
+ }
37
+ snapshot(queueDepth) {
38
+ const lastOutcome = this.lastOutcome ? Object.freeze({ ...this.lastOutcome }) : null;
39
+ return Object.freeze({
40
+ queueDepth,
41
+ queueHighWaterMark: this.queueHighWaterMark,
42
+ attemptedBatches: this.attemptedBatches,
43
+ attemptedEvents: this.attemptedEvents,
44
+ sentBatches: this.sentBatches,
45
+ sentEvents: this.sentEvents,
46
+ retriedBatches: this.retriedBatches,
47
+ retriedEvents: this.retriedEvents,
48
+ droppedBatches: this.droppedBatches,
49
+ droppedEvents: this.droppedEvents,
50
+ lastOutcome,
51
+ lastFlushDurationMs: this.lastFlushDurationMs,
52
+ });
53
+ }
54
+ setOutcome(category, statusCode) {
55
+ this.lastOutcome = { category, statusCode };
56
+ }
57
+ }
@@ -0,0 +1,22 @@
1
+ import type { SquasherClient } from "./client.js";
2
+ import type { ErrorEvent, TraceContext } from "./types.js";
3
+ export type HttpHeadersLike = Headers | Record<string, number | string | string[] | undefined> | Iterable<readonly [string, string]>;
4
+ export interface HttpRequestLike {
5
+ method?: string;
6
+ url?: string;
7
+ originalUrl?: string;
8
+ headers?: HttpHeadersLike;
9
+ }
10
+ export interface HttpResponseLike {
11
+ status?: number;
12
+ statusCode?: number;
13
+ }
14
+ export interface HttpRequestContext {
15
+ request: HttpRequestLike;
16
+ response?: HttpResponseLike;
17
+ attributes?: ErrorEvent["attributes"];
18
+ extra?: ErrorEvent["extra"];
19
+ /** Incoming or ambient trace parent. */
20
+ trace?: Pick<TraceContext, "span_id" | "trace_id">;
21
+ }
22
+ export declare function withHttpRequest<T>(client: SquasherClient, context: HttpRequestContext, handler: () => T | Promise<T>): Promise<T>;
@@ -0,0 +1,139 @@
1
+ import { createRedactor } from "../sdk-runtime/runtime/public-sdk-runtime.js";
2
+ import { createTraceContext } from "./trace-context.js";
3
+ const redactRequestValue = createRedactor();
4
+ export async function withHttpRequest(client, context, handler) {
5
+ const startedAt = Date.now();
6
+ const trace = createTraceContext(context.trace);
7
+ let failure;
8
+ try {
9
+ return await handler();
10
+ }
11
+ catch (cause) {
12
+ failure = cause instanceof Error ? cause : new Error(String(cause));
13
+ throw cause;
14
+ }
15
+ finally {
16
+ const durationMs = Date.now() - startedAt;
17
+ const status = readStatus(context.response) ?? (failure ? 500 : 200);
18
+ const request = normalizeRequest(context.request);
19
+ const route = readRoute(context.attributes, request.url);
20
+ const outcome = status >= 400 || failure ? "failure" : "success";
21
+ try {
22
+ const measurements = [
23
+ { name: "http.server.request.count", unit: "1", value: 1 },
24
+ { name: "http.server.request.duration_ms", unit: "ms", value: durationMs },
25
+ ];
26
+ if (status >= 400) {
27
+ measurements.push({ name: "http.server.request.failure.count", unit: "1", value: 1 });
28
+ }
29
+ const event = {
30
+ attributes: {
31
+ "http.request.method": request.method ?? "GET",
32
+ "http.response.status_code": status,
33
+ "http.route": route,
34
+ "squasher.operation": route,
35
+ "squasher.outcome": outcome,
36
+ ...context.attributes,
37
+ },
38
+ event_name: "http.request.completed",
39
+ extra: {
40
+ ...context.extra,
41
+ squasher_http: { duration_ms: durationMs, status_code: status },
42
+ },
43
+ kind: failure ? "error" : "log",
44
+ level: failure || status >= 500 ? "error" : status >= 400 ? "warning" : "info",
45
+ measurements,
46
+ message: `http request ${outcome}`,
47
+ request,
48
+ timestamp: new Date(startedAt).toISOString(),
49
+ trace: {
50
+ ...trace,
51
+ duration_ms: durationMs,
52
+ span_kind: "server",
53
+ span_name: `${request.method ?? "GET"} ${route}`,
54
+ status: failure || status >= 500 ? "error" : "ok",
55
+ },
56
+ };
57
+ if (failure) {
58
+ event.stack = failure.stack;
59
+ event.type = failure.name;
60
+ }
61
+ await client.captureTelemetry(event);
62
+ }
63
+ catch {
64
+ // Telemetry must not change the request outcome.
65
+ }
66
+ }
67
+ }
68
+ function readRoute(attributes, url) {
69
+ const configured = attributes?.["http.route"];
70
+ return Object.prototype.toString.call(configured) === "[object String]"
71
+ ? String(configured)
72
+ : routeFromUrl(url);
73
+ }
74
+ function routeFromUrl(url) {
75
+ if (!url)
76
+ return "/";
77
+ try {
78
+ return new URL(url).pathname;
79
+ }
80
+ catch {
81
+ return url.split("?", 1)[0] || "/";
82
+ }
83
+ }
84
+ function normalizeRequest(request) {
85
+ return {
86
+ method: request.method,
87
+ url: sanitizeUrl(request.originalUrl ?? request.url),
88
+ headers: normalizeHeaders(request.headers),
89
+ };
90
+ }
91
+ function sanitizeUrl(url) {
92
+ if (!url)
93
+ return undefined;
94
+ try {
95
+ const parsed = new URL(url);
96
+ return `${parsed.origin}${parsed.pathname}`;
97
+ }
98
+ catch {
99
+ return url.split("?")[0];
100
+ }
101
+ }
102
+ function normalizeHeaders(headers) {
103
+ if (!headers)
104
+ return undefined;
105
+ const normalized = {};
106
+ if (headers instanceof Headers) {
107
+ headers.forEach((value, key) => {
108
+ normalized[key] = value;
109
+ });
110
+ return redactHeaderRecord(normalized);
111
+ }
112
+ if (Symbol.iterator in headers) {
113
+ for (const [key, value] of headers) {
114
+ normalized[key] = value;
115
+ }
116
+ return redactHeaderRecord(normalized);
117
+ }
118
+ for (const [key, value] of Object.entries(headers)) {
119
+ if (Array.isArray(value)) {
120
+ normalized[key] = value.join(", ");
121
+ }
122
+ else if (value !== undefined) {
123
+ normalized[key] = String(value);
124
+ }
125
+ }
126
+ return redactHeaderRecord(normalized);
127
+ }
128
+ function readStatus(response) {
129
+ return response?.status ?? response?.statusCode;
130
+ }
131
+ function redactHeaderRecord(headers) {
132
+ // SAFETY: createRedactor preserves object shape and replaces only leaf values.
133
+ const redacted = redactRequestValue(headers);
134
+ const result = {};
135
+ for (const [key, value] of Object.entries(redacted)) {
136
+ result[key] = String(value);
137
+ }
138
+ return result;
139
+ }
@@ -0,0 +1,36 @@
1
+ export { SquasherClient } from "./client.js";
2
+ export type { DeliveryOutcome, DeliveryOutcomeCategory, DeliveryStats } from "./delivery-stats.js";
3
+ export { withHttpRequest } from "./http.js";
4
+ export type { HttpRequestContext, HttpRequestLike, HttpResponseLike } from "./http.js";
5
+ export { withAwsLambda } from "./aws-lambda.js";
6
+ export type { AwsLambdaContextLike, AwsLambdaHandler, AwsLambdaOptions } from "./aws-lambda.js";
7
+ export { createSpanId, createTraceContext, createTraceId } from "./trace-context.js";
8
+ export { ActionableError, createActionableError, createRedactor, detectRelease, isValidRelease, parseRelease, } from "../sdk-runtime/runtime/public-sdk-runtime.js";
9
+ export type { ActionableErrorContext, ActionableErrorInternal, ActionableErrorOptions, ParsedRelease, Redactor, RedactorOptions, } from "../sdk-runtime/runtime/public-sdk-runtime.js";
10
+ export type { AnalyticsContext, Breadcrumb, CaptureErrorOptions, ErrorEvent, IngestBatchPayload, IngestResponse, JsonObject, JsonValue, Level, LocalEventSinkConfig, LlmContext, PageContext, RequestContext, SessionContext, SquasherConfig, StackFrame, TelemetryKind, TelemetryMeasurement, TelemetrySamplingConfig, ToolCallContext, TraceContext, UserContext, VisitorContext, } from "./types.js";
11
+ import { SquasherClient } from "./client.js";
12
+ import type { Breadcrumb, CaptureErrorOptions, ErrorEvent, JsonObject, Level, SquasherConfig, UserContext } from "./types.js";
13
+ /** Initialize the global Squasher client. Throws if called twice. */
14
+ export declare function init(config: SquasherConfig): SquasherClient;
15
+ /** Get the global client (throws if not initialized). */
16
+ export declare function getClient(): SquasherClient;
17
+ export declare function captureError(error: Error, extra?: JsonObject, options?: CaptureErrorOptions): Promise<string | null>;
18
+ export declare function captureMessage(message: string, level?: Level): Promise<string | null>;
19
+ export declare function captureTelemetry(event: ErrorEvent): Promise<string | null>;
20
+ export declare function track(eventName: string, properties?: JsonObject, context?: Partial<ErrorEvent>): Promise<string | null>;
21
+ export declare function identify(distinctId: string, traits?: JsonObject, context?: Partial<ErrorEvent>): Promise<string | null>;
22
+ export declare function page(name: string, properties?: JsonObject, context?: Partial<ErrorEvent>): Promise<string | null>;
23
+ export declare function screen(name: string, properties?: JsonObject, context?: Partial<ErrorEvent>): Promise<string | null>;
24
+ export declare function captureSpan(name: string, context?: Partial<ErrorEvent>): Promise<string | null>;
25
+ export declare function captureToolCall(name: string, context?: Partial<ErrorEvent>): Promise<string | null>;
26
+ export declare function captureGeneration(message: string, context?: Partial<ErrorEvent>): Promise<string | null>;
27
+ export declare function setUser(user: UserContext | undefined): void;
28
+ export declare function setTag(key: string, value: string): void;
29
+ export declare function setTags(tags: Record<string, string>): void;
30
+ export declare function addBreadcrumb(crumb: Omit<Breadcrumb, "timestamp">): void;
31
+ /** Read local delivery health without sending a telemetry event. */
32
+ export declare function getDeliveryStats(): import("./delivery-stats.js").DeliveryStats;
33
+ export declare function flush(timeoutMs?: number): Promise<void>;
34
+ /** Handles `uncaughtException` and `unhandledRejection` at the process level. */
35
+ export declare function installGlobalHandlers(): void;
36
+ export declare function close(): Promise<void>;
@@ -0,0 +1,84 @@
1
+ export { SquasherClient } from "./client.js";
2
+ export { withHttpRequest } from "./http.js";
3
+ export { withAwsLambda } from "./aws-lambda.js";
4
+ export { createSpanId, createTraceContext, createTraceId } from "./trace-context.js";
5
+ export { ActionableError, createActionableError, createRedactor, detectRelease, isValidRelease, parseRelease, } from "../sdk-runtime/runtime/public-sdk-runtime.js";
6
+ import { SquasherClient } from "./client.js";
7
+ const NODE_RUNTIME_SYMBOL = Symbol.for("@squasher-ai/node/runtime");
8
+ // SAFETY: this process-wide symbol is owned by this SDK and always receives NodeRuntimeRegistry.
9
+ const nodeRuntimeGlobal = globalThis;
10
+ const nodeRuntime = (nodeRuntimeGlobal[NODE_RUNTIME_SYMBOL] ??= { client: null });
11
+ /** Initialize the global Squasher client. Throws if called twice. */
12
+ export function init(config) {
13
+ if (nodeRuntime.client) {
14
+ throw new Error("squasher.init() called more than once. The SDK can only be initialized once.");
15
+ }
16
+ nodeRuntime.client = new SquasherClient(config);
17
+ return nodeRuntime.client;
18
+ }
19
+ /** Get the global client (throws if not initialized). */
20
+ export function getClient() {
21
+ if (!nodeRuntime.client) {
22
+ throw new Error("Squasher not initialized. Call init() first.");
23
+ }
24
+ return nodeRuntime.client;
25
+ }
26
+ export async function captureError(error, extra, options) {
27
+ return getClient().captureError(error, extra, options);
28
+ }
29
+ export async function captureMessage(message, level) {
30
+ return getClient().captureMessage(message, level);
31
+ }
32
+ export async function captureTelemetry(event) {
33
+ return getClient().captureTelemetry(event);
34
+ }
35
+ export async function track(eventName, properties, context) {
36
+ return getClient().track(eventName, properties, context);
37
+ }
38
+ export async function identify(distinctId, traits, context) {
39
+ return getClient().identify(distinctId, traits, context);
40
+ }
41
+ export async function page(name, properties, context) {
42
+ return getClient().page(name, properties, context);
43
+ }
44
+ export async function screen(name, properties, context) {
45
+ return getClient().screen(name, properties, context);
46
+ }
47
+ export async function captureSpan(name, context) {
48
+ return getClient().captureSpan(name, context);
49
+ }
50
+ export async function captureToolCall(name, context) {
51
+ return getClient().captureToolCall(name, context);
52
+ }
53
+ export async function captureGeneration(message, context) {
54
+ return getClient().captureGeneration(message, context);
55
+ }
56
+ export function setUser(user) {
57
+ getClient().setUser(user);
58
+ }
59
+ export function setTag(key, value) {
60
+ getClient().setTag(key, value);
61
+ }
62
+ export function setTags(tags) {
63
+ getClient().setTags(tags);
64
+ }
65
+ export function addBreadcrumb(crumb) {
66
+ getClient().addBreadcrumb(crumb);
67
+ }
68
+ /** Read local delivery health without sending a telemetry event. */
69
+ export function getDeliveryStats() {
70
+ return getClient().getDeliveryStats();
71
+ }
72
+ export async function flush(timeoutMs) {
73
+ return getClient().flush(timeoutMs);
74
+ }
75
+ /** Handles `uncaughtException` and `unhandledRejection` at the process level. */
76
+ export function installGlobalHandlers() {
77
+ getClient().installGlobalHandlers();
78
+ }
79
+ export async function close() {
80
+ if (nodeRuntime.client) {
81
+ await nodeRuntime.client.close();
82
+ nodeRuntime.client = null;
83
+ }
84
+ }
@@ -0,0 +1,6 @@
1
+ import type { ErrorEvent, LocalEventSinkConfig } from "../types.js";
2
+ export interface LocalEventSink {
3
+ write(event: ErrorEvent): void;
4
+ }
5
+ export declare function createLocalEventSink(config: LocalEventSinkConfig | undefined): LocalEventSink | undefined;
6
+ export declare function normalizeLocalEventSinkConfig(config: boolean | LocalEventSinkConfig | undefined): LocalEventSinkConfig | undefined;
@@ -0,0 +1,43 @@
1
+ import { mkdir, appendFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { createRedactor } from "../../sdk-runtime/runtime/public-sdk-runtime.js";
4
+ export function createLocalEventSink(config) {
5
+ if (!config?.enabled)
6
+ return undefined;
7
+ const directory = config.directory ?? join(process.cwd(), ".squasher", "events");
8
+ const redact = createRedactor({
9
+ keyMasks: ["x-squasher-key", ...(config.redactKeys ?? [])],
10
+ mask: config.redactionText,
11
+ });
12
+ return {
13
+ write(event) {
14
+ const line = safeSerializeRedacted(event, redact);
15
+ if (!line)
16
+ return;
17
+ const path = join(directory, `${new Date().toISOString().slice(0, 10)}.jsonl`);
18
+ void mkdir(directory, { recursive: true })
19
+ .then(() => appendFile(path, `${line}\n`, "utf8"))
20
+ .catch(() => { });
21
+ },
22
+ };
23
+ }
24
+ export function normalizeLocalEventSinkConfig(config) {
25
+ if (config === undefined || config === false)
26
+ return undefined;
27
+ if (config === true)
28
+ return { enabled: true };
29
+ return config;
30
+ }
31
+ function safeSerializeRedacted(event, redact) {
32
+ try {
33
+ return serializeRedacted(event, redact);
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ }
39
+ function serializeRedacted(event, redact) {
40
+ const plain = JSON.parse(JSON.stringify(event));
41
+ const redacted = redact(plain);
42
+ return JSON.stringify(redacted);
43
+ }
@@ -0,0 +1,8 @@
1
+ import type { TraceContext } from "./types.js";
2
+ export declare function createTraceId(): string;
3
+ export declare function createSpanId(): string;
4
+ export declare function createTraceContext(parent?: Pick<TraceContext, "span_id" | "trace_id">): {
5
+ parent_span_id: string | undefined;
6
+ span_id: string;
7
+ trace_id: string;
8
+ };
@@ -0,0 +1,13 @@
1
+ export function createTraceId() {
2
+ return crypto.randomUUID().replaceAll("-", "");
3
+ }
4
+ export function createSpanId() {
5
+ return crypto.randomUUID().replaceAll("-", "").slice(0, 16);
6
+ }
7
+ export function createTraceContext(parent) {
8
+ return {
9
+ parent_span_id: parent?.span_id,
10
+ span_id: createSpanId(),
11
+ trace_id: parent?.trace_id ?? createTraceId(),
12
+ };
13
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Wire protocol types matching sdk-spec/protocol.schema.json.
3
+ * Config types matching sdk-spec/config.schema.json.
4
+ */
5
+ import type { AnalyticsContext, Breadcrumb as ApiBreadcrumb, IngestEvent, IngestResponse, JsonObject, JsonValue, Level, LlmContext, PageContext, RequestContext, SessionContext, StackFrame, TelemetryKind, TelemetryMeasurement, TelemetrySamplingConfig, ToolCallContext, TraceContext, UserContext, VisitorContext } from "../telemetry-contract/sdk/public-contract.js";
6
+ export interface SquasherConfig {
7
+ /** API key (sq_pk_...). Required. */
8
+ apiKey: string;
9
+ /** Project ID. Required. */
10
+ projectId: string;
11
+ /** Public edge ingestion endpoint. Default: "https://ingest.squasher.ai" */
12
+ endpoint?: string;
13
+ /** Environment tag (production, staging, development). */
14
+ environment?: string;
15
+ /** Release/version tag. */
16
+ release?: string;
17
+ /** Enable debug logging. Default: false. */
18
+ debug?: boolean;
19
+ /** Outcome-aware, deterministic sampling policy. */
20
+ sampling?: TelemetrySamplingConfig;
21
+ /**
22
+ * Hook invoked before every event is sent.
23
+ * Return the event (possibly modified) to send, or null to drop.
24
+ * Applied to ALL captures (errors AND messages) per spec.
25
+ * The RETURN VALUE is used (not the original).
26
+ */
27
+ beforeSend?: (event: ErrorEvent) => ErrorEvent | null;
28
+ /** Max breadcrumbs to retain. Default: 50. */
29
+ maxBreadcrumbs?: number;
30
+ /** Flush when this many events are buffered. Default: 25. */
31
+ batchSize?: number;
32
+ /** Max ms between automatic flushes. Default: 5000. */
33
+ flushIntervalMs?: number;
34
+ /** Max retry attempts on transient failures. Default: 3. */
35
+ maxRetries?: number;
36
+ /** OTel resource attributes merged into every outbound event. */
37
+ resourceAttributes?: Record<string, string>;
38
+ /**
39
+ * Opt-in local event artifact for agent debugging. Writes redacted NDJSON to
40
+ * `.squasher/events/YYYY-MM-DD.jsonl` by default.
41
+ */
42
+ localEventSink?: boolean | LocalEventSinkConfig;
43
+ }
44
+ export interface LocalEventSinkConfig {
45
+ enabled: boolean;
46
+ directory?: string;
47
+ redactKeys?: string[];
48
+ redactionText?: string;
49
+ }
50
+ export interface CaptureErrorOptions {
51
+ attributes?: ErrorEvent["attributes"];
52
+ level?: Level;
53
+ request?: RequestContext;
54
+ }
55
+ export type { AnalyticsContext, IngestResponse, JsonObject, JsonValue, Level, LlmContext, PageContext, RequestContext, SessionContext, StackFrame, TelemetryKind, TelemetryMeasurement, TelemetrySamplingConfig, ToolCallContext, TraceContext, UserContext, VisitorContext, };
56
+ export type Breadcrumb = Omit<ApiBreadcrumb, "data"> & {
57
+ data?: JsonObject;
58
+ };
59
+ export type ErrorEvent = Omit<IngestEvent, "breadcrumbs" | "extra"> & {
60
+ breadcrumbs?: Breadcrumb[];
61
+ extra?: JsonObject;
62
+ };
63
+ export interface IngestBatchPayload {
64
+ events: ErrorEvent[];
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Batching primitives used by Squasher SDKs.
3
+ *
4
+ * The 1 MB default keeps request bodies bounded while amortizing per-request
5
+ * overhead. Callers may choose a lower limit when their runtime requires one.
6
+ */
7
+ /** Maximum serialized SDK batch body, in bytes. */
8
+ export declare const MAX_BATCH_BODY_BYTES = 1000000;
9
+ export interface BuildBatchesOptions<TItem> {
10
+ /**
11
+ * Static JSON immediately before the comma-separated serialized items.
12
+ * Keeping the envelope split lets the builder serialize each item once
13
+ * instead of repeatedly serializing a growing candidate array.
14
+ */
15
+ batchPrefix: string;
16
+ /** Static JSON immediately after the serialized items. */
17
+ batchSuffix: string;
18
+ /**
19
+ * Serialize a single item (no wrapper). Called when a batch of size 1 is
20
+ * flushed. Lets SDKs send `{...event}` instead of `{events: [{...}]}` for
21
+ * single-event flushes — half the bytes for the common case.
22
+ */
23
+ serializeSingle: (item: TItem) => string;
24
+ /** Override the default 1_000_000-byte ceiling (for tests). */
25
+ maxBatchBodyBytes?: number;
26
+ }
27
+ export interface BuiltBatch {
28
+ /** The exact request body (already serialized). */
29
+ body: string;
30
+ /** Number of items in this batch. */
31
+ itemCount: number;
32
+ }
33
+ /**
34
+ * Greedy batching: walk the items in order, adding each to the current batch
35
+ * unless doing so would exceed `MAX_BATCH_BODY_BYTES`. When it would, flush
36
+ * the current batch and start a new one with the offending item.
37
+ *
38
+ * An item that is itself larger than the limit becomes a single-item batch
39
+ * regardless — callers are expected to drop or split oversized items at a
40
+ * higher layer; this function does not silently drop data.
41
+ */
42
+ export declare function buildBatches<TItem>(items: TItem[], options: BuildBatchesOptions<TItem>): BuiltBatch[];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Batching primitives used by Squasher SDKs.
3
+ *
4
+ * The 1 MB default keeps request bodies bounded while amortizing per-request
5
+ * overhead. Callers may choose a lower limit when their runtime requires one.
6
+ */
7
+ /** Maximum serialized SDK batch body, in bytes. */
8
+ export const MAX_BATCH_BODY_BYTES = 1_000_000;
9
+ const BODY_ENCODER = new TextEncoder();
10
+ /**
11
+ * Greedy batching: walk the items in order, adding each to the current batch
12
+ * unless doing so would exceed `MAX_BATCH_BODY_BYTES`. When it would, flush
13
+ * the current batch and start a new one with the offending item.
14
+ *
15
+ * An item that is itself larger than the limit becomes a single-item batch
16
+ * regardless — callers are expected to drop or split oversized items at a
17
+ * higher layer; this function does not silently drop data.
18
+ */
19
+ export function buildBatches(items, options) {
20
+ const max = options.maxBatchBodyBytes ?? MAX_BATCH_BODY_BYTES;
21
+ const batches = [];
22
+ let current = [];
23
+ const envelopeBytes = BODY_ENCODER.encode(options.batchPrefix).length +
24
+ BODY_ENCODER.encode(options.batchSuffix).length;
25
+ let currentBodyBytes = envelopeBytes;
26
+ const flush = () => {
27
+ if (current.length === 0)
28
+ return;
29
+ const body = current.length === 1
30
+ ? current[0]
31
+ : `${options.batchPrefix}${current.join(",")}${options.batchSuffix}`;
32
+ batches.push({ body, itemCount: current.length });
33
+ current = [];
34
+ currentBodyBytes = envelopeBytes;
35
+ };
36
+ for (const item of items) {
37
+ const serialized = options.serializeSingle(item);
38
+ const serializedBytes = BODY_ENCODER.encode(serialized).length;
39
+ const separatorBytes = current.length === 0 ? 0 : 1;
40
+ if (current.length > 0 && currentBodyBytes + separatorBytes + serializedBytes > max) {
41
+ flush();
42
+ }
43
+ currentBodyBytes += (current.length === 0 ? 0 : 1) + serializedBytes;
44
+ current.push(serialized);
45
+ }
46
+ flush();
47
+ return batches;
48
+ }
@@ -0,0 +1,6 @@
1
+ export type HeaderMap = Record<string, string>;
2
+ export interface HeaderLike {
3
+ get(name: string): string | null;
4
+ }
5
+ export type HeaderSource = HeaderLike | HeaderMap;
6
+ export declare function readHeader(headers: HeaderSource | undefined, name: string): string | undefined;
@@ -0,0 +1,44 @@
1
+ export function readHeader(headers, name) {
2
+ if (!headers)
3
+ return undefined;
4
+ const get = readProperty(headers, "get");
5
+ if (isHeaderGetter(get)) {
6
+ try {
7
+ const value = get.call(headers, name);
8
+ return Object.prototype.toString.call(value) === "[object String]"
9
+ ? String(value)
10
+ : undefined;
11
+ }
12
+ catch {
13
+ return undefined;
14
+ }
15
+ }
16
+ const normalizedName = name.toLowerCase();
17
+ for (const [key, value] of Object.entries(Object(headers))) {
18
+ if (key.toLowerCase() === normalizedName &&
19
+ Object.prototype.toString.call(value) === "[object String]") {
20
+ return String(value);
21
+ }
22
+ }
23
+ return undefined;
24
+ }
25
+ function isHeaderGetter(value) {
26
+ return Object.prototype.toString.call(value) === "[object Function]";
27
+ }
28
+ function readProperty(owner, key) {
29
+ try {
30
+ const target = Object(owner);
31
+ let cursor = target;
32
+ while (cursor) {
33
+ const descriptor = Object.getOwnPropertyDescriptor(cursor, key);
34
+ if (descriptor) {
35
+ return "value" in descriptor ? descriptor.value : descriptor.get?.call(target);
36
+ }
37
+ cursor = Object.getPrototypeOf(cursor);
38
+ }
39
+ return undefined;
40
+ }
41
+ catch {
42
+ return undefined;
43
+ }
44
+ }