trashlytics 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,8 +28,14 @@ yarn add trashlytics effect
28
28
  ```typescript
29
29
  import { createTracker, TransportError } from "trashlytics"
30
30
 
31
- // 1. Create a tracker with your transport
32
- const tracker = createTracker({
31
+ // 1. Define your event types
32
+ type MyEvents = {
33
+ page_view: { page: string; referrer?: string }
34
+ button_click: { buttonId: string }
35
+ }
36
+
37
+ // 2. Create a typed tracker with your transport
38
+ const tracker = createTracker<MyEvents>({
33
39
  transports: [{
34
40
  name: "http",
35
41
  send: async (events) => {
@@ -51,18 +57,18 @@ const tracker = createTracker({
51
57
  flushIntervalMs: 5000,
52
58
  })
53
59
 
54
- // 2. Track events (fire-and-forget)
60
+ // 3. Track events with full type safety (fire-and-forget)
55
61
  tracker.track("page_view", { page: "/home" })
56
62
  tracker.track("button_click", { buttonId: "signup" })
57
63
 
58
- // 3. Graceful shutdown when done
64
+ // 4. Graceful shutdown when done
59
65
  await tracker.shutdown()
60
66
  ```
61
67
 
62
68
  ## Configuration
63
69
 
64
70
  ```typescript
65
- import { createTracker } from "trashlytics"
71
+ import { createTracker, consoleLogger, noopLogger } from "trashlytics"
66
72
 
67
73
  const tracker = createTracker({
68
74
  // Required: array of transports
@@ -92,6 +98,9 @@ const tracker = createTracker({
92
98
  environment: "production",
93
99
  },
94
100
 
101
+ // Logging (default: consoleLogger)
102
+ logger: consoleLogger, // Use noopLogger to silence output
103
+
95
104
  // Error Callback (called after all retries exhausted)
96
105
  onError: (error, events) => {
97
106
  console.error(`Failed to send ${events.length} events:`, error.message)
@@ -275,6 +284,54 @@ class TransportError extends Error {
275
284
  }
276
285
  ```
277
286
 
287
+ ### Logger
288
+
289
+ ```typescript
290
+ interface Logger {
291
+ debug: (message: string, ...args: unknown[]) => void
292
+ info: (message: string, ...args: unknown[]) => void
293
+ warn: (message: string, ...args: unknown[]) => void
294
+ error: (message: string, ...args: unknown[]) => void
295
+ }
296
+ ```
297
+
298
+ ## Custom Logging
299
+
300
+ Control library logging output:
301
+
302
+ ```typescript
303
+ import { createTracker, consoleLogger, noopLogger, createMinLevelLogger } from "trashlytics"
304
+
305
+ // Default: logs to console
306
+ const tracker = createTracker({
307
+ transports,
308
+ logger: consoleLogger,
309
+ })
310
+
311
+ // Silence all logging
312
+ const silentTracker = createTracker({
313
+ transports,
314
+ logger: noopLogger,
315
+ })
316
+
317
+ // Only log warnings and errors
318
+ const warnTracker = createTracker({
319
+ transports,
320
+ logger: createMinLevelLogger("warn"),
321
+ })
322
+
323
+ // Custom logger integration
324
+ const customTracker = createTracker({
325
+ transports,
326
+ logger: {
327
+ debug: (msg, ...args) => myLogger.debug("[analytics]", msg, ...args),
328
+ info: (msg, ...args) => myLogger.info("[analytics]", msg, ...args),
329
+ warn: (msg, ...args) => myLogger.warn("[analytics]", msg, ...args),
330
+ error: (msg, ...args) => myLogger.error("[analytics]", msg, ...args),
331
+ },
332
+ })
333
+ ```
334
+
278
335
  ## Browser Tips
279
336
 
280
337
  ### Beacon API Transport
package/dist/index.cjs CHANGED
@@ -422,14 +422,15 @@ const createRuntime = (config, _generateId, _globalMetadata) => {
422
422
  if (events.length === 0) return;
423
423
  const results = await Promise.allSettled(config.transports.map((transport) => dispatchToTransport(transport, events, retrySchedule)));
424
424
  for (const result of results) if (result.status === "rejected") {
425
- const error = result.reason;
425
+ const error = extractTransportError(result.reason);
426
426
  if (config.onError) config.onError(error, events);
427
427
  config.logger.error("Transport failed:", error.message);
428
428
  }
429
429
  };
