@chaja/sdk-node 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,20 @@
1
- # Temporary Holding Version
1
+ # @chaja/sdk-node
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Error tracking for Node.js 22+, reporting to a self-hosted [Chajá](https://github.com/emiliodominguez/chaja) server.
4
+
5
+ ```sh
6
+ pnpm add @chaja/sdk-node
7
+ ```
8
+
9
+ ```ts
10
+ import { expressErrorHandler, init } from "@chaja/sdk-node";
11
+
12
+ init({ endpoint: process.env.CHAJA_URL!, key: process.env.CHAJA_KEY!, service: "api", release: process.env.RELEASE });
13
+
14
+ app.use(expressErrorHandler());
15
+ ```
16
+
17
+ Uncaught exceptions and unhandled rejections are reported with source context. Events that cannot be delivered can be kept on
18
+ disk (`offlineDirectory`) and resent on the next start. For Hono, Next.js route handlers and other Fetch-API servers, use
19
+ `captureRequestError(error, request)`. A desktop app's main process passes `sessionId` for its run, and events or errors
20
+ recorded while offline keep their time with `timestamp`. All options: [docs/sdk.md](https://github.com/emiliodominguez/chaja/blob/main/docs/sdk.md).
@@ -0,0 +1,176 @@
1
+ import { BreadcrumbInput, CaptureContext, CaptureContext as CaptureContext$1, Client, Client as Client$1, ClientOptions, ClientOptions as ClientOptions$1 } from "@chaja/sdk-core";
2
+ //#region ../protocol/src/wire.d.ts
3
+ type AttributeScalar = string | number | boolean;
4
+ type AttributeValue = AttributeScalar | AttributeScalar[];
5
+ type Attributes = Record<string, AttributeValue>;
6
+ interface RequestInfo {
7
+ url?: string;
8
+ method?: string;
9
+ headers?: Record<string, string>;
10
+ }
11
+ //#endregion
12
+ //#region src/index.d.ts
13
+ export interface NodeOptions extends ClientOptions$1 {
14
+ /**
15
+ * Capture `uncaughtException` and `unhandledRejection`. Default true.
16
+ */
17
+ processHandlers?: boolean;
18
+ /**
19
+ * Exit with code 1 after reporting an uncaught exception (Node's default behaviour). Default true.
20
+ */
21
+ exitOnUncaught?: boolean;
22
+ /**
23
+ * Directory where envelopes that could not be delivered are kept and retried on the next start.
24
+ */
25
+ offlineDirectory?: string;
26
+ /**
27
+ * Lines of source code around in-app frames. Default 5; 0 disables.
28
+ */
29
+ contextLines?: number;
30
+ /**
31
+ * HTTP client (tests inject one). Defaults to global fetch.
32
+ */
33
+ fetch?: typeof fetch;
34
+ /**
35
+ * Session of this process's run, for apps where one run is one session (a desktop app's main process). Events carry it
36
+ * and errors carry it as `session.id`, so release health counts sessions and crashes. Servers acting for many people
37
+ * leave it unset.
38
+ */
39
+ sessionId?: string;
40
+ }
41
+ export interface NodeClient extends Client$1 {
42
+ close(timeoutMs?: number): Promise<boolean>;
43
+ /**
44
+ * The HTTP client the SDK sends with.
45
+ */
46
+ readonly fetch: typeof fetch;
47
+ }
48
+ /**
49
+ * Start the Node SDK.
50
+ *
51
+ * @param options - SDK options.
52
+ * @returns The client; calling `init` again replaces it.
53
+ */
54
+ export declare function init(options: NodeOptions): NodeClient;
55
+ /**
56
+ * The parts of an Express, Fastify or `http.IncomingMessage` request the SDK reads.
57
+ */
58
+ export interface NodeRequestLike {
59
+ method?: string;
60
+ url?: string;
61
+ originalUrl?: string;
62
+ headers?: Record<string, string | string[] | undefined>;
63
+ }
64
+ /**
65
+ * Request details from a Node-style request object, without the body.
66
+ *
67
+ * @param request - Incoming request.
68
+ * @returns Request info.
69
+ */
70
+ export declare function requestInfo(request: NodeRequestLike): RequestInfo;
71
+ export type ExpressErrorHandler = (error: unknown, request: NodeRequestLike, response: unknown, next: (error?: unknown) => void) => void;
72
+ /**
73
+ * Express error middleware: reports the error with request details, then passes it on.
74
+ *
75
+ * @param client - Client; defaults to the one from `init`.
76
+ * @returns Express error handler.
77
+ */
78
+ export declare function expressErrorHandler(client?: Client$1): ExpressErrorHandler;
79
+ /**
80
+ * Report an error from a Fetch-API style request (Hono, Next.js route handlers, Bun, Deno).
81
+ *
82
+ * @param error - Thrown value.
83
+ * @param request - Web request.
84
+ * @param context - Extra context.
85
+ * @returns Event id, if sent.
86
+ */
87
+ export declare function captureRequestError(error: unknown, request: Request, context?: CaptureContext$1): string | undefined;
88
+ /**
89
+ * Capture an error with the client from `init`.
90
+ *
91
+ * @param error - Thrown value.
92
+ * @param context - Extra context.
93
+ * @returns Event id, if sent.
94
+ */
95
+ export declare function captureException(error: unknown, context?: CaptureContext$1): string | undefined;
96
+ /**
97
+ * Capture a message with the client from `init`.
98
+ *
99
+ * @param message - Message.
100
+ * @param level - Level.
101
+ * @param context - Extra context.
102
+ * @returns Event id, if sent.
103
+ */
104
+ export declare function captureMessage(message: string, level?: Parameters<Client$1["captureMessage"]>[1], context?: CaptureContext$1): string | undefined;
105
+ export interface ServerEvent {
106
+ /**
107
+ * Your id for the person who did it, the same one the browser SDK passes to `identify`.
108
+ */
109
+ distinctId: string;
110
+ /**
111
+ * Event name, such as `subscription_renewed`.
112
+ */
113
+ name: string;
114
+ properties?: Attributes;
115
+ /**
116
+ * Person properties to set.
117
+ */
118
+ set?: Attributes;
119
+ /**
120
+ * Person properties to set only when the person does not have them yet.
121
+ */
122
+ setOnce?: Attributes;
123
+ /**
124
+ * When it happened, in epoch milliseconds, if earlier than now.
125
+ */
126
+ timestamp?: number;
127
+ /**
128
+ * The session it belongs to.
129
+ */
130
+ sessionId?: string;
131
+ }
132
+ /**
133
+ * Record a product analytics event for a person with the client from `init`. Servers act for many people, so every event
134
+ * names its person.
135
+ *
136
+ * @param event - Person, name and properties.
137
+ * @returns Event id, if sent.
138
+ * @example
139
+ * capture({ distinctId: user.id, name: "subscription_renewed", properties: { plan: "pro" } });
140
+ */
141
+ export declare function capture(event: ServerEvent): string | undefined;
142
+ /**
143
+ * Send queued events.
144
+ *
145
+ * @param timeoutMs - Maximum wait.
146
+ * @returns Whether everything was sent.
147
+ */
148
+ export declare function flush(timeoutMs?: number): Promise<boolean>;
149
+ /**
150
+ * The client created by `init`, if any.
151
+ *
152
+ * @returns Current client.
153
+ */
154
+ export declare function getClient(): NodeClient | undefined;
155
+ /**
156
+ * Feature flags for a person, with the client from `init`, cached for 30 seconds per person and properties.
157
+ *
158
+ * @param distinctId - Your id for the person.
159
+ * @param properties - Properties the flags' conditions may test, on top of what product analytics knows about them.
160
+ * @returns Flag values: `false`, `true` or a variant key; empty when the server cannot be reached.
161
+ * @example
162
+ * const flags = await getFeatureFlags(user.id, { plan: user.plan });
163
+ * if (flags["new-checkout"]) { ... }
164
+ */
165
+ export declare function getFeatureFlags(distinctId: string, properties?: Attributes): Promise<Record<string, boolean | string>>;
166
+ /**
167
+ * Whether a flag is on for a person (a variant counts as on), with the client from `init`.
168
+ *
169
+ * @param key - Flag key.
170
+ * @param distinctId - Your id for the person.
171
+ * @param properties - Properties for the flag's conditions.
172
+ * @returns True when on.
173
+ */
174
+ export declare function isFeatureEnabled(key: string, distinctId: string, properties?: Attributes): Promise<boolean>;
175
+ //#endregion
176
+ export type { BreadcrumbInput, CaptureContext, Client, ClientOptions };
package/dist/index.mjs ADDED
@@ -0,0 +1,431 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { SDK_VERSION, createClient, parseRetryAfter } from "@chaja/sdk-core";
6
+ //#region src/index.ts
7
+ const SOURCE_CACHE_LIMIT = 100;
8
+ const MAX_LINE = 300;
9
+ let current;
10
+ /**
11
+ * Shorten long source lines.
12
+ *
13
+ * @param text - Source line.
14
+ * @returns Clipped line.
15
+ */
16
+ function clip(text) {
17
+ return text.slice(0, MAX_LINE);
18
+ }
19
+ /**
20
+ * Whether a scheduled handle is a Node timer.
21
+ *
22
+ * @param handle - Handle from `schedule`.
23
+ * @returns True for timers.
24
+ */
25
+ function isTimer(handle) {
26
+ return typeof handle === "object" && handle !== null && "unref" in handle;
27
+ }
28
+ /**
29
+ * Read source lines for frames, caching files and skipping dependencies.
30
+ *
31
+ * @param lines - Context lines per side.
32
+ * @returns Frame enricher.
33
+ */
34
+ function sourceContext(lines) {
35
+ const cache = /* @__PURE__ */ new Map();
36
+ /**
37
+ * Lines of a file, or null when unreadable.
38
+ *
39
+ * @param filename - Absolute path or file URL.
40
+ * @returns Lines.
41
+ */
42
+ function read(filename) {
43
+ if (cache.has(filename)) return cache.get(filename) ?? null;
44
+ let content = null;
45
+ try {
46
+ const path = filename.startsWith("file://") ? fileURLToPath(filename) : filename;
47
+ content = readFileSync(path, "utf8").split(/\r?\n/u);
48
+ } catch {
49
+ content = null;
50
+ }
51
+ if (cache.size >= SOURCE_CACHE_LIMIT) cache.clear();
52
+ cache.set(filename, content);
53
+ return content;
54
+ }
55
+ return function withContext(frame) {
56
+ const { filename, lineno } = frame;
57
+ if (lines <= 0 || !filename || !lineno || filename.includes("node_modules") || filename.startsWith("node:") || !/^(?:\/|file:\/\/|[a-z]:\\)/iu.test(filename)) return frame;
58
+ const source = read(filename);
59
+ const line = source?.[lineno - 1];
60
+ if (!source || line === void 0) return frame;
61
+ return {
62
+ ...frame,
63
+ contextLine: clip(line),
64
+ preContext: source.slice(Math.max(0, lineno - 1 - lines), lineno - 1).map(clip),
65
+ postContext: source.slice(lineno, lineno + lines).map(clip)
66
+ };
67
+ };
68
+ }
69
+ /**
70
+ * Whether a frame is application code.
71
+ *
72
+ * @param frame - Frame.
73
+ * @returns In-app flag.
74
+ */
75
+ function inApp(frame) {
76
+ const filename = frame.filename ?? "";
77
+ return !filename.includes("node_modules") && !filename.startsWith("node:") && !filename.startsWith("internal/");
78
+ }
79
+ /**
80
+ * Start the Node SDK.
81
+ *
82
+ * @param options - SDK options.
83
+ * @returns The client; calling `init` again replaces it.
84
+ */
85
+ function init(options) {
86
+ current?.close(0);
87
+ const send = options.fetch ?? fetch;
88
+ const envelopeUrl = `${options.endpoint.replace(/\/+$/u, "")}/api/v1/envelope`;
89
+ const withContext = sourceContext(options.contextLines ?? 5);
90
+ const cleanups = [];
91
+ const transport = { async send(body) {
92
+ try {
93
+ const response = await send(envelopeUrl, {
94
+ method: "POST",
95
+ headers: {
96
+ "content-type": "application/json",
97
+ "x-chaja-key": options.key
98
+ },
99
+ body,
100
+ signal: AbortSignal.timeout(1e4)
101
+ });
102
+ const retry = parseRetryAfter(response.headers.get("retry-after"));
103
+ return {
104
+ status: response.status,
105
+ ...retry === void 0 ? {} : { retryAfterSeconds: retry }
106
+ };
107
+ } catch {
108
+ return { status: 0 };
109
+ }
110
+ } };
111
+ const client = createClient(options, {
112
+ sdk: {
113
+ name: "chaja.javascript.node",
114
+ version: SDK_VERSION
115
+ },
116
+ platform: "node",
117
+ transport,
118
+ eventId() {
119
+ return randomUUID().replaceAll("-", "");
120
+ },
121
+ now: Date.now,
122
+ random: Math.random,
123
+ ...options.sessionId === void 0 ? {} : { session: runSession(options.sessionId) },
124
+ schedule(callback, delayMs) {
125
+ const handle = setTimeout(callback, delayMs);
126
+ handle.unref();
127
+ return handle;
128
+ },
129
+ cancel(handle) {
130
+ if (isTimer(handle)) clearTimeout(handle);
131
+ },
132
+ enrich(item) {
133
+ return {
134
+ ...item,
135
+ ...item.exceptions ? { exceptions: item.exceptions.map(function exception(entry) {
136
+ return {
137
+ ...entry,
138
+ frames: entry.frames.map(function frame(value) {
139
+ return withContext({
140
+ ...value,
141
+ inApp: value.inApp ?? inApp(value)
142
+ });
143
+ })
144
+ };
145
+ }) } : {},
146
+ attributes: {
147
+ "process.runtime.version": process.version,
148
+ "host.arch": process.arch,
149
+ "os.type": process.platform,
150
+ ...item.attributes
151
+ }
152
+ };
153
+ },
154
+ ...options.offlineDirectory ? { onUndeliverable: persist(options.offlineDirectory) } : {}
155
+ });
156
+ if (options.processHandlers !== false) {
157
+ /**
158
+ * Report an uncaught exception, flush and exit like Node would.
159
+ *
160
+ * @param error - Uncaught error.
161
+ */
162
+ function onUncaught(error) {
163
+ client.captureException(error, {
164
+ level: "fatal",
165
+ mechanism: {
166
+ type: "uncaughtException",
167
+ handled: false
168
+ }
169
+ });
170
+ if (options.exitOnUncaught !== false) client.flush(2e3).finally(function exit() {
171
+ process.exit(1);
172
+ });
173
+ }
174
+ /**
175
+ * Report an unhandled promise rejection.
176
+ *
177
+ * @param reason - Rejection reason.
178
+ */
179
+ function onRejection(reason) {
180
+ client.captureException(reason, { mechanism: {
181
+ type: "unhandledRejection",
182
+ handled: false
183
+ } });
184
+ }
185
+ process.on("uncaughtException", onUncaught);
186
+ process.on("unhandledRejection", onRejection);
187
+ cleanups.push(function remove() {
188
+ process.off("uncaughtException", onUncaught);
189
+ process.off("unhandledRejection", onRejection);
190
+ });
191
+ }
192
+ if (options.offlineDirectory) replay(options.offlineDirectory, transport);
193
+ const instance = {
194
+ ...client,
195
+ fetch: send,
196
+ async close(timeoutMs = 2e3) {
197
+ for (const cleanup of cleanups.splice(0)) cleanup();
198
+ if (current === instance) current = void 0;
199
+ return client.flush(timeoutMs);
200
+ }
201
+ };
202
+ current = instance;
203
+ return instance;
204
+ }
205
+ /**
206
+ * A session that lasts the whole run: `reset` (sign-out) keeps it, since the run goes on.
207
+ *
208
+ * @param id - Session id.
209
+ * @returns The platform's session hooks.
210
+ */
211
+ function runSession(id) {
212
+ return {
213
+ current() {
214
+ return id;
215
+ },
216
+ reset() {}
217
+ };
218
+ }
219
+ /**
220
+ * Persist undeliverable envelopes as files.
221
+ *
222
+ * @param directory - Offline directory.
223
+ * @returns Callback for the queue.
224
+ */
225
+ function persist(directory) {
226
+ return function save(body) {
227
+ try {
228
+ mkdirSync(directory, { recursive: true });
229
+ writeFileSync(join(directory, `${Date.now()}-${randomUUID()}.json`), body);
230
+ } catch {}
231
+ };
232
+ }
233
+ /**
234
+ * Resend envelopes saved by a previous run, deleting the ones the server accepted or rejected for good.
235
+ *
236
+ * @param directory - Offline directory.
237
+ * @param transport - Transport.
238
+ */
239
+ function replay(directory, transport) {
240
+ if (!existsSync(directory)) return;
241
+ (async function resend() {
242
+ for (const name of readdirSync(directory).filter(isEnvelopeFile).toSorted()) {
243
+ const path = join(directory, name);
244
+ const result = await transport.send(readFileSync(path, "utf8"));
245
+ if (result.status === 0 || result.status === 429 || result.status >= 500) return;
246
+ rmSync(path, { force: true });
247
+ }
248
+ })();
249
+ }
250
+ /**
251
+ * Whether a file name looks like a saved envelope.
252
+ *
253
+ * @param name - File name.
254
+ * @returns True for `.json` files.
255
+ */
256
+ function isEnvelopeFile(name) {
257
+ return name.endsWith(".json");
258
+ }
259
+ /**
260
+ * Request details from a Node-style request object, without the body.
261
+ *
262
+ * @param request - Incoming request.
263
+ * @returns Request info.
264
+ */
265
+ function requestInfo(request) {
266
+ const headers = {};
267
+ for (const [key, value] of Object.entries(request.headers ?? {})) if (value !== void 0) headers[key] = Array.isArray(value) ? value.join(", ") : value;
268
+ return {
269
+ ...request.method ? { method: request.method } : {},
270
+ ...request.originalUrl ?? request.url ? { url: request.originalUrl ?? request.url } : {},
271
+ headers
272
+ };
273
+ }
274
+ /**
275
+ * Express error middleware: reports the error with request details, then passes it on.
276
+ *
277
+ * @param client - Client; defaults to the one from `init`.
278
+ * @returns Express error handler.
279
+ */
280
+ function expressErrorHandler(client) {
281
+ return function chajaErrorHandler(error, request, _response, next) {
282
+ (client ?? current)?.captureException(error, {
283
+ request: requestInfo(request),
284
+ mechanism: {
285
+ type: "express",
286
+ handled: false
287
+ }
288
+ });
289
+ next(error);
290
+ };
291
+ }
292
+ /**
293
+ * Report an error from a Fetch-API style request (Hono, Next.js route handlers, Bun, Deno).
294
+ *
295
+ * @param error - Thrown value.
296
+ * @param request - Web request.
297
+ * @param context - Extra context.
298
+ * @returns Event id, if sent.
299
+ */
300
+ function captureRequestError(error, request, context = {}) {
301
+ const headers = {};
302
+ request.headers.forEach(function copy(value, key) {
303
+ headers[key] = value;
304
+ });
305
+ return current?.captureException(error, {
306
+ ...context,
307
+ request: {
308
+ method: request.method,
309
+ url: request.url,
310
+ headers
311
+ },
312
+ mechanism: context.mechanism ?? {
313
+ type: "request",
314
+ handled: false
315
+ }
316
+ });
317
+ }
318
+ /**
319
+ * Capture an error with the client from `init`.
320
+ *
321
+ * @param error - Thrown value.
322
+ * @param context - Extra context.
323
+ * @returns Event id, if sent.
324
+ */
325
+ function captureException(error, context) {
326
+ return current?.captureException(error, context);
327
+ }
328
+ /**
329
+ * Capture a message with the client from `init`.
330
+ *
331
+ * @param message - Message.
332
+ * @param level - Level.
333
+ * @param context - Extra context.
334
+ * @returns Event id, if sent.
335
+ */
336
+ function captureMessage(message, level, context) {
337
+ return current?.captureMessage(message, level, context);
338
+ }
339
+ /**
340
+ * Record a product analytics event for a person with the client from `init`. Servers act for many people, so every event
341
+ * names its person.
342
+ *
343
+ * @param event - Person, name and properties.
344
+ * @returns Event id, if sent.
345
+ * @example
346
+ * capture({ distinctId: user.id, name: "subscription_renewed", properties: { plan: "pro" } });
347
+ */
348
+ function capture(event) {
349
+ return current?.capture(event.name, event.properties, {
350
+ distinctId: event.distinctId,
351
+ ...event.set ? { set: event.set } : {},
352
+ ...event.setOnce ? { setOnce: event.setOnce } : {},
353
+ ...event.timestamp === void 0 ? {} : { timestamp: event.timestamp },
354
+ ...event.sessionId === void 0 ? {} : { sessionId: event.sessionId }
355
+ });
356
+ }
357
+ /**
358
+ * Send queued events.
359
+ *
360
+ * @param timeoutMs - Maximum wait.
361
+ * @returns Whether everything was sent.
362
+ */
363
+ async function flush(timeoutMs) {
364
+ return current ? current.flush(timeoutMs) : true;
365
+ }
366
+ /**
367
+ * The client created by `init`, if any.
368
+ *
369
+ * @returns Current client.
370
+ */
371
+ function getClient() {
372
+ return current;
373
+ }
374
+ const FLAG_CACHE_MS = 3e4;
375
+ const flagCache = /* @__PURE__ */ new Map();
376
+ /**
377
+ * Feature flags for a person, with the client from `init`, cached for 30 seconds per person and properties.
378
+ *
379
+ * @param distinctId - Your id for the person.
380
+ * @param properties - Properties the flags' conditions may test, on top of what product analytics knows about them.
381
+ * @returns Flag values: `false`, `true` or a variant key; empty when the server cannot be reached.
382
+ * @example
383
+ * const flags = await getFeatureFlags(user.id, { plan: user.plan });
384
+ * if (flags["new-checkout"]) { ... }
385
+ */
386
+ async function getFeatureFlags(distinctId, properties = {}) {
387
+ const client = current;
388
+ if (!client) return {};
389
+ const cacheKey = JSON.stringify([distinctId, properties]);
390
+ const cached = flagCache.get(cacheKey);
391
+ if (cached && Date.now() - cached.at < FLAG_CACHE_MS) return cached.flags;
392
+ try {
393
+ const response = await client.fetch(`${client.options.endpoint.replace(/\/+$/u, "")}/api/v1/flags`, {
394
+ method: "POST",
395
+ headers: {
396
+ "content-type": "application/json",
397
+ "x-chaja-key": client.options.key
398
+ },
399
+ body: JSON.stringify({
400
+ distinctId,
401
+ properties
402
+ }),
403
+ signal: AbortSignal.timeout(5e3)
404
+ });
405
+ const body = response.ok ? await response.json() : void 0;
406
+ const raw = typeof body === "object" && body !== null && "flags" in body && typeof body.flags === "object" && body.flags !== null ? body.flags : {};
407
+ const flags = {};
408
+ for (const [key, value] of Object.entries(raw)) if (typeof value === "boolean" || typeof value === "string") flags[key] = value;
409
+ flagCache.set(cacheKey, {
410
+ at: Date.now(),
411
+ flags
412
+ });
413
+ return flags;
414
+ } catch {
415
+ return cached?.flags ?? {};
416
+ }
417
+ }
418
+ /**
419
+ * Whether a flag is on for a person (a variant counts as on), with the client from `init`.
420
+ *
421
+ * @param key - Flag key.
422
+ * @param distinctId - Your id for the person.
423
+ * @param properties - Properties for the flag's conditions.
424
+ * @returns True when on.
425
+ */
426
+ async function isFeatureEnabled(key, distinctId, properties = {}) {
427
+ const value = (await getFeatureFlags(distinctId, properties))[key];
428
+ return value !== void 0 && value !== false;
429
+ }
430
+ //#endregion
431
+ export { capture, captureException, captureMessage, captureRequestError, expressErrorHandler, flush, getClient, getFeatureFlags, init, isFeatureEnabled, requestInfo };
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@chaja/sdk-node",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Chajá error tracking for Node.js: process handlers, source context, offline buffer, Express and Fetch helpers.",
5
+ "keywords": [
6
+ "chaja",
7
+ "error-tracking",
8
+ "observability",
9
+ "node",
10
+ "sdk"
11
+ ],
12
+ "homepage": "https://github.com/emiliodominguez/chaja#readme",
13
+ "bugs": "https://github.com/emiliodominguez/chaja/issues",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/emiliodominguez/chaja.git",
17
+ "directory": "packages/sdk-node"
18
+ },
19
+ "type": "module",
20
+ "nx": {
21
+ "tags": [
22
+ "scope:sdk",
23
+ "type:sdk"
24
+ ]
25
+ },
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.mts",
29
+ "default": "./dist/index.mjs"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "sideEffects": false,
36
+ "devDependencies": {
37
+ "@chaja/protocol": "0.0.0",
38
+ "tsdown": "0.23.0"
39
+ },
40
+ "dependencies": {
41
+ "@chaja/sdk-core": "0.2.0"
42
+ },
43
+ "engines": {
44
+ "node": ">=22"
45
+ },
46
+ "scripts": {
47
+ "build": "tsdown src/index.ts --format esm --dts --clean --out-dir dist",
48
+ "typecheck": "tsc --noEmit -p ."
49
+ }
6
50
  }