@allstak/angular 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,740 @@
1
+ import { AllStak } from '@allstak/js';
2
+ export { AllStak, Scope, Session, SessionTracker, Span, WebVitalsModule, consoleIntegration, databaseIntegration, dedupeIntegration, defineIntegration, eventFiltersIntegration, httpClientIntegration, inboundFiltersIntegration, isWebVitalsSupported } from '@allstak/js';
3
+ import * as i0 from '@angular/core';
4
+ import { Injectable, Optional, Input, Directive, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, ErrorHandler, APP_INITIALIZER, inject, InjectionToken, NgModule } from '@angular/core';
5
+ import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
6
+ import { Observable } from 'rxjs';
7
+ import * as i1 from '@angular/router';
8
+ import { NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
9
+
10
+ // AUTO-GENERATED by scripts/gen-version.mjs — do not edit by hand.
11
+ const SDK_NAME = 'allstak-angular';
12
+ const SDK_VERSION = "0.1.0";
13
+
14
+ /**
15
+ * Initialize the AllStak SDK for an Angular application.
16
+ *
17
+ * This is a thin shim over `AllStak.init` from `@allstak/js` that stamps the
18
+ * wrapper identity (`sdkName: 'allstak-angular'`, `sdkVersion`) so the backend
19
+ * can tell Angular traffic apart from plain JS traffic. Everything else —
20
+ * buffering, sampling, session health, offline queue, PII scrubbing — is
21
+ * delegated to the underlying core client.
22
+ *
23
+ * Call this once at app boot in `main.ts`, BEFORE `bootstrapApplication(...)`
24
+ * (standalone) or `platformBrowserDynamic().bootstrapModule(...)` (NgModule),
25
+ * so the global `ErrorHandler` and router instrumentation see a live client:
26
+ *
27
+ * import { init } from '@allstak/angular';
28
+ *
29
+ * init({
30
+ * apiKey: environment.allstakApiKey,
31
+ * environment: environment.production ? 'production' : 'development',
32
+ * });
33
+ *
34
+ * bootstrapApplication(AppComponent, appConfig);
35
+ */
36
+ function init(config) {
37
+ return AllStak.init({
38
+ ...config,
39
+ sdkName: config.sdkName ?? SDK_NAME,
40
+ sdkVersion: config.sdkVersion ?? SDK_VERSION,
41
+ });
42
+ }
43
+
44
+ const DEFAULT_OPTIONS = {
45
+ logErrors: true,
46
+ };
47
+ /**
48
+ * Unwrap the common Angular/zone.js error envelopes so we capture the real
49
+ * underlying error rather than the wrapper:
50
+ *
51
+ * - Angular wraps errors thrown during change detection as
52
+ * `{ ngOriginalError }` / `{ originalError }`.
53
+ * - `HttpErrorResponse` carries the server payload on `.error`.
54
+ * - DOM `ErrorEvent` carries the real error on `.error`.
55
+ * - Rejected promises may surface as `{ rejection }`.
56
+ */
57
+ function defaultExtractor(error) {
58
+ if (error && typeof error === 'object') {
59
+ const candidate = error;
60
+ // PromiseRejectionEvent-style wrapping from zone.js.
61
+ if ('rejection' in candidate && candidate['rejection'] != null) {
62
+ return defaultExtractor(candidate['rejection']);
63
+ }
64
+ // Angular wraps the thrown value during change detection.
65
+ if ('ngOriginalError' in candidate && candidate['ngOriginalError'] != null) {
66
+ return defaultExtractor(candidate['ngOriginalError']);
67
+ }
68
+ if ('originalError' in candidate && candidate['originalError'] != null) {
69
+ return defaultExtractor(candidate['originalError']);
70
+ }
71
+ // DOM ErrorEvent / HttpErrorResponse expose the real error on `.error`.
72
+ if ('error' in candidate && candidate['error'] instanceof Error) {
73
+ return candidate['error'];
74
+ }
75
+ }
76
+ return error;
77
+ }
78
+ /** Coerce any extracted value into an `Error` so `captureException` is happy. */
79
+ function toError(value) {
80
+ if (value instanceof Error)
81
+ return value;
82
+ if (typeof value === 'string')
83
+ return new Error(value);
84
+ try {
85
+ return new Error(JSON.stringify(value));
86
+ }
87
+ catch {
88
+ return new Error(String(value));
89
+ }
90
+ }
91
+ /**
92
+ * AllStak's implementation of Angular's `ErrorHandler`. Captures every error
93
+ * routed through Angular's global error handling — uncaught render/lifecycle
94
+ * errors, change-detection errors, and (when zone.js is present) unhandled
95
+ * promise rejections.
96
+ *
97
+ * Wired manually as `{ provide: ErrorHandler, useValue: createErrorHandler() }`
98
+ * or via {@link provideAllStakErrorHandler}.
99
+ */
100
+ class AllStakErrorHandler {
101
+ options;
102
+ constructor(options = {}) {
103
+ this.options = { ...DEFAULT_OPTIONS, ...options };
104
+ }
105
+ handleError(error) {
106
+ const extracted = this.options.extractor
107
+ ? this.options.extractor(error, defaultExtractor)
108
+ : defaultExtractor(error);
109
+ const err = toError(extracted);
110
+ try {
111
+ AllStak.captureException(err, { framework: 'angular' });
112
+ }
113
+ catch {
114
+ // Never let our handler break the host's error path.
115
+ }
116
+ if (this.options.logErrors !== false) {
117
+ // Preserve Angular's default of surfacing the original (pre-unwrap)
118
+ // value so stack traces and zone context stay intact in the console.
119
+ // eslint-disable-next-line no-console
120
+ console.error(error);
121
+ }
122
+ }
123
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakErrorHandler, deps: "invalid", target: i0.ɵɵFactoryTarget.Injectable });
124
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakErrorHandler });
125
+ }
126
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakErrorHandler, decorators: [{
127
+ type: Injectable
128
+ }], ctorParameters: () => [{ type: undefined }] });
129
+ /**
130
+ * Factory returning an Angular `ErrorHandler` that forwards to
131
+ * `AllStak.captureException`.
132
+ *
133
+ * Standalone (`app.config.ts`):
134
+ *
135
+ * import { ErrorHandler } from '@angular/core';
136
+ * import { createErrorHandler } from '@allstak/angular';
137
+ *
138
+ * export const appConfig = {
139
+ * providers: [{ provide: ErrorHandler, useValue: createErrorHandler() }],
140
+ * };
141
+ *
142
+ * NgModule (`AppModule`):
143
+ *
144
+ * @NgModule({
145
+ * providers: [{ provide: ErrorHandler, useValue: createErrorHandler() }],
146
+ * })
147
+ * export class AppModule {}
148
+ */
149
+ function createErrorHandler(options) {
150
+ return new AllStakErrorHandler(options);
151
+ }
152
+
153
+ const KNOWN_METHODS = new Set([
154
+ 'GET',
155
+ 'POST',
156
+ 'PUT',
157
+ 'DELETE',
158
+ 'PATCH',
159
+ 'HEAD',
160
+ 'OPTIONS',
161
+ ]);
162
+ /** Normalise an Angular request method into the core method union. */
163
+ function normalizeMethod(method) {
164
+ const upper = method.toUpperCase();
165
+ return (KNOWN_METHODS.has(upper) ? upper : 'GET');
166
+ }
167
+ /** Split an outbound URL into `host` + `path`, tolerating relative URLs. */
168
+ function splitUrl(url) {
169
+ try {
170
+ const base = typeof window !== 'undefined' && window.location
171
+ ? window.location.origin
172
+ : 'http://localhost';
173
+ const parsed = new URL(url, base);
174
+ return { host: parsed.host, path: parsed.pathname + parsed.search };
175
+ }
176
+ catch {
177
+ return { host: '', path: url };
178
+ }
179
+ }
180
+ /**
181
+ * Open a span + record an outbound request for a single `HttpRequest`, and
182
+ * return the lifecycle hooks the interceptor (class or functional) uses to
183
+ * finish it. Shared so the class-based and functional interceptors stay
184
+ * behaviourally identical.
185
+ */
186
+ function instrumentRequest(req) {
187
+ const startedAt = Date.now();
188
+ const method = normalizeMethod(req.method);
189
+ const { host, path } = splitUrl(req.urlWithParams);
190
+ let span = null;
191
+ try {
192
+ span = AllStak.startSpan('http.client', {
193
+ op: 'http.client',
194
+ description: `${req.method} ${req.urlWithParams}`,
195
+ attributes: {
196
+ 'allstak.origin': 'auto.http.angular',
197
+ 'http.method': req.method,
198
+ 'http.url': req.urlWithParams,
199
+ },
200
+ });
201
+ }
202
+ catch {
203
+ // Never block the request on observability errors.
204
+ }
205
+ const finish = (statusCode, status) => {
206
+ const durationMs = Date.now() - startedAt;
207
+ try {
208
+ span?.setMeasurement('http.duration_ms', durationMs);
209
+ span?.finish(status);
210
+ }
211
+ catch {
212
+ // ignore — span may already be finished
213
+ }
214
+ try {
215
+ const item = {
216
+ direction: 'outbound',
217
+ method,
218
+ host,
219
+ path,
220
+ statusCode,
221
+ durationMs,
222
+ };
223
+ AllStak.captureRequest(item);
224
+ }
225
+ catch {
226
+ // ignore — request recording is best-effort
227
+ }
228
+ };
229
+ return {
230
+ onResponse(statusCode) {
231
+ finish(statusCode, statusCode >= 400 ? 'error' : 'ok');
232
+ },
233
+ onError(error) {
234
+ const statusCode = error instanceof HttpErrorResponse ? error.status : 0;
235
+ finish(statusCode, 'error');
236
+ },
237
+ };
238
+ }
239
+ /**
240
+ * Class-based `HttpInterceptor` that records every outbound HTTP request and
241
+ * opens a `http.client` span around it. Register in the DI-token style used by
242
+ * NgModule apps and standalone apps that opt into `withInterceptorsFromDi()`:
243
+ *
244
+ * import { HTTP_INTERCEPTORS } from '@angular/common/http';
245
+ * import { AllStakHttpInterceptor } from '@allstak/angular';
246
+ *
247
+ * providers: [
248
+ * { provide: HTTP_INTERCEPTORS, useClass: AllStakHttpInterceptor, multi: true },
249
+ * ]
250
+ *
251
+ * The interceptor never mutates the request and never swallows errors — it
252
+ * only observes the stream.
253
+ */
254
+ class AllStakHttpInterceptor {
255
+ intercept(req, next) {
256
+ const hooks = instrumentRequest(req);
257
+ return new Observable((subscriber) => {
258
+ const sub = next.handle(req).subscribe({
259
+ next: (event) => {
260
+ if (event instanceof HttpResponse) {
261
+ hooks.onResponse(event.status);
262
+ }
263
+ subscriber.next(event);
264
+ },
265
+ error: (error) => {
266
+ hooks.onError(error);
267
+ subscriber.error(error);
268
+ },
269
+ complete: () => subscriber.complete(),
270
+ });
271
+ return () => sub.unsubscribe();
272
+ });
273
+ }
274
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakHttpInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
275
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakHttpInterceptor });
276
+ }
277
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakHttpInterceptor, decorators: [{
278
+ type: Injectable
279
+ }] });
280
+ /**
281
+ * Functional `HttpInterceptor` for standalone apps using
282
+ * `provideHttpClient(withInterceptors([allStakHttpInterceptor]))`.
283
+ *
284
+ * Behaviourally identical to {@link AllStakHttpInterceptor}.
285
+ */
286
+ function allStakHttpInterceptor(req, next) {
287
+ const hooks = instrumentRequest(req);
288
+ return new Observable((subscriber) => {
289
+ const sub = next(req).subscribe({
290
+ next: (event) => {
291
+ if (event instanceof HttpResponse) {
292
+ hooks.onResponse(event.status);
293
+ }
294
+ subscriber.next(event);
295
+ },
296
+ error: (error) => {
297
+ hooks.onError(error);
298
+ subscriber.error(error);
299
+ },
300
+ complete: () => subscriber.complete(),
301
+ });
302
+ return () => sub.unsubscribe();
303
+ });
304
+ }
305
+
306
+ /**
307
+ * Router-aware navigation instrumentation.
308
+ *
309
+ * `TraceService` subscribes to Angular `Router` events and opens a
310
+ * `navigation` span per route change — `NavigationStart` opens it,
311
+ * `NavigationEnd` finishes it `ok`, and `NavigationCancel` / `NavigationError`
312
+ * finish it `error`. A navigation breadcrumb is recorded alongside each span.
313
+ *
314
+ * Because Angular only constructs a service when something injects it, the
315
+ * service must be *force-instantiated* for its Router subscription to run.
316
+ * Use {@link provideAllStakRouterInstrumentation} (standalone) or inject it in
317
+ * your `AppComponent` constructor (NgModule) — see those helpers for wiring.
318
+ *
319
+ * `Router` is `@Optional()` so apps without `@angular/router` (which is an
320
+ * optional peer dependency) can still construct the service without error; it
321
+ * simply becomes a no-op.
322
+ */
323
+ class TraceService {
324
+ router;
325
+ activeNavSpan = null;
326
+ subscription = null;
327
+ constructor(router) {
328
+ this.router = router;
329
+ if (!this.router)
330
+ return;
331
+ this.subscription = this.router.events.subscribe((event) => {
332
+ this.handleEvent(event);
333
+ });
334
+ }
335
+ handleEvent(event) {
336
+ try {
337
+ if (event instanceof NavigationStart) {
338
+ this.onNavigationStart(event.url);
339
+ }
340
+ else if (event instanceof NavigationEnd) {
341
+ this.finishActive('ok');
342
+ }
343
+ else if (event instanceof NavigationCancel ||
344
+ event instanceof NavigationError) {
345
+ this.finishActive('error');
346
+ if (event instanceof NavigationError) {
347
+ AllStak.captureException(event.error instanceof Error
348
+ ? event.error
349
+ : new Error(String(event.error)), { framework: 'angular', source: '@angular/router' });
350
+ }
351
+ }
352
+ }
353
+ catch {
354
+ // ignore — never block navigation on observability errors
355
+ }
356
+ }
357
+ onNavigationStart(url) {
358
+ // A new navigation supersedes any span still open from a redirect chain.
359
+ this.finishActive('ok');
360
+ AllStak.addBreadcrumb({
361
+ type: 'navigation',
362
+ message: url,
363
+ level: 'info',
364
+ data: { url },
365
+ });
366
+ this.activeNavSpan = AllStak.startSpan('navigation', {
367
+ op: 'navigation',
368
+ description: url,
369
+ attributes: {
370
+ 'allstak.origin': 'auto.navigation.angular',
371
+ 'route.url': url,
372
+ },
373
+ });
374
+ }
375
+ finishActive(status) {
376
+ if (!this.activeNavSpan)
377
+ return;
378
+ try {
379
+ this.activeNavSpan.finish(status);
380
+ }
381
+ catch {
382
+ // ignore — span may already be finished
383
+ }
384
+ finally {
385
+ this.activeNavSpan = null;
386
+ }
387
+ }
388
+ ngOnDestroy() {
389
+ this.subscription?.unsubscribe();
390
+ this.finishActive('ok');
391
+ }
392
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TraceService, deps: [{ token: i1.Router, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
393
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TraceService, providedIn: 'root' });
394
+ }
395
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TraceService, decorators: [{
396
+ type: Injectable,
397
+ args: [{ providedIn: 'root' }]
398
+ }], ctorParameters: () => [{ type: i1.Router, decorators: [{
399
+ type: Optional
400
+ }] }] });
401
+
402
+ /**
403
+ * `[trace]` template directive for component render timing.
404
+ *
405
+ * <app-checkout trace="checkout"></app-checkout>
406
+ *
407
+ * Opens a `ui.angular.init` span when the host component initialises
408
+ * (`ngOnInit`) and finishes it once the component's view has been fully
409
+ * initialised (`ngAfterViewInit`). The span is named after the directive's
410
+ * `trace` value — required, because component class names are not reliable in
411
+ * minified production builds.
412
+ *
413
+ * Standalone components import the directive directly; module-based apps get
414
+ * it via {@link AllStakTraceModule}.
415
+ */
416
+ class TraceDirective {
417
+ /**
418
+ * Human-readable name for the traced component. Used as the span
419
+ * description. Required so spans stay meaningful after minification.
420
+ */
421
+ componentName = '';
422
+ span = null;
423
+ ngOnInit() {
424
+ const name = this.componentName || 'unnamed';
425
+ try {
426
+ this.span = AllStak.startSpan('ui.angular.init', {
427
+ op: 'ui.angular.init',
428
+ description: name,
429
+ attributes: {
430
+ 'allstak.origin': 'auto.ui.angular',
431
+ 'angular.component': name,
432
+ },
433
+ });
434
+ }
435
+ catch {
436
+ // Never let render tracing break a component.
437
+ }
438
+ }
439
+ ngAfterViewInit() {
440
+ try {
441
+ this.span?.finish('ok');
442
+ }
443
+ catch {
444
+ // ignore — span may already be finished
445
+ }
446
+ finally {
447
+ this.span = null;
448
+ }
449
+ }
450
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TraceDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
451
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: TraceDirective, isStandalone: true, selector: "[trace]", inputs: { componentName: ["trace", "componentName"] }, ngImport: i0 });
452
+ }
453
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TraceDirective, decorators: [{
454
+ type: Directive,
455
+ args: [{
456
+ selector: '[trace]',
457
+ standalone: true,
458
+ }]
459
+ }], propDecorators: { componentName: [{
460
+ type: Input,
461
+ args: ['trace']
462
+ }] } });
463
+
464
+ /**
465
+ * Class decorator that instruments a component's init lifecycle. Wraps
466
+ * `ngOnInit` so a `ui.angular.init` span is opened on init and finished on
467
+ * `ngAfterViewInit` (or immediately after `ngOnInit` if the component has no
468
+ * `ngAfterViewInit`).
469
+ *
470
+ * @TraceClassDecorator({ name: 'CheckoutComponent' })
471
+ * @Component({ ... })
472
+ * export class CheckoutComponent implements OnInit {}
473
+ */
474
+ function TraceClassDecorator(options) {
475
+ return function (target) {
476
+ const proto = target.prototype;
477
+ const SPAN_KEY = '__allstak_trace_class_span__';
478
+ const originalOnInit = proto.ngOnInit;
479
+ const originalAfterViewInit = proto.ngAfterViewInit;
480
+ proto.ngOnInit = function () {
481
+ try {
482
+ this[SPAN_KEY] = AllStak.startSpan('ui.angular.init', {
483
+ op: 'ui.angular.init',
484
+ description: options.name,
485
+ attributes: {
486
+ 'allstak.origin': 'auto.ui.angular',
487
+ 'angular.component': options.name,
488
+ },
489
+ });
490
+ }
491
+ catch {
492
+ // ignore
493
+ }
494
+ originalOnInit?.call(this);
495
+ // If there is no ngAfterViewInit, close the span right after init.
496
+ if (!originalAfterViewInit && !proto.ngAfterViewInit) {
497
+ finishSpan(this[SPAN_KEY]);
498
+ this[SPAN_KEY] = undefined;
499
+ }
500
+ };
501
+ proto.ngAfterViewInit = function () {
502
+ originalAfterViewInit?.call(this);
503
+ finishSpan(this[SPAN_KEY]);
504
+ this[SPAN_KEY] = undefined;
505
+ };
506
+ return target;
507
+ };
508
+ }
509
+ /** Alias for the alternate naming. */
510
+ const TraceClass = TraceClassDecorator;
511
+ /**
512
+ * Method decorator emitting a point-in-time `ui.angular.[hook]` span around a
513
+ * single lifecycle method (e.g. `ngOnInit`, `ngOnChanges`, `ngAfterViewInit`).
514
+ *
515
+ * class Foo {
516
+ * @TraceMethodDecorator({ name: 'Foo.ngOnInit' })
517
+ * ngOnInit() {}
518
+ * }
519
+ */
520
+ function TraceMethodDecorator(options) {
521
+ return function (_target, propertyKey, descriptor) {
522
+ const original = descriptor.value;
523
+ descriptor.value = function (...args) {
524
+ let span = null;
525
+ try {
526
+ span = AllStak.startSpan(`ui.angular.${propertyKey}`, {
527
+ op: `ui.angular.${propertyKey}`,
528
+ description: options.name,
529
+ attributes: {
530
+ 'allstak.origin': 'auto.ui.angular',
531
+ 'angular.hook': propertyKey,
532
+ },
533
+ });
534
+ }
535
+ catch {
536
+ // ignore
537
+ }
538
+ try {
539
+ return original?.apply(this, args);
540
+ }
541
+ finally {
542
+ finishSpan(span ?? undefined);
543
+ }
544
+ };
545
+ return descriptor;
546
+ };
547
+ }
548
+ /** Alias for the alternate naming. */
549
+ const TraceMethod = TraceMethodDecorator;
550
+ function finishSpan(span) {
551
+ try {
552
+ span?.finish('ok');
553
+ }
554
+ catch {
555
+ // ignore — span may already be finished
556
+ }
557
+ }
558
+
559
+ /**
560
+ * Bootstrap the AllStak SDK from a standalone app's `appConfig.providers`.
561
+ *
562
+ * import { provideAllStak } from '@allstak/angular';
563
+ *
564
+ * export const appConfig: ApplicationConfig = {
565
+ * providers: [
566
+ * provideAllStak({ apiKey: environment.allstakApiKey }),
567
+ * ],
568
+ * };
569
+ *
570
+ * This calls {@link init} during DI bootstrap. For the global `ErrorHandler`
571
+ * to catch errors thrown *during bootstrap itself*, prefer calling `init(...)`
572
+ * directly in `main.ts` before `bootstrapApplication`; this provider is the
573
+ * convenient alternative for apps that initialise inside the DI graph.
574
+ */
575
+ function provideAllStak(config) {
576
+ return makeEnvironmentProviders([
577
+ {
578
+ provide: ENVIRONMENT_INITIALIZER,
579
+ multi: true,
580
+ useValue: () => {
581
+ init(config);
582
+ },
583
+ },
584
+ ]);
585
+ }
586
+ /**
587
+ * Provide the AllStak `ErrorHandler` (overriding Angular's default) so every
588
+ * error routed through Angular's global error handling is captured.
589
+ *
590
+ * providers: [provideAllStakErrorHandler()]
591
+ *
592
+ * Equivalent to `{ provide: ErrorHandler, useValue: createErrorHandler() }`.
593
+ */
594
+ function provideAllStakErrorHandler(options) {
595
+ return { provide: ErrorHandler, useValue: createErrorHandler(options) };
596
+ }
597
+ /**
598
+ * Register and force-instantiate {@link TraceService} so its Router
599
+ * subscription runs and a navigation span opens per route change.
600
+ *
601
+ * providers: [provideRouter(routes), provideAllStakRouterInstrumentation()]
602
+ *
603
+ * `TraceService` is `providedIn: 'root'`, but Angular only constructs a
604
+ * service when something injects it. The `APP_INITIALIZER` below injects it
605
+ * during bootstrap, guaranteeing the subscription is live before the first
606
+ * navigation completes.
607
+ */
608
+ function provideAllStakRouterInstrumentation() {
609
+ return makeEnvironmentProviders([
610
+ TraceService,
611
+ {
612
+ provide: APP_INITIALIZER,
613
+ multi: true,
614
+ useFactory: () => {
615
+ // Force-instantiate the service; the factory itself is a no-op.
616
+ inject(TraceService);
617
+ return () => undefined;
618
+ },
619
+ },
620
+ ]);
621
+ }
622
+
623
+ /** DI token carrying the config passed to {@link AllStakModule.forRoot}. */
624
+ const ALLSTAK_CONFIG = new InjectionToken('ALLSTAK_CONFIG');
625
+ /**
626
+ * NgModule that exposes {@link TraceDirective} for module-based apps (or
627
+ * standalone components that prefer NgModule-style `imports`):
628
+ *
629
+ * @NgModule({ imports: [AllStakTraceModule] })
630
+ * export class AppModule {}
631
+ *
632
+ * <app-checkout trace="checkout"></app-checkout>
633
+ *
634
+ * `TraceDirective` is a standalone directive, so it is imported (not declared)
635
+ * and re-exported.
636
+ */
637
+ class AllStakTraceModule {
638
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakTraceModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
639
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: AllStakTraceModule, imports: [TraceDirective], exports: [TraceDirective] });
640
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakTraceModule });
641
+ }
642
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakTraceModule, decorators: [{
643
+ type: NgModule,
644
+ args: [{
645
+ imports: [TraceDirective],
646
+ exports: [TraceDirective],
647
+ }]
648
+ }] });
649
+ /**
650
+ * Root NgModule for module-based Angular apps.
651
+ *
652
+ * import { AllStakModule } from '@allstak/angular';
653
+ *
654
+ * @NgModule({
655
+ * imports: [
656
+ * BrowserModule,
657
+ * AllStakModule.forRoot({ apiKey: environment.allstakApiKey }),
658
+ * ],
659
+ * })
660
+ * export class AppModule {}
661
+ *
662
+ * `forRoot(config)`:
663
+ * - calls {@link init} during bootstrap (via `APP_INITIALIZER`);
664
+ * - overrides the global `ErrorHandler` with the AllStak handler;
665
+ * - force-instantiates {@link TraceService} so router navigation spans open;
666
+ * - exposes {@link TraceDirective} via {@link AllStakTraceModule}.
667
+ *
668
+ * Call `forRoot` exactly once, in your root module. Feature modules that only
669
+ * need the `[trace]` directive can import {@link AllStakTraceModule} directly.
670
+ */
671
+ class AllStakModule {
672
+ // Inject TraceService here so importing the module force-instantiates it and
673
+ // its Router subscription runs. `@Optional` keeps router-less apps working.
674
+ constructor(_trace) { }
675
+ static forRoot(config) {
676
+ return {
677
+ ngModule: AllStakModule,
678
+ providers: [
679
+ { provide: ALLSTAK_CONFIG, useValue: config },
680
+ { provide: ErrorHandler, useValue: createErrorHandler() },
681
+ TraceService,
682
+ {
683
+ provide: APP_INITIALIZER,
684
+ multi: true,
685
+ useFactory: appInitializerFactory,
686
+ deps: [ALLSTAK_CONFIG, TraceService],
687
+ },
688
+ ],
689
+ };
690
+ }
691
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakModule, deps: [{ token: TraceService, optional: true }], target: i0.ɵɵFactoryTarget.NgModule });
692
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: AllStakModule, imports: [AllStakTraceModule], exports: [AllStakTraceModule] });
693
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakModule, imports: [AllStakTraceModule, AllStakTraceModule] });
694
+ }
695
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakModule, decorators: [{
696
+ type: NgModule,
697
+ args: [{
698
+ imports: [AllStakTraceModule],
699
+ exports: [AllStakTraceModule],
700
+ }]
701
+ }], ctorParameters: () => [{ type: TraceService, decorators: [{
702
+ type: Optional
703
+ }] }] });
704
+ /**
705
+ * `APP_INITIALIZER` factory: initialises the SDK from the injected config and
706
+ * touches `TraceService` so it is constructed (its constructor wires the
707
+ * Router subscription). Declared as a named function so ng-packagr's partial
708
+ * compiler can statically reference it (no arrow-in-decorator-metadata error).
709
+ */
710
+ function appInitializerFactory(config,
711
+ // TraceService is injected via `deps` purely to force its construction.
712
+ _trace) {
713
+ return () => {
714
+ init(config);
715
+ };
716
+ }
717
+
718
+ /**
719
+ * AllStak for Angular.
720
+ *
721
+ * Public surface:
722
+ * - `init` — bootstrap (delegates to @allstak/js with Angular tagging)
723
+ * - `createErrorHandler` / `AllStakErrorHandler` — Angular `ErrorHandler` forwarding to captureException
724
+ * - `AllStakHttpInterceptor` / `allStakHttpInterceptor` — records outbound requests + opens spans
725
+ * - `TraceService` — Router-aware navigation span instrumentation
726
+ * - `TraceDirective` / `AllStakTraceModule` — `[trace]` component render-timing directive
727
+ * - `TraceClassDecorator` / `TraceMethodDecorator` — lifecycle-span decorators
728
+ * - `provideAllStak` / `provideAllStakErrorHandler` / `provideAllStakRouterInstrumentation`
729
+ * — standalone provider functions
730
+ * - `AllStakModule.forRoot(config)` — NgModule registration for module-based apps
731
+ * - re-exports — every top-level @allstak/js export
732
+ */
733
+ // Bootstrap.
734
+
735
+ /**
736
+ * Generated bundle index. Do not edit.
737
+ */
738
+
739
+ export { ALLSTAK_CONFIG, AllStakErrorHandler, AllStakHttpInterceptor, AllStakModule, AllStakTraceModule, SDK_NAME, SDK_VERSION, TraceClass, TraceClassDecorator, TraceDirective, TraceMethod, TraceMethodDecorator, TraceService, allStakHttpInterceptor, appInitializerFactory, createErrorHandler, init, provideAllStak, provideAllStakErrorHandler, provideAllStakRouterInstrumentation };
740
+ //# sourceMappingURL=allstak-angular.mjs.map