@koderlabs/tasks-sdk-nestjs 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.
@@ -0,0 +1,283 @@
1
+ import { NestModule, DynamicModule, MiddlewareConsumer, OnModuleInit, OnModuleDestroy, ExceptionFilter, ArgumentsHost, NestInterceptor, ExecutionContext, CallHandler, NestMiddleware } from '@nestjs/common';
2
+ import { Observable } from 'rxjs';
3
+ import { AsyncLocalStorage } from 'async_hooks';
4
+
5
+ interface Breadcrumb {
6
+ ts: string;
7
+ category: 'http' | 'console' | 'db' | 'span' | 'custom';
8
+ level?: 'debug' | 'info' | 'warning' | 'error';
9
+ message: string;
10
+ data?: Record<string, unknown>;
11
+ }
12
+ interface RequestInfo {
13
+ method?: string;
14
+ route?: string;
15
+ url?: string;
16
+ userAgent?: string;
17
+ ip?: string;
18
+ userId?: string;
19
+ statusCode?: number;
20
+ }
21
+ interface SpanRecord {
22
+ name: string;
23
+ startTime: string;
24
+ endTime: string;
25
+ durationMs: number;
26
+ status: 'ok' | 'error';
27
+ attrs?: Record<string, unknown>;
28
+ }
29
+ interface InstantTasksOptions {
30
+ apiUrl: string;
31
+ projectId: string;
32
+ managementKey: string;
33
+ environment?: string;
34
+ release?: string;
35
+ beforeSend?: (event: OutgoingEvent) => OutgoingEvent | null;
36
+ dryRun?: boolean;
37
+ debug?: boolean;
38
+ /** Capture per-request breadcrumb ring + request context middleware. Default false. */
39
+ captureRequestContext?: boolean;
40
+ /** Patch globalThis.fetch + http/https.request to record outbound HTTP as breadcrumbs. Default false. */
41
+ captureHttp?: boolean;
42
+ /** Patch console.{log,warn,error} to add breadcrumbs (output preserved). Default false. */
43
+ captureConsole?: boolean;
44
+ /** Wrap each controller method call in a span; attach span list to error events. Default false. */
45
+ captureSpans?: boolean;
46
+ /** Enable TypeORM query tracing — requires `dataSource` ref. Default false. */
47
+ captureTypeOrm?: boolean;
48
+ /** TypeORM DataSource reference (required when captureTypeOrm:true). */
49
+ dataSource?: any;
50
+ /** URL patterns to ignore for HTTP tracing (own ingest is ignored automatically). */
51
+ ignoreUrls?: Array<string | RegExp>;
52
+ /** Max breadcrumbs to keep per request. Default 50. */
53
+ maxBreadcrumbs?: number;
54
+ }
55
+ interface OutgoingEvent {
56
+ kind: 'error';
57
+ ts: string;
58
+ release: string;
59
+ environment: string;
60
+ url: string;
61
+ userAgent: string;
62
+ viewport: {
63
+ width: number;
64
+ height: number;
65
+ };
66
+ payload: {
67
+ exception: {
68
+ type: string;
69
+ value: string;
70
+ stack?: string;
71
+ mechanism: 'uncaughtException' | 'unhandledrejection' | 'http' | 'notify';
72
+ };
73
+ level: 'fatal' | 'error' | 'warning';
74
+ };
75
+ user?: {
76
+ id?: string;
77
+ email?: string;
78
+ };
79
+ request?: RequestInfo;
80
+ breadcrumbs?: Breadcrumb[];
81
+ spans?: SpanRecord[];
82
+ }
83
+ declare const INSTANTTASKS_OPTIONS: unique symbol;
84
+
85
+ declare class InstantTasksModule implements NestModule {
86
+ private readonly opts;
87
+ static forRoot(options: InstantTasksOptions): DynamicModule;
88
+ static forRootAsync(opts: {
89
+ imports?: any[];
90
+ inject?: any[];
91
+ useFactory: (...args: any[]) => InstantTasksOptions | Promise<InstantTasksOptions>;
92
+ }): DynamicModule;
93
+ private static build;
94
+ constructor(opts: InstantTasksOptions);
95
+ configure(consumer: MiddlewareConsumer): void;
96
+ }
97
+
98
+ declare class InstantTasksReporter implements OnModuleInit, OnModuleDestroy {
99
+ private readonly opts;
100
+ private readonly logger;
101
+ private apiUrl;
102
+ private projectId;
103
+ private managementKey;
104
+ private environment;
105
+ private release;
106
+ private debug;
107
+ private dryRun;
108
+ private beforeSend?;
109
+ private recent;
110
+ private readonly RECENT_WINDOW_MS;
111
+ private readonly RECENT_LIMIT;
112
+ private uncaughtHandler;
113
+ private rejectionHandler;
114
+ constructor(opts: InstantTasksOptions);
115
+ onModuleInit(): void;
116
+ onModuleDestroy(): void;
117
+ get enabled(): boolean;
118
+ reportError(err: Error, ctx: {
119
+ mechanism: 'uncaughtException' | 'unhandledrejection' | 'http' | 'notify';
120
+ level?: 'fatal' | 'error' | 'warning';
121
+ url?: string;
122
+ userId?: string;
123
+ }): void;
124
+ private shouldSuppress;
125
+ }
126
+
127
+ /**
128
+ * Reports 5xx (or non-HttpException) crashes to InstantTasks then re-throws.
129
+ *
130
+ * IMPORTANT: a NestJS ExceptionFilter is the final stop for an exception —
131
+ * filters do NOT cascade. Re-throwing here means the exception escapes the
132
+ * filter chain and falls through to Nest's default error handler, BYPASSING
133
+ * any other `APP_FILTER` the host app has registered (e.g. an
134
+ * `AllExceptionsFilter` that formats the response envelope).
135
+ *
136
+ * Two recommended deployments:
137
+ *
138
+ * 1. **Prefer the Interceptor.** Wire `InstantTasksReportInterceptor`
139
+ * instead (see `report.interceptor.ts`). Interceptors compose with
140
+ * `catchError(rxjs)` — they observe the error, report it, and re-throw;
141
+ * the host's existing exception filter still runs and formats the
142
+ * response correctly.
143
+ *
144
+ * 2. **If you must use this filter**, it must be the ONLY filter you
145
+ * register, OR your `AllExceptionsFilter` must extend it. The current
146
+ * re-throw is left in place for backwards compatibility, but the
147
+ * response is no longer guaranteed to match the host's envelope.
148
+ *
149
+ * @deprecated Use `InstantTasksReportInterceptor` instead. This class will
150
+ * be removed in a future major release.
151
+ */
152
+ declare class InstantTasksExceptionFilter implements ExceptionFilter {
153
+ private readonly reporter;
154
+ private readonly logger;
155
+ constructor(reporter: InstantTasksReporter);
156
+ catch(exception: unknown, host: ArgumentsHost): void;
157
+ }
158
+
159
+ /**
160
+ * Observes errors flowing through the HTTP pipeline, reports 5xx (or
161
+ * non-HttpException) to InstantTasks, then re-throws so the host's exception
162
+ * filter chain runs unmodified. Unlike a NestJS `ExceptionFilter`, an
163
+ * interceptor's re-throw propagates correctly to all downstream filters —
164
+ * this is the recommended way to integrate InstantTasks reporting in a
165
+ * NestJS app that already has an `AllExceptionsFilter` that formats the
166
+ * response envelope.
167
+ *
168
+ * Wire with:
169
+ * { provide: APP_INTERCEPTOR, useClass: InstantTasksReportInterceptor }
170
+ */
171
+ declare class InstantTasksReportInterceptor implements NestInterceptor {
172
+ private readonly reporter;
173
+ private readonly logger;
174
+ constructor(reporter: InstantTasksReporter);
175
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
176
+ }
177
+
178
+ declare class InstantTasksRequestContextMiddleware implements NestMiddleware {
179
+ private readonly max;
180
+ constructor(opts: InstantTasksOptions);
181
+ use(req: any, res: any, next: (err?: unknown) => void): void;
182
+ }
183
+
184
+ /**
185
+ * Wraps every HTTP-bound controller method call in a span. Spans are
186
+ * recorded into the current request's ALS ring (capped at `maxBreadcrumbs`)
187
+ * and attached to any error event emitted for that request.
188
+ *
189
+ * Mirrors the core SDK's `client.startSpan({ name: 'http.<method>.<route>' })`
190
+ * semantics — same name shape, same attr keys — but persists in-band
191
+ * with the request context. Out-of-band export to a span endpoint can be
192
+ * added later without changing this interface.
193
+ */
194
+ declare class InstantTasksSpanInterceptor implements NestInterceptor {
195
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
196
+ }
197
+
198
+ /**
199
+ * Per-request context captured at the start of each HTTP request and
200
+ * made available to any downstream code via AsyncLocalStorage. The ring is
201
+ * bounded — once `max` breadcrumbs are added, oldest are evicted.
202
+ *
203
+ * Concurrency: AsyncLocalStorage isolates contexts across concurrent async
204
+ * tasks. Two requests being processed simultaneously each see their own
205
+ * ring; one cannot read or evict the other's breadcrumbs.
206
+ *
207
+ * Caveats: callbacks that escape the request scope (timers scheduled
208
+ * before the request, queues processed elsewhere) will NOT inherit the
209
+ * context unless wrapped with `als.run()` at the boundary. Bull/BullMQ
210
+ * processors run outside the HTTP context — leaveBreadcrumb() inside them
211
+ * is a no-op.
212
+ */
213
+ declare class RequestContextRing {
214
+ private readonly buf;
215
+ private readonly spans;
216
+ request: RequestInfo;
217
+ private readonly max;
218
+ constructor(request: RequestInfo, max?: number);
219
+ add(crumb: Breadcrumb): void;
220
+ addSpan(span: SpanRecord): void;
221
+ getBreadcrumbs(): Breadcrumb[];
222
+ getSpans(): SpanRecord[];
223
+ setUser(userId: string): void;
224
+ }
225
+ /**
226
+ * Module-level storage — single instance per process, shared by every
227
+ * file that imports it (the standard ALS pattern).
228
+ */
229
+ declare const requestContextStorage: AsyncLocalStorage<RequestContextRing>;
230
+ /**
231
+ * Add a breadcrumb to the current request's ring. No-op outside an
232
+ * ALS-scoped request.
233
+ */
234
+ declare function leaveBreadcrumb(crumb: Omit<Breadcrumb, 'ts'> & {
235
+ ts?: string;
236
+ }): void;
237
+ /** Returns the current ring or null when outside an ALS scope. */
238
+ declare function getCurrentContext(): RequestContextRing | null;
239
+
240
+ interface HttpTracingOptions {
241
+ ignoreUrls?: Array<string | RegExp>;
242
+ }
243
+ /**
244
+ * Patch global `fetch` AND Node's `http.request` / `https.request` to emit
245
+ * an `http` breadcrumb on completion. Idempotent via wrap-sentinel.
246
+ *
247
+ * Honors `ignoreUrls` — InstantTasks' own ingest endpoint should be added
248
+ * by the caller (module.ts injects it automatically) to avoid recursion.
249
+ */
250
+ declare function installHttpTracing(opts?: HttpTracingOptions): {
251
+ restore: () => void;
252
+ };
253
+
254
+ /**
255
+ * Patch console.{log,warn,error} to emit breadcrumbs. The original
256
+ * console method is always invoked, so stdout/stderr output is preserved.
257
+ *
258
+ * Skips messages that look like NestJS Logger output — those already
259
+ * surface in your logging stack and re-capturing them risks recursion.
260
+ */
261
+ declare function installConsoleCapture(): {
262
+ restore: () => void;
263
+ };
264
+
265
+ declare class InstantTasksTypeOrmLogger {
266
+ private readonly slowQueryMs;
267
+ constructor(slowQueryMs?: number);
268
+ private emit;
269
+ logQuery(sql: string): void;
270
+ logQueryError(error: string | Error, sql: string): void;
271
+ logQuerySlow(time: number, sql: string): void;
272
+ logSchemaBuild(): void;
273
+ logMigration(): void;
274
+ log(): void;
275
+ }
276
+ /**
277
+ * Install the logger on a TypeORM DataSource. Safe to call multiple times
278
+ * — TypeORM accepts logger replacement on every `setOptions()` call, and
279
+ * we always install the same instance once installed.
280
+ */
281
+ declare function installTypeOrmTracing(dataSource: any): InstantTasksTypeOrmLogger | null;
282
+
283
+ export { type Breadcrumb, INSTANTTASKS_OPTIONS, InstantTasksExceptionFilter, InstantTasksModule, type InstantTasksOptions, InstantTasksReportInterceptor, InstantTasksReporter, InstantTasksRequestContextMiddleware, InstantTasksSpanInterceptor, InstantTasksTypeOrmLogger, type OutgoingEvent, RequestContextRing, type RequestInfo, type SpanRecord, getCurrentContext, installConsoleCapture, installHttpTracing, installTypeOrmTracing, leaveBreadcrumb, requestContextStorage };