@fixflow/sdk 0.1.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 ADDED
@@ -0,0 +1,143 @@
1
+ # FixFlow SDK
2
+
3
+ Lightweight error capture SDK for Node.js and browser environments.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @fixflow/sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import fixflow from "@fixflow/sdk";
15
+
16
+ fixflow.init("PROJECT_KEY");
17
+ ```
18
+
19
+ The SDK posts to the FixFlow cloud ingest URL by default. You do **not** need to pass an `endpoint` for normal use.
20
+
21
+ The SDK auto-captures uncaught errors and unhandled promise rejections after initialization.
22
+
23
+ ## Usage by environment
24
+
25
+ **API request tracking:** In Node.js, outbound HTTP monitoring uses Axios (`trackAxios`). In the browser you can use `trackFetch`, Axios, or both.
26
+
27
+ ### Browser / frontend
28
+
29
+ Initialize in your client entry file (for example `main.ts` or `index.tsx`) **as early as possible** so startup errors are captured:
30
+
31
+ ```ts
32
+ import fixflow from "@fixflow/sdk";
33
+
34
+ fixflow.init("PROJECT_KEY", {
35
+ trackFetch: true, // optional: instruments window.fetch (browser only)
36
+ });
37
+ ```
38
+
39
+ Use `captureError` or `capture` in `catch` blocks for handled errors (see [Capturing errors manually](#capturing-errors-manually)).
40
+
41
+ `trackFetch` instruments **`window.fetch` only** and does not patch Node’s global `fetch`. For all options, see [`init` Options](#init-options) below.
42
+
43
+ ### React
44
+
45
+ Prefer calling `init` from your **entry file** before `createRoot(...).render(...)` when the project key is available there.
46
+
47
+ If the key only exists on the client (for example a `NEXT_PUBLIC_*` variable), use a **once-only** `useEffect` with an empty dependency array. Do **not** call `init` in the component render body:
48
+
49
+ ```tsx
50
+ import { useEffect } from "react";
51
+ import fixflow from "@fixflow/sdk";
52
+
53
+ export function FixFlowInit() {
54
+ useEffect(() => {
55
+ const key = process.env.NEXT_PUBLIC_FIXFLOW_PROJECT_KEY;
56
+ if (key) {
57
+ fixflow.init(key, { trackFetch: true });
58
+ }
59
+ }, []);
60
+
61
+ return null;
62
+ }
63
+ ```
64
+
65
+ Mount that component once near the root of your tree. Next.js App Router users can wrap it in a small client component used from the root layout.
66
+
67
+ ### Node.js
68
+
69
+ Call `init` **once at process startup** (for example at the top of `server.ts`, before creating or listening on your HTTP server):
70
+
71
+ ```ts
72
+ import axios from "axios";
73
+ import fixflow from "@fixflow/sdk";
74
+
75
+ fixflow.init("PROJECT_KEY", {
76
+ trackAxios: true,
77
+ axios,
78
+ slowThreshold: 1000,
79
+ });
80
+ ```
81
+
82
+ Do **not** rely on `trackFetch` for Node in the current SDK; use `trackAxios` and pass the same `axios` instance your app uses. See [Axios option (important)](#axios-option-important).
83
+
84
+ ## `init` Options
85
+
86
+ Typical setup combines browser `trackFetch` with server Axios tracking and passes your app's Axios instance when you use Axios on the server:
87
+
88
+ ```ts
89
+ import axios from "axios";
90
+ import fixflow from "@fixflow/sdk";
91
+
92
+ fixflow.init("PROJECT_KEY", {
93
+ trackFetch: true,
94
+ trackAxios: true,
95
+ slowThreshold: 1000,
96
+ axios,
97
+ });
98
+ ```
99
+
100
+ | Option | Type | Default | Description |
101
+ | --- | --- | --- | --- |
102
+ | `trackFetch` | `boolean` | `false` | Enables API tracking for `window.fetch` (browser). |
103
+ | `trackAxios` | `boolean` | `false` | Enables API tracking for Axios via interceptors. |
104
+ | `slowThreshold` | `number` | `1000` | Request duration threshold in ms used for "slow API" events. Values below `0` are clamped to `0`. |
105
+ | `axios` | `AxiosStatic` | `undefined` | Axios instance to instrument. Recommended for Node apps when `trackAxios` is enabled. |
106
+
107
+ At init, the SDK requests `GET …/sdk/config/:projectKey` on the same API host as ingest (for the default cloud URL, that is `https://api.fixflow.ai/sdk/config/:projectKey`). It applies `apiTrackingEnabled` and `trackInDevelopment` from the project when the response is valid, and otherwise falls back to your `trackFetch` / `trackAxios` flags.
108
+
109
+
110
+ ## Axios option (important)
111
+
112
+ In Node/npm projects, `@fixflow/sdk` can have a different installed Axios copy than your app.
113
+ If that happens, interceptors attached by the SDK will not see requests made by your app's Axios import.
114
+
115
+ Pass your app's Axios instance so API events are tracked from the client your code actually uses:
116
+
117
+ ```ts
118
+ import axios from "axios";
119
+ import fixflow from "@fixflow/sdk";
120
+
121
+ fixflow.init("PROJECT_KEY", {
122
+ trackAxios: true,
123
+ axios,
124
+ });
125
+ ```
126
+
127
+ ## Capturing errors manually
128
+
129
+ ```ts
130
+ try {
131
+ throw new Error("Something failed");
132
+ } catch (err) {
133
+ fixflow.captureError(err as Error);
134
+ }
135
+ ```
136
+
137
+ You can also call the async `capture` method directly:
138
+
139
+ ```ts
140
+ import fixflow from "@fixflow/sdk";
141
+
142
+ await fixflow.capture(new Error("Manual capture"));
143
+ ```
@@ -0,0 +1,9 @@
1
+ import type { Runtime } from "./types.js";
2
+ export declare function isNode(): boolean;
3
+ export declare function getRuntime(): Runtime;
4
+ export declare function extractFile(stack?: string): string | undefined;
5
+ export declare function extractLine(stack?: string): number | undefined;
6
+ export declare function capture(error: Error): Promise<void>;
7
+ export declare function setupApiTracking(): void;
8
+ export declare function setupGlobalHandlers(): void;
9
+ export declare function registerAutoCapture(): void;
@@ -0,0 +1,396 @@
1
+ import { sendError } from "./sender.js";
2
+ import { getSlowThreshold, getTrackingAxios, shouldTrackAxios, shouldTrackFetch } from "./core.js";
3
+ import axios from "axios";
4
+ let autoCaptureRegistered = false;
5
+ let fetchTrackingRegistered = false;
6
+ let axiosTrackingRegistered = false;
7
+ function isReactNative() {
8
+ try {
9
+ const nav = globalThis.navigator;
10
+ return nav?.product === "ReactNative";
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ export function isNode() {
17
+ try {
18
+ const nodeProcess = globalThis.process;
19
+ return typeof nodeProcess !== "undefined" && nodeProcess.release?.name === "node";
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ export function getRuntime() {
26
+ try {
27
+ if (isReactNative()) {
28
+ return "react-native";
29
+ }
30
+ return isNode() ? "node" : "browser";
31
+ }
32
+ catch {
33
+ return "node";
34
+ }
35
+ }
36
+ export function extractFile(stack) {
37
+ try {
38
+ if (!stack) {
39
+ return undefined;
40
+ }
41
+ const match = stack.match(/(?:at\s+.*\()?(.+):\d+:\d+\)?/);
42
+ return match?.[1];
43
+ }
44
+ catch {
45
+ return undefined;
46
+ }
47
+ }
48
+ export function extractLine(stack) {
49
+ try {
50
+ if (!stack) {
51
+ return undefined;
52
+ }
53
+ const match = stack.match(/:(\d+):\d+\)?/);
54
+ if (!match?.[1]) {
55
+ return undefined;
56
+ }
57
+ const line = Number(match[1]);
58
+ return Number.isFinite(line) ? line : undefined;
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ }
64
+ function getEnv() {
65
+ try {
66
+ const nodeProcess = globalThis.process;
67
+ if (nodeProcess?.env?.NODE_ENV) {
68
+ return nodeProcess.env.NODE_ENV;
69
+ }
70
+ if (isNode()) {
71
+ return "production";
72
+ }
73
+ }
74
+ catch {
75
+ // noop
76
+ }
77
+ return "unknown";
78
+ }
79
+ function getNodeVersion() {
80
+ try {
81
+ const nodeProcess = globalThis.process;
82
+ return nodeProcess?.version;
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ }
88
+ function getNodePlatform() {
89
+ try {
90
+ const nodeProcess = globalThis.process;
91
+ return nodeProcess?.platform;
92
+ }
93
+ catch {
94
+ return undefined;
95
+ }
96
+ }
97
+ function markCaptured(error) {
98
+ try {
99
+ if (error.__fixflow) {
100
+ return true;
101
+ }
102
+ error.__fixflow = true;
103
+ return false;
104
+ }
105
+ catch {
106
+ return true;
107
+ }
108
+ }
109
+ function normalizeError(input) {
110
+ try {
111
+ if (input instanceof Error) {
112
+ return input;
113
+ }
114
+ return new Error(typeof input === "string" ? input : "Unknown error");
115
+ }
116
+ catch {
117
+ return new Error("Unknown error");
118
+ }
119
+ }
120
+ function normalizeRejectionReason(reason) {
121
+ try {
122
+ if (reason instanceof Error) {
123
+ return reason;
124
+ }
125
+ return new Error(JSON.stringify(reason));
126
+ }
127
+ catch {
128
+ return new Error("Unhandled promise rejection");
129
+ }
130
+ }
131
+ export async function capture(error) {
132
+ try {
133
+ const safeError = normalizeError(error);
134
+ if (markCaptured(safeError)) {
135
+ return;
136
+ }
137
+ await sendError({
138
+ message: safeError.message || "Unknown error",
139
+ stack: safeError.stack,
140
+ file: extractFile(safeError.stack),
141
+ line: extractLine(safeError.stack),
142
+ runtime: getRuntime(),
143
+ env: getEnv(),
144
+ nodeVersion: isNode() ? getNodeVersion() : undefined,
145
+ platform: isNode() ? getNodePlatform() : undefined,
146
+ type: "error",
147
+ });
148
+ }
149
+ catch {
150
+ // SDK should never throw
151
+ }
152
+ }
153
+ async function sendApiEvent(data) {
154
+ try {
155
+ await sendError({
156
+ message: data.message,
157
+ runtime: getRuntime(),
158
+ env: getEnv(),
159
+ nodeVersion: isNode() ? getNodeVersion() : undefined,
160
+ platform: isNode() ? getNodePlatform() : undefined,
161
+ type: "api",
162
+ url: data.url,
163
+ status: data.status,
164
+ duration: data.duration,
165
+ });
166
+ }
167
+ catch {
168
+ // SDK should never throw
169
+ }
170
+ }
171
+ export function setupApiTracking() {
172
+ try {
173
+ if (shouldTrackFetch() && !fetchTrackingRegistered && typeof window !== "undefined" && typeof window.fetch === "function") {
174
+ fetchTrackingRegistered = true;
175
+ const originalFetch = window.fetch.bind(window);
176
+ window.fetch = async (...args) => {
177
+ const start = Date.now();
178
+ let requestUrl = "";
179
+ try {
180
+ const input = args[0];
181
+ requestUrl = typeof input === "string" ? input : input instanceof Request ? input.url : "";
182
+ }
183
+ catch {
184
+ // noop
185
+ }
186
+ try {
187
+ const response = await originalFetch(...args);
188
+ const duration = Date.now() - start;
189
+ const threshold = getSlowThreshold();
190
+ if (!response.ok || duration > threshold) {
191
+ void sendApiEvent({
192
+ message: !response.ok ? "API request failed" : "API request slow",
193
+ url: requestUrl,
194
+ status: response.status,
195
+ duration,
196
+ });
197
+ }
198
+ return response;
199
+ }
200
+ catch (error) {
201
+ const duration = Date.now() - start;
202
+ void sendApiEvent({
203
+ message: "API request error",
204
+ url: requestUrl,
205
+ duration,
206
+ });
207
+ throw error;
208
+ }
209
+ };
210
+ }
211
+ if (shouldTrackAxios() && !axiosTrackingRegistered) {
212
+ axiosTrackingRegistered = true;
213
+ const axiosClient = getTrackingAxios() ?? axios;
214
+ axiosClient.interceptors.request.use((config) => {
215
+ try {
216
+ const meta = config.__fixflowMeta || {
217
+ startTime: Date.now(),
218
+ };
219
+ config.__fixflowMeta = meta;
220
+ }
221
+ catch {
222
+ // noop
223
+ }
224
+ return config;
225
+ });
226
+ axiosClient.interceptors.response.use((response) => {
227
+ try {
228
+ const startTime = response.config.__fixflowMeta?.startTime ||
229
+ Date.now();
230
+ const duration = Date.now() - startTime;
231
+ const threshold = getSlowThreshold();
232
+ let requestUrl = "";
233
+ try {
234
+ requestUrl = axiosClient.getUri(response.config);
235
+ }
236
+ catch {
237
+ requestUrl = response.config?.url || "";
238
+ }
239
+ if (!response.status || response.status >= 400 || duration > threshold) {
240
+ void sendApiEvent({
241
+ message: response.status >= 400 ? "API request failed" : "API request slow",
242
+ url: requestUrl || response.config?.url,
243
+ status: response.status,
244
+ duration,
245
+ });
246
+ }
247
+ }
248
+ catch {
249
+ // noop
250
+ }
251
+ return response;
252
+ }, (error) => {
253
+ try {
254
+ const config = (error?.config || {});
255
+ const startTime = config.__fixflowMeta?.startTime || Date.now();
256
+ const duration = Date.now() - startTime;
257
+ let requestUrl = "";
258
+ try {
259
+ requestUrl = axiosClient.getUri(config);
260
+ }
261
+ catch {
262
+ requestUrl = config.url || "";
263
+ }
264
+ void sendApiEvent({
265
+ message: "API request error",
266
+ url: requestUrl || config.url,
267
+ status: error?.response?.status,
268
+ duration,
269
+ });
270
+ }
271
+ catch {
272
+ // noop
273
+ }
274
+ return Promise.reject(error);
275
+ });
276
+ }
277
+ }
278
+ catch {
279
+ // SDK should never throw
280
+ }
281
+ }
282
+ export function setupGlobalHandlers() {
283
+ try {
284
+ if (autoCaptureRegistered) {
285
+ return;
286
+ }
287
+ autoCaptureRegistered = true;
288
+ const nodeProcess = globalThis.process;
289
+ if (isNode() && typeof nodeProcess?.on === "function") {
290
+ nodeProcess.on("uncaughtException", (error) => {
291
+ try {
292
+ void capture(normalizeError(error));
293
+ }
294
+ catch {
295
+ // noop
296
+ }
297
+ });
298
+ nodeProcess.on("unhandledRejection", (reason) => {
299
+ try {
300
+ void capture(normalizeRejectionReason(reason));
301
+ }
302
+ catch {
303
+ // noop
304
+ }
305
+ });
306
+ nodeProcess.on("beforeExit", (code) => {
307
+ try {
308
+ console.debug("[FixFlow] process beforeExit", code);
309
+ }
310
+ catch {
311
+ // noop
312
+ }
313
+ });
314
+ nodeProcess.on("exit", (code) => {
315
+ try {
316
+ console.debug("[FixFlow] process exit", code);
317
+ }
318
+ catch {
319
+ // noop
320
+ }
321
+ });
322
+ }
323
+ if (isReactNative()) {
324
+ const rnGlobal = globalThis;
325
+ if (typeof rnGlobal.ErrorUtils?.setGlobalHandler === "function") {
326
+ const previousHandler = typeof rnGlobal.ErrorUtils.getGlobalHandler === "function"
327
+ ? rnGlobal.ErrorUtils.getGlobalHandler()
328
+ : undefined;
329
+ rnGlobal.ErrorUtils.setGlobalHandler((error, isFatal) => {
330
+ try {
331
+ void capture(normalizeError(error));
332
+ }
333
+ catch {
334
+ // noop
335
+ }
336
+ try {
337
+ if (typeof previousHandler === "function") {
338
+ previousHandler(error, isFatal);
339
+ }
340
+ }
341
+ catch {
342
+ // noop
343
+ }
344
+ });
345
+ }
346
+ return;
347
+ }
348
+ if (typeof window !== "undefined") {
349
+ const previousOnError = window.onerror;
350
+ const previousUnhandledRejection = window.onunhandledrejection;
351
+ window.onerror = (message, source, lineno, _colno, error) => {
352
+ try {
353
+ const normalized = normalizeError(error ?? message);
354
+ if (!normalized.stack && source) {
355
+ normalized.stack = `at ${source}:${lineno || 0}:0`;
356
+ }
357
+ void capture(normalized);
358
+ }
359
+ catch {
360
+ // noop
361
+ }
362
+ try {
363
+ if (typeof previousOnError === "function") {
364
+ return previousOnError(message, source, lineno, _colno, error);
365
+ }
366
+ }
367
+ catch {
368
+ // noop
369
+ }
370
+ return false;
371
+ };
372
+ window.onunhandledrejection = (event) => {
373
+ try {
374
+ void capture(normalizeRejectionReason(event.reason));
375
+ }
376
+ catch {
377
+ // noop
378
+ }
379
+ try {
380
+ if (typeof previousUnhandledRejection === "function") {
381
+ previousUnhandledRejection.call(window, event);
382
+ }
383
+ }
384
+ catch {
385
+ // noop
386
+ }
387
+ };
388
+ }
389
+ }
390
+ catch {
391
+ // SDK should never throw
392
+ }
393
+ }
394
+ export function registerAutoCapture() {
395
+ setupGlobalHandlers();
396
+ }
package/dist/core.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { AxiosStatic } from "axios";
2
+ import type { InitOptions } from "./types.js";
3
+ export declare function initConfig(key: string, options?: InitOptions): void;
4
+ export declare function getTrackingAxios(): AxiosStatic | undefined;
5
+ export declare function getProjectKey(): string;
6
+ export declare function getEndpoint(): string;
7
+ export declare function isInitialized(): boolean;
8
+ export declare function shouldTrackFetch(): boolean;
9
+ export declare function shouldTrackAxios(): boolean;
10
+ export declare function getSlowThreshold(): number;
11
+ export declare function syncRemoteApiTrackingConfig(): Promise<void>;
package/dist/core.js ADDED
@@ -0,0 +1,135 @@
1
+ const DEFAULT_ENDPOINT = "https://api.tryfixflow.com/sdk/error";
2
+ const DEFAULT_SLOW_THRESHOLD = 1000;
3
+ let projectKey = "";
4
+ let endpoint = DEFAULT_ENDPOINT;
5
+ let trackFetch = false;
6
+ let trackAxios = false;
7
+ let slowThreshold = DEFAULT_SLOW_THRESHOLD;
8
+ let trackingAxios;
9
+ let remoteApiTrackingEnabled = null;
10
+ let remoteTrackInDevelopment = false;
11
+ export function initConfig(key, options) {
12
+ try {
13
+ if (typeof key !== "string" || key.trim() === "") {
14
+ return;
15
+ }
16
+ projectKey = key;
17
+ endpoint = options?.endpoint || DEFAULT_ENDPOINT;
18
+ trackFetch = Boolean(options?.trackFetch);
19
+ trackAxios = Boolean(options?.trackAxios);
20
+ slowThreshold =
21
+ typeof options?.slowThreshold === "number" && Number.isFinite(options.slowThreshold)
22
+ ? Math.max(0, options.slowThreshold)
23
+ : DEFAULT_SLOW_THRESHOLD;
24
+ trackingAxios = options?.axios;
25
+ }
26
+ catch {
27
+ // SDK should never throw
28
+ }
29
+ }
30
+ export function getTrackingAxios() {
31
+ try {
32
+ return trackingAxios;
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
38
+ export function getProjectKey() {
39
+ try {
40
+ return projectKey;
41
+ }
42
+ catch {
43
+ return "";
44
+ }
45
+ }
46
+ export function getEndpoint() {
47
+ try {
48
+ return endpoint || DEFAULT_ENDPOINT;
49
+ }
50
+ catch {
51
+ return DEFAULT_ENDPOINT;
52
+ }
53
+ }
54
+ export function isInitialized() {
55
+ try {
56
+ return Boolean(projectKey);
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ export function shouldTrackFetch() {
63
+ try {
64
+ const enabled = remoteApiTrackingEnabled !== null ? remoteApiTrackingEnabled : trackFetch;
65
+ if (!enabled)
66
+ return false;
67
+ if (isDevelopmentEnvironment() && !remoteTrackInDevelopment)
68
+ return false;
69
+ return true;
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ export function shouldTrackAxios() {
76
+ try {
77
+ const enabled = remoteApiTrackingEnabled !== null ? remoteApiTrackingEnabled : trackAxios;
78
+ if (!enabled)
79
+ return false;
80
+ if (isDevelopmentEnvironment() && !remoteTrackInDevelopment)
81
+ return false;
82
+ return true;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
88
+ export function getSlowThreshold() {
89
+ try {
90
+ return slowThreshold;
91
+ }
92
+ catch {
93
+ return DEFAULT_SLOW_THRESHOLD;
94
+ }
95
+ }
96
+ function getSdkConfigEndpoint() {
97
+ const base = getEndpoint().replace(/\/+$/, "");
98
+ if (base.endsWith("/sdk/error")) {
99
+ const suffix = "/sdk/error";
100
+ return `${base.slice(0, -suffix.length)}/sdk/config`;
101
+ }
102
+ return `${base}/config`;
103
+ }
104
+ export async function syncRemoteApiTrackingConfig() {
105
+ try {
106
+ const key = getProjectKey();
107
+ if (!key)
108
+ return;
109
+ const configUrl = `${getSdkConfigEndpoint()}/${encodeURIComponent(key)}`;
110
+ const response = await fetch(configUrl, { method: "GET" });
111
+ if (!response.ok)
112
+ return;
113
+ const data = (await response.json());
114
+ if (typeof data?.apiTrackingEnabled === "boolean") {
115
+ remoteApiTrackingEnabled = data.apiTrackingEnabled;
116
+ }
117
+ if (typeof data?.trackInDevelopment === "boolean") {
118
+ remoteTrackInDevelopment = data.trackInDevelopment;
119
+ }
120
+ }
121
+ catch {
122
+ // SDK should never throw
123
+ }
124
+ }
125
+ function isDevelopmentEnvironment() {
126
+ try {
127
+ const processObj = globalThis.process;
128
+ const env = processObj?.env || {};
129
+ const value = env.ENVIRONMENT || env.NODE_ENV || "";
130
+ return String(value).toLowerCase() === "development";
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ }
@@ -0,0 +1,11 @@
1
+ import { capture } from "./capture.js";
2
+ import type { InitOptions } from "./types.js";
3
+ export declare function init(projectKey: string, options?: InitOptions): void;
4
+ export { capture };
5
+ export declare function captureError(error: Error): void;
6
+ declare const fixflow: {
7
+ init: typeof init;
8
+ capture: typeof capture;
9
+ captureError: typeof captureError;
10
+ };
11
+ export default fixflow;
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ import { initConfig, syncRemoteApiTrackingConfig } from "./core.js";
2
+ import { capture, registerAutoCapture, setupApiTracking } from "./capture.js";
3
+ export function init(projectKey, options) {
4
+ try {
5
+ initConfig(projectKey, options);
6
+ registerAutoCapture();
7
+ void syncRemoteApiTrackingConfig().finally(() => {
8
+ setupApiTracking();
9
+ });
10
+ }
11
+ catch {
12
+ // SDK should never throw
13
+ }
14
+ }
15
+ export { capture };
16
+ export function captureError(error) {
17
+ try {
18
+ void capture(error);
19
+ }
20
+ catch {
21
+ // SDK should never throw
22
+ }
23
+ }
24
+ const fixflow = {
25
+ init,
26
+ capture,
27
+ captureError,
28
+ };
29
+ export default fixflow;
@@ -0,0 +1,2 @@
1
+ import type { ErrorPayload } from "./types.js";
2
+ export declare function sendError(payload: Omit<ErrorPayload, "projectKey">): Promise<void>;
package/dist/sender.js ADDED
@@ -0,0 +1,32 @@
1
+ import axios from "axios";
2
+ import { getEndpoint, getProjectKey, isInitialized } from "./core.js";
3
+ export async function sendError(payload) {
4
+ try {
5
+ if (!isInitialized()) {
6
+ return;
7
+ }
8
+ const key = getProjectKey();
9
+ const endpoint = getEndpoint();
10
+ if (!key || !endpoint) {
11
+ return;
12
+ }
13
+ await axios.post(endpoint, {
14
+ projectKey: key,
15
+ message: payload.message,
16
+ stack: payload.stack,
17
+ file: payload.file,
18
+ line: payload.line,
19
+ runtime: payload.runtime,
20
+ env: payload.env,
21
+ nodeVersion: payload.nodeVersion,
22
+ platform: payload.platform,
23
+ type: payload.type,
24
+ url: payload.url,
25
+ status: payload.status,
26
+ duration: payload.duration,
27
+ });
28
+ }
29
+ catch {
30
+ // Silent fail by design
31
+ }
32
+ }
@@ -0,0 +1,33 @@
1
+ import type { AxiosStatic } from "axios";
2
+ export interface InitOptions {
3
+ /** Override ingest URL. Defaults to `https://api.fixflow.ai/sdk/error`. Only set for self-hosted APIs. */
4
+ endpoint?: string;
5
+ trackFetch?: boolean;
6
+ trackAxios?: boolean;
7
+ slowThreshold?: number;
8
+ /**
9
+ * Your application's `axios` import. Required in Node when `trackAxios` is true:
10
+ * npm often installs a separate `axios` copy inside `@fixflow/sdk`, so interceptors
11
+ * on the SDK's default axios would not see requests made with your app's axios.
12
+ */
13
+ axios?: AxiosStatic;
14
+ }
15
+ export type Runtime = "node" | "browser" | "react-native";
16
+ export interface ErrorPayload {
17
+ projectKey: string;
18
+ message: string;
19
+ stack?: string;
20
+ file?: string;
21
+ line?: number;
22
+ runtime: Runtime;
23
+ env: string;
24
+ nodeVersion?: string;
25
+ platform?: string;
26
+ type?: "error" | "api";
27
+ url?: string;
28
+ status?: number;
29
+ duration?: number;
30
+ }
31
+ export type FixflowError = Error & {
32
+ __fixflow?: boolean;
33
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@fixflow/sdk",
3
+ "version": "0.1.0",
4
+ "description": "FixFlow SDK for error capture",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "license": "MIT",
8
+ "type": "module",
9
+ "files": ["dist"],
10
+ "scripts": {
11
+ "build": "tsc"
12
+ },
13
+ "dependencies": {
14
+ "axios": "^1.13.0"
15
+ },
16
+ "peerDependencies": {
17
+ "axios": ">=1.6.0"
18
+ },
19
+ "peerDependenciesMeta": {
20
+ "axios": {
21
+ "optional": true
22
+ }
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.9.2"
26
+ }
27
+ }