430
430
  const batchLoop = effect.Effect.gen(function* () {
431
431
  yield* effect.Effect.forever(effect.Effect.gen(function* () {
432
- const events = yield* effect.Effect.race(effect.Queue.takeBetween(queue, config.batchSize, config.batchSize), effect.Effect.sleep(effect.Duration.millis(config.flushIntervalMs)).pipe(effect.Effect.zipRight(effect.Queue.takeUpTo(queue, config.batchSize))));
432
+ yield* effect.Effect.sleep(effect.Duration.millis(config.flushIntervalMs));
433
+ const events = yield* effect.Queue.takeUpTo(queue, config.batchSize);
433
434
  if (events.length > 0) yield* effect.Effect.tryPromise({
434
435
  try: () => dispatch([...events]),
435
436
  catch: () => /* @__PURE__ */ new Error("Dispatch failed")
@@ -439,7 +440,7 @@ const createRuntime = (config, _generateId, _globalMetadata) => {
439
440
  const batchFiber = effect.Effect.runFork(batchLoop.pipe(effect.Effect.provideService(effect.Scope.Scope, scope)));
440
441
  return {
441
442
  offer: (event) => {
442
- effect.Effect.runFork(effect.Queue.offer(queue, event));
443
+ effect.Queue.unsafeOffer(queue, event);
443
444
  },
444
445
  offerAsync: (event) => effect.Effect.runPromise(effect.Queue.offer(queue, event)).then(() => void 0),
445
446
  flush: async () => {
@@ -479,6 +480,19 @@ const dispatchToTransport = async (transport, events, retrySchedule) => {
479
480
  }));
480
481
  await effect.Effect.runPromise(effect$1);
481
482
  };
483
+ const FiberFailureCauseSymbol = Symbol.for("effect/Runtime/FiberFailure/Cause");
484
+ const extractTransportError = (reason) => {
485
+ if (reason instanceof TransportError) return reason;
486
+ if (reason && typeof reason === "object") {
487
+ const cause = reason[FiberFailureCauseSymbol];
488
+ if (cause?.error instanceof TransportError) return cause.error;
489
+ }
490
+ return new TransportError({
491
+ transport: "unknown",
492
+ reason: String(reason),
493
+ retryable: false
494
+ });
495
+ };
482
496
 
483
497
  //#endregion
484
498
  exports.TransportError = TransportError;
package/dist/index.js CHANGED
@@ -422,14 +422,15 @@ const createRuntime = (config, _generateId, _globalMetadata) => {
422
422
  if (events.length === 0) return;
423
423
  const results = await Promise.allSettled(config.transports.map((transport) => dispatchToTransport(transport, events, retrySchedule)));
424
424
  for (const result of results) if (result.status === "rejected") {
425
- const error = result.reason;
425
+ const error = extractTransportError(result.reason);
426
426
  if (config.onError) config.onError(error, events);
427
427
  config.logger.error("Transport failed:", error.message);
428
428
  }
429
429
  };
430
430
  const batchLoop = Effect.gen(function* () {
431
431
  yield* Effect.forever(Effect.gen(function* () {
432
- const events = yield* Effect.race(Queue.takeBetween(queue, config.batchSize, config.batchSize), Effect.sleep(Duration.millis(config.flushIntervalMs)).pipe(Effect.zipRight(Queue.takeUpTo(queue, config.batchSize))));
432
+ yield* Effect.sleep(Duration.millis(config.flushIntervalMs));
433
+ const events = yield* Queue.takeUpTo(queue, config.batchSize);
433
434
  if (events.length > 0) yield* Effect.tryPromise({
434
435
  try: () => dispatch([...events]),
435
436
  catch: () => /* @__PURE__ */ new Error("Dispatch failed")
@@ -439,7 +440,7 @@ const createRuntime = (config, _generateId, _globalMetadata) => {
439
440
  const batchFiber = Effect.runFork(batchLoop.pipe(Effect.provideService(Scope.Scope, scope)));
440
441
  return {
441
442
  offer: (event) => {
442
- Effect.runFork(Queue.offer(queue, event));
443
+ Queue.unsafeOffer(queue, event);
443
444
  },
444
445
  offerAsync: (event) => Effect.runPromise(Queue.offer(queue, event)).then(() => void 0),
445
446
  flush: async () => {
@@ -479,6 +480,19 @@ const dispatchToTransport = async (transport, events, retrySchedule) => {
479
480
  }));
480
481
  await Effect.runPromise(effect);
481
482
  };
483
+ const FiberFailureCauseSymbol = Symbol.for("effect/Runtime/FiberFailure/Cause");
484
+ const extractTransportError = (reason) => {
485
+ if (reason instanceof TransportError) return reason;
486
+ if (reason && typeof reason === "object") {
487
+ const cause = reason[FiberFailureCauseSymbol];
488
+ if (cause?.error instanceof TransportError) return cause.error;
489
+ }
490
+ return new TransportError({
491
+ transport: "unknown",
492
+ reason: String(reason),
493
+ retryable: false
494
+ });
495
+ };
482
496
 
483
497
  //#endregion
484
498
  export { TransportError, addMetadata, addMetadataFrom, compose, consoleLogger, createEvent, createMinLevelLogger, createTracker, defaults, filter, identity, map, mapName, mapPayload, noopLogger, resolveConfig, tap };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "trashlytics",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "description": "A lightweight event tracking library",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
@@ -51,6 +51,7 @@
51
51
  "@total-typescript/ts-reset": "0.6.1",
52
52
  "@types/node": "25.0.3",
53
53
  "rolldown": "1.0.0-beta.57",
54
+ "tsx": "4.21.0",
54
55
  "typescript": "5.9.3",
55
56
  "ultracite": "6.5.0",
56
57
  "vitest": "4.0.16"