@telperion/ng-pack 1.2.2 → 1.3.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
@@ -60,6 +60,77 @@ export class SettingsComponent {
60
60
 
61
61
  ---
62
62
 
63
+ ### SSE Client
64
+
65
+ **Import:** `@telperion/ng-pack/sse-client`
66
+
67
+ Angular service for Server-Sent Events (SSE) with RxJS Observables and HttpClient-inspired interceptors.
68
+
69
+ #### Key Features
70
+
71
+ - 🚀 Observable-based API integrated with RxJS ecosystem
72
+ - 🔗 HttpClient-inspired interceptor chain for request manipulation
73
+ - 🎯 Type-safe with full generic support
74
+ - ⚡ EventSource wrapper with automatic cleanup
75
+ - 🔄 Real-time streaming data updates
76
+ - 🎨 Feature-based configuration
77
+
78
+ #### Quick Start
79
+
80
+ ```typescript
81
+ import { ApplicationConfig } from '@angular/core';
82
+ import { provideSseClient, withSseInterceptors } from '@telperion/ng-pack/sse-client';
83
+
84
+ export const appConfig: ApplicationConfig = {
85
+ providers: [
86
+ provideSseClient(
87
+ withSseInterceptors(loggingInterceptor, authInterceptor)
88
+ ),
89
+ ]
90
+ };
91
+ ```
92
+
93
+ ```typescript
94
+ import { Component, inject } from '@angular/core';
95
+ import { SseClient } from '@telperion/ng-pack/sse-client';
96
+ import { AsyncPipe } from '@angular/common';
97
+
98
+ interface StockUpdate {
99
+ symbol: string;
100
+ price: number;
101
+ change: number;
102
+ }
103
+
104
+ @Component({
105
+ selector: 'app-stock-ticker',
106
+ template: `
107
+ <div>
108
+ <h2>Live Stock Prices</h2>
109
+ @if (stockUpdates$ | async; as update) {
110
+ <div class="stock-card">
111
+ <span>{{ update.symbol }}: \${{ update.price }}</span>
112
+ <span [class.positive]="update.change > 0">
113
+ {{ update.change > 0 ? '+' : '' }}{{ update.change }}%
114
+ </span>
115
+ </div>
116
+ }
117
+ </div>
118
+ `,
119
+ standalone: true,
120
+ imports: [AsyncPipe]
121
+ })
122
+ export class StockTickerComponent {
123
+ private sseClient = inject(SseClient);
124
+
125
+ // Start SSE connection and get Observable stream
126
+ stockUpdates$ = this.sseClient.start<StockUpdate>('/api/stocks/stream');
127
+ }
128
+ ```
129
+
130
+ [Full documentation →](./sse-client/README.md)
131
+
132
+ ---
133
+
63
134
  ### Template Signal Forms
64
135
 
65
136
  **Import:** `@telperion/ng-pack/template-signal-forms`
@@ -0,0 +1,152 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, Injectable } from '@angular/core';
3
+ import { Observable } from 'rxjs';
4
+
5
+ /**
6
+ * Enum identifying different types of SSE features.
7
+ */
8
+ var SseFeatureKind;
9
+ (function (SseFeatureKind) {
10
+ SseFeatureKind[SseFeatureKind["InterceptorFunction"] = 0] = "InterceptorFunction";
11
+ })(SseFeatureKind || (SseFeatureKind = {}));
12
+
13
+ /**
14
+ * Configures functional interceptors for the SSE client.
15
+ * Interceptors are executed in the order they are provided.
16
+ *
17
+ * @param interceptors - One or more functional interceptor functions
18
+ * @returns Array of SSE features to be used with provideSseClient()
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const loggingInterceptor: SseInterceptorFn<any> = (req, next) => {
23
+ * console.log('Connecting to:', req.url);
24
+ * return next(req);
25
+ * };
26
+ *
27
+ * provideSseClient(
28
+ * withSseInterceptors(loggingInterceptor)
29
+ * )
30
+ * ```
31
+ */
32
+ function withSseInterceptors(...interceptors) {
33
+ return interceptors.map(interceptor => ({
34
+ kind: SseFeatureKind.InterceptorFunction,
35
+ interceptor,
36
+ }));
37
+ }
38
+
39
+ /**
40
+ * Injection token for providing SSE interceptors.
41
+ * Use with multi: true to provide multiple interceptors.
42
+ */
43
+ const SSE_INTERCEPTORS = new InjectionToken('Telperion/SSE_INTERCEPTORS');
44
+
45
+ /**
46
+ * Angular service for managing Server-Sent Events (SSE) connections.
47
+ * Wraps the EventSource API with RxJS Observables and an HttpClient-inspired interceptor chain.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * class MyComponent {
52
+ * private sseClient = inject(SseClient);
53
+ *
54
+ * messages$ = this.sseClient.start<string>('/api/events');
55
+ * }
56
+ * ```
57
+ */
58
+ class SseClient {
59
+ #interceptors = inject(SSE_INTERCEPTORS, { optional: true }) ?? [];
60
+ constructor() {
61
+ if (!(this.#interceptors instanceof Array)) {
62
+ throw new Error('SSE_INTERCEPTORS must be provided as an array. Please ensure you are using multi: true when providing interceptors.');
63
+ }
64
+ }
65
+ /**
66
+ * Starts a new Server-Sent Events connection and returns an Observable stream.
67
+ *
68
+ * @param url - The URL endpoint for the SSE connection
69
+ * @param init - Optional EventSource initialization configuration
70
+ * @returns Observable stream of server-sent events
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * this.sseClient.start<MessageData>('/api/notifications', { withCredentials: true })
75
+ * .subscribe(data => console.log('Received:', data));
76
+ * ```
77
+ */
78
+ start(url, init = {}) {
79
+ return this.#interceptors.reduceRight((next, interceptor) => (req) => interceptor.sseIntercept(req, next), (req) => this.#createEventSource(req))({ url, init });
80
+ }
81
+ #createEventSource(request) {
82
+ return new Observable((subscriber) => {
83
+ const eventSource = new EventSource(request.url, request.init);
84
+ function handleMessage(event) {
85
+ subscriber.next(event.data);
86
+ }
87
+ function handleError(err) {
88
+ subscriber.error(err);
89
+ eventSource.close();
90
+ }
91
+ eventSource.addEventListener('message', handleMessage);
92
+ eventSource.addEventListener('error', handleError);
93
+ return () => {
94
+ eventSource.removeEventListener('message', handleMessage);
95
+ eventSource.removeEventListener('error', handleError);
96
+ eventSource.close();
97
+ };
98
+ });
99
+ }
100
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SseClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
101
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SseClient });
102
+ }
103
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SseClient, decorators: [{
104
+ type: Injectable
105
+ }], ctorParameters: () => [] });
106
+
107
+ /**
108
+ * Provides the SseClient service with optional features.
109
+ * Configure SSE client with interceptors and other features using a functional API.
110
+ *
111
+ * @param features - Optional feature configurations (e.g., withSseInterceptors())
112
+ * @returns Array of Angular providers
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * import { provideSseClient, withSseInterceptors } from '@telperion/ng-pack/sse-client';
117
+ *
118
+ * export const appConfig: ApplicationConfig = {
119
+ * providers: [
120
+ * provideSseClient(
121
+ * withSseInterceptors(loggingInterceptor, authInterceptor)
122
+ * )
123
+ * ]
124
+ * };
125
+ * ```
126
+ */
127
+ function provideSseClient(...features) {
128
+ const interceptorFns = features
129
+ .flat()
130
+ .filter(feature => feature.kind === SseFeatureKind.InterceptorFunction)
131
+ .map(feature => ({
132
+ provide: SSE_INTERCEPTORS,
133
+ useValue: {
134
+ sseIntercept: feature.interceptor,
135
+ },
136
+ multi: true,
137
+ }));
138
+ return [
139
+ {
140
+ provide: SseClient,
141
+ useClass: SseClient
142
+ },
143
+ ...interceptorFns
144
+ ];
145
+ }
146
+
147
+ /**
148
+ * Generated bundle index. Do not edit.
149
+ */
150
+
151
+ export { SSE_INTERCEPTORS, SseClient, SseFeatureKind, provideSseClient, withSseInterceptors };
152
+ //# sourceMappingURL=telperion-ng-pack-sse-client.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telperion-ng-pack-sse-client.mjs","sources":["../../sse-client/src/features/feature.ts","../../sse-client/src/features/with-interceptors.ts","../../sse-client/src/interceptor.ts","../../sse-client/src/client.ts","../../sse-client/src/provider.ts","../../sse-client/src/telperion-ng-pack-sse-client.ts"],"sourcesContent":["/**\r\n * Enum identifying different types of SSE features.\r\n */\r\nexport enum SseFeatureKind {\r\n InterceptorFunction\r\n}\r\n\r\n/**\r\n * Base interface for SSE feature configurations.\r\n */\r\nexport interface SseFeature {\r\n kind: SseFeatureKind;\r\n}\r\n","import { SseInterceptorFn } from \"../interceptor\";\r\nimport { SseFeature, SseFeatureKind } from \"./feature\";\r\n\r\n/**\r\n * Feature interface for functional interceptors.\r\n * @internal\r\n */\r\nexport interface SseInterceptorFunctionFeature extends SseFeature {\r\n kind: SseFeatureKind.InterceptorFunction;\r\n interceptor: SseInterceptorFn<unknown>;\r\n}\r\n\r\n/**\r\n * Configures functional interceptors for the SSE client.\r\n * Interceptors are executed in the order they are provided.\r\n *\r\n * @param interceptors - One or more functional interceptor functions\r\n * @returns Array of SSE features to be used with provideSseClient()\r\n *\r\n * @example\r\n * ```typescript\r\n * const loggingInterceptor: SseInterceptorFn<any> = (req, next) => {\r\n * console.log('Connecting to:', req.url);\r\n * return next(req);\r\n * };\r\n *\r\n * provideSseClient(\r\n * withSseInterceptors(loggingInterceptor)\r\n * )\r\n * ```\r\n */\r\nexport function withSseInterceptors<T = unknown>(...interceptors: SseInterceptorFn<T>[]): SseFeature[] {\r\n return interceptors.map(interceptor => ({\r\n kind: SseFeatureKind.InterceptorFunction,\r\n interceptor,\r\n }));\r\n}\r\n","import { InjectionToken } from \"@angular/core\";\r\nimport type { Observable } from \"rxjs\";\r\n\r\n/**\r\n * Represents an SSE request with URL and initialization options.\r\n */\r\nexport interface SseRequest {\r\n url: string;\r\n init: EventSourceInit;\r\n}\r\n\r\n/**\r\n * Function type for passing the request to the next handler in the interceptor chain.\r\n */\r\nexport type SseNextFn<T> = (request: SseRequest) => Observable<T>;\r\n\r\n/**\r\n * Functional interceptor type for intercepting SSE requests.\r\n * Interceptors can modify requests, handle errors, add logging, etc.\r\n *\r\n * @example\r\n * ```typescript\r\n * const loggingInterceptor: SseInterceptorFn<any> = (req, next) => {\r\n * console.log('SSE Request:', req.url);\r\n * return next(req);\r\n * };\r\n * ```\r\n */\r\nexport type SseInterceptorFn<T> = (\r\n request: SseRequest,\r\n next: SseNextFn<T>\r\n) => Observable<T>;\r\n\r\n/**\r\n * Class-based interceptor interface for SSE requests.\r\n * Implements the same pattern as Angular's HttpInterceptor.\r\n */\r\nexport interface SseInterceptor<T = unknown> {\r\n sseIntercept<U = T>(...args: Parameters<SseInterceptorFn<U>>): ReturnType<SseInterceptorFn<U>>;\r\n}\r\n\r\n/**\r\n * Injection token for providing SSE interceptors.\r\n * Use with multi: true to provide multiple interceptors.\r\n */\r\nexport const SSE_INTERCEPTORS = new InjectionToken<SseInterceptor<unknown>[]>('Telperion/SSE_INTERCEPTORS');\r\n","import { inject, Injectable } from \"@angular/core\";\r\nimport { Observable } from \"rxjs\";\r\n\r\nimport { SSE_INTERCEPTORS, SseRequest } from \"./interceptor\";\r\n\r\n/**\r\n * Angular service for managing Server-Sent Events (SSE) connections.\r\n * Wraps the EventSource API with RxJS Observables and an HttpClient-inspired interceptor chain.\r\n *\r\n * @example\r\n * ```typescript\r\n * class MyComponent {\r\n * private sseClient = inject(SseClient);\r\n *\r\n * messages$ = this.sseClient.start<string>('/api/events');\r\n * }\r\n * ```\r\n */\r\n@Injectable()\r\nexport class SseClient {\r\n #interceptors = inject(SSE_INTERCEPTORS, { optional: true }) ?? [];\r\n\r\n constructor() {\r\n if (!(this.#interceptors instanceof Array)) {\r\n throw new Error('SSE_INTERCEPTORS must be provided as an array. Please ensure you are using multi: true when providing interceptors.');\r\n }\r\n }\r\n\r\n /**\r\n * Starts a new Server-Sent Events connection and returns an Observable stream.\r\n *\r\n * @param url - The URL endpoint for the SSE connection\r\n * @param init - Optional EventSource initialization configuration\r\n * @returns Observable stream of server-sent events\r\n *\r\n * @example\r\n * ```typescript\r\n * this.sseClient.start<MessageData>('/api/notifications', { withCredentials: true })\r\n * .subscribe(data => console.log('Received:', data));\r\n * ```\r\n */\r\n start<T>(url: string, init: EventSourceInit = {}): Observable<T> {\r\n return this.#interceptors.reduceRight(\r\n (next, interceptor) => (req) => interceptor.sseIntercept(req, next),\r\n (req: SseRequest) => this.#createEventSource(req) as Observable<T>\r\n )({ url, init });\r\n }\r\n\r\n #createEventSource<T>(request: SseRequest): Observable<T> {\r\n return new Observable<T>((subscriber) => {\r\n const eventSource = new EventSource(request.url, request.init);\r\n\r\n function handleMessage(event: MessageEvent) {\r\n subscriber.next(event.data);\r\n }\r\n\r\n function handleError(err: Event) {\r\n subscriber.error(err);\r\n eventSource.close();\r\n }\r\n\r\n eventSource.addEventListener('message', handleMessage);\r\n eventSource.addEventListener('error', handleError);\r\n\r\n return () => {\r\n eventSource.removeEventListener('message', handleMessage);\r\n eventSource.removeEventListener('error', handleError);\r\n eventSource.close();\r\n };\r\n });\r\n }\r\n}\r\n","import { Provider } from \"@angular/core\";\r\n\r\nimport { SseClient } from \"./client\";\r\nimport { SseFeature, SseFeatureKind, SseInterceptorFunctionFeature } from \"./features\";\r\nimport { SSE_INTERCEPTORS } from \"./interceptor\";\r\n\r\n/**\r\n * Provides the SseClient service with optional features.\r\n * Configure SSE client with interceptors and other features using a functional API.\r\n *\r\n * @param features - Optional feature configurations (e.g., withSseInterceptors())\r\n * @returns Array of Angular providers\r\n *\r\n * @example\r\n * ```typescript\r\n * import { provideSseClient, withSseInterceptors } from '@telperion/ng-pack/sse-client';\r\n *\r\n * export const appConfig: ApplicationConfig = {\r\n * providers: [\r\n * provideSseClient(\r\n * withSseInterceptors(loggingInterceptor, authInterceptor)\r\n * )\r\n * ]\r\n * };\r\n * ```\r\n */\r\nexport function provideSseClient(...features: SseFeature[][]): Provider[] {\r\n const interceptorFns = features\r\n .flat()\r\n .filter(feature => feature.kind === SseFeatureKind.InterceptorFunction)\r\n .map(feature => ({\r\n provide: SSE_INTERCEPTORS,\r\n useValue: {\r\n sseIntercept: (feature as SseInterceptorFunctionFeature).interceptor,\r\n },\r\n multi: true,\r\n }));\r\n\r\n return [\r\n {\r\n provide: SseClient,\r\n useClass: SseClient\r\n },\r\n ...interceptorFns\r\n ];\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telperion/ng-pack",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^21.0.0",
6
6
  "@angular/core": "^21.0.0",
@@ -40,6 +40,10 @@
40
40
  "types": "./types/telperion-ng-pack.d.ts",
41
41
  "default": "./fesm2022/telperion-ng-pack.mjs"
42
42
  },
43
+ "./sse-client": {
44
+ "types": "./types/telperion-ng-pack-sse-client.d.ts",
45
+ "default": "./fesm2022/telperion-ng-pack-sse-client.mjs"
46
+ },
43
47
  "./storage-signals": {
44
48
  "types": "./types/telperion-ng-pack-storage-signals.d.ts",
45
49
  "default": "./fesm2022/telperion-ng-pack-storage-signals.mjs"
@@ -0,0 +1,575 @@
1
+ # @telperion/ng-pack/sse-client
2
+
3
+ Angular service for Server-Sent Events (SSE) with RxJS Observables and HttpClient-inspired interceptors.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Observable-based API** - Seamlessly integrate with RxJS ecosystem
8
+ - 🔗 **HttpClient-inspired interceptors** - Familiar pattern for request/response manipulation
9
+ - 🎯 **Type-safe** - Full TypeScript support with generic typing
10
+ - ⚡ **EventSource wrapper** - Clean abstraction over native EventSource API
11
+ - 🔄 **Reactive streaming** - Real-time data updates with automatic cleanup
12
+ - 🎨 **Feature-based configuration** - Extensible architecture following modern Angular patterns
13
+ - 🧹 **Automatic cleanup** - Proper resource management on unsubscribe
14
+
15
+ ## Installation
16
+
17
+ This is a secondary entry point of `@telperion/ng-pack`. Import from `@telperion/ng-pack/sse-client`.
18
+
19
+ ```bash
20
+ npm install @telperion/ng-pack
21
+ ```
22
+
23
+ ## Setup
24
+
25
+ Configure the SSE client in your application config:
26
+
27
+ ```typescript
28
+ import { ApplicationConfig } from '@angular/core';
29
+ import { provideSseClient } from '@telperion/ng-pack/sse-client';
30
+
31
+ export const appConfig: ApplicationConfig = {
32
+ providers: [
33
+ provideSseClient(),
34
+ // ... other providers
35
+ ]
36
+ };
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ### Basic Usage
42
+
43
+ Create a component that connects to an SSE endpoint and displays real-time data:
44
+
45
+ ```typescript
46
+ import { Component, inject } from '@angular/core';
47
+ import { SseClient } from '@telperion/ng-pack/sse-client';
48
+ import { AsyncPipe } from '@angular/common';
49
+
50
+ interface StockUpdate {
51
+ symbol: string;
52
+ price: number;
53
+ change: number;
54
+ }
55
+
56
+ @Component({
57
+ selector: 'app-stock-ticker',
58
+ template: `
59
+ <div class="stock-ticker">
60
+ <h2>Live Stock Prices</h2>
61
+ @if (stockUpdates$ | async; as update) {
62
+ <div class="stock-card">
63
+ <span class="symbol">{{ update.symbol }}</span>
64
+ <span class="price">\${{ update.price }}</span>
65
+ <span [class.positive]="update.change > 0" [class.negative]="update.change < 0">
66
+ {{ update.change > 0 ? '+' : '' }}{{ update.change }}%
67
+ </span>
68
+ </div>
69
+ }
70
+ </div>
71
+ `,
72
+ standalone: true,
73
+ imports: [AsyncPipe]
74
+ })
75
+ export class StockTickerComponent {
76
+ private sseClient = inject(SseClient);
77
+
78
+ // Start SSE connection and get Observable stream
79
+ stockUpdates$ = this.sseClient.start<StockUpdate>('/api/stocks/stream');
80
+ }
81
+ ```
82
+
83
+ ### Using Interceptors
84
+
85
+ Interceptors allow you to modify requests, add logging, handle authentication, or implement retry logic. They work just like Angular's HttpClient interceptors.
86
+
87
+ #### Logging Interceptor
88
+
89
+ ```typescript
90
+ import { SseInterceptorFn } from '@telperion/ng-pack/sse-client';
91
+ import { tap } from 'rxjs/operators';
92
+
93
+ export const loggingInterceptor: SseInterceptorFn<any> = (req, next) => {
94
+ console.log('[SSE] Connecting to:', req.url);
95
+
96
+ return next(req).pipe(
97
+ tap({
98
+ next: data => console.log('[SSE] Received:', data),
99
+ error: err => console.error('[SSE] Error:', err),
100
+ complete: () => console.log('[SSE] Connection closed')
101
+ })
102
+ );
103
+ };
104
+ ```
105
+
106
+ #### Authentication Interceptor
107
+
108
+ ```typescript
109
+ import { SseInterceptorFn } from '@telperion/ng-pack/sse-client';
110
+
111
+ export const authInterceptor: SseInterceptorFn<any> = (req, next) => {
112
+ // Add authentication token to URL or modify request
113
+ const token = localStorage.getItem('auth_token');
114
+ const authenticatedUrl = `${req.url}?token=${token}`;
115
+
116
+ return next({
117
+ ...req,
118
+ url: authenticatedUrl
119
+ });
120
+ };
121
+ ```
122
+
123
+ #### Retry Interceptor
124
+
125
+ ```typescript
126
+ import { SseInterceptorFn } from '@telperion/ng-pack/sse-client';
127
+ import { retry, timer } from 'rxjs';
128
+
129
+ export const retryInterceptor: SseInterceptorFn<any> = (req, next) => {
130
+ return next(req).pipe(
131
+ retry({
132
+ count: 3,
133
+ delay: (error, retryCount) => {
134
+ console.log(`[SSE] Retry attempt ${retryCount} after error:`, error);
135
+ return timer(1000 * retryCount); // Exponential backoff
136
+ }
137
+ })
138
+ );
139
+ };
140
+ ```
141
+
142
+ #### Configuring Interceptors
143
+
144
+ Provide interceptors using the `withSseInterceptors()` feature function:
145
+
146
+ ```typescript
147
+ import { ApplicationConfig } from '@angular/core';
148
+ import { provideSseClient, withSseInterceptors } from '@telperion/ng-pack/sse-client';
149
+ import { loggingInterceptor, authInterceptor, retryInterceptor } from './interceptors';
150
+
151
+ export const appConfig: ApplicationConfig = {
152
+ providers: [
153
+ provideSseClient(
154
+ withSseInterceptors(
155
+ loggingInterceptor,
156
+ authInterceptor,
157
+ retryInterceptor
158
+ )
159
+ )
160
+ ]
161
+ };
162
+ ```
163
+
164
+ **Interceptor execution order:** Interceptors are executed in the order they are provided. In the example above, the chain is: logging → auth → retry → EventSource connection.
165
+
166
+ ### Advanced Usage
167
+
168
+ #### Real-time Notifications
169
+
170
+ ```typescript
171
+ import { Component, inject } from '@angular/core';
172
+ import { SseClient } from '@telperion/ng-pack/sse-client';
173
+ import { AsyncPipe } from '@angular/common';
174
+
175
+ interface Notification {
176
+ id: string;
177
+ message: string;
178
+ type: 'info' | 'warning' | 'error';
179
+ timestamp: number;
180
+ }
181
+
182
+ @Component({
183
+ selector: 'app-notifications',
184
+ template: `
185
+ <div class="notifications">
186
+ @for (notification of notifications$ | async; track notification.id) {
187
+ <div [class]="'notification ' + notification.type">
188
+ <span class="message">{{ notification.message }}</span>
189
+ <span class="time">{{ notification.timestamp | date:'short' }}</span>
190
+ </div>
191
+ }
192
+ </div>
193
+ `,
194
+ standalone: true,
195
+ imports: [AsyncPipe]
196
+ })
197
+ export class NotificationsComponent {
198
+ private sseClient = inject(SseClient);
199
+
200
+ notifications$ = this.sseClient.start<Notification>('/api/notifications/stream');
201
+ }
202
+ ```
203
+
204
+ #### Managing Subscriptions
205
+
206
+ Always unsubscribe from SSE connections when they're no longer needed:
207
+
208
+ ```typescript
209
+ import { Component, inject, OnDestroy } from '@angular/core';
210
+ import { SseClient } from '@telperion/ng-pack/sse-client';
211
+ import { Subscription } from 'rxjs';
212
+
213
+ @Component({
214
+ selector: 'app-dashboard',
215
+ template: `
216
+ <div>
217
+ <h2>Dashboard</h2>
218
+ <p>Active users: {{ activeUsers }}</p>
219
+ </div>
220
+ `
221
+ })
222
+ export class DashboardComponent implements OnDestroy {
223
+ private sseClient = inject(SseClient);
224
+ private subscription?: Subscription;
225
+
226
+ activeUsers = 0;
227
+
228
+ ngOnInit() {
229
+ // Subscribe manually for more control
230
+ this.subscription = this.sseClient
231
+ .start<{ count: number }>('/api/dashboard/active-users')
232
+ .subscribe({
233
+ next: data => this.activeUsers = data.count,
234
+ error: err => console.error('SSE error:', err)
235
+ });
236
+ }
237
+
238
+ ngOnDestroy() {
239
+ // Clean up subscription and close EventSource
240
+ this.subscription?.unsubscribe();
241
+ }
242
+ }
243
+ ```
244
+
245
+ **Best practice:** Use the `AsyncPipe` when possible to automatically handle subscription management.
246
+
247
+ #### Error Handling
248
+
249
+ ```typescript
250
+ import { Component, inject } from '@angular/core';
251
+ import { SseClient } from '@telperion/ng-pack/sse-client';
252
+ import { catchError, of } from 'rxjs';
253
+
254
+ @Component({
255
+ selector: 'app-feed',
256
+ template: `
257
+ <div>
258
+ @if (error) {
259
+ <div class="error">{{ error }}</div>
260
+ } @else {
261
+ <div>{{ data$ | async }}</div>
262
+ }
263
+ </div>
264
+ `
265
+ })
266
+ export class FeedComponent {
267
+ private sseClient = inject(SseClient);
268
+ error?: string;
269
+
270
+ data$ = this.sseClient.start('/api/feed').pipe(
271
+ catchError(err => {
272
+ this.error = 'Failed to connect to live feed';
273
+ console.error('SSE connection error:', err);
274
+ return of(null);
275
+ })
276
+ );
277
+ }
278
+ ```
279
+
280
+ #### Custom EventSource Configuration
281
+
282
+ Pass EventSource initialization options to configure credentials, CORS, etc.:
283
+
284
+ ```typescript
285
+ @Component({
286
+ selector: 'app-secure-feed',
287
+ template: `<div>{{ data$ | async }}</div>`
288
+ })
289
+ export class SecureFeedComponent {
290
+ private sseClient = inject(SseClient);
291
+
292
+ data$ = this.sseClient.start('/api/secure/feed', {
293
+ withCredentials: true // Send cookies with the request
294
+ });
295
+ }
296
+ ```
297
+
298
+ ## API Reference
299
+
300
+ ### `provideSseClient(...features: SseFeature[][]): Provider[]`
301
+
302
+ Provides the SSE client service with optional feature configurations.
303
+
304
+ **Parameters:**
305
+ - `features` (optional) - Feature configurations such as `withSseInterceptors()`
306
+
307
+ **Returns:** Array of Angular providers
308
+
309
+ **Example:**
310
+ ```typescript
311
+ provideSseClient(
312
+ withSseInterceptors(loggingInterceptor, authInterceptor)
313
+ )
314
+ ```
315
+
316
+ ---
317
+
318
+ ### `SseClient`
319
+
320
+ Injectable service for managing SSE connections.
321
+
322
+ #### `start<T>(url: string, init?: EventSourceInit): Observable<T>`
323
+
324
+ Starts a new Server-Sent Events connection and returns an Observable stream.
325
+
326
+ **Parameters:**
327
+ - `url` - The endpoint URL for the SSE connection
328
+ - `init` (optional) - EventSource initialization options (e.g., `{ withCredentials: true }`)
329
+
330
+ **Returns:** Observable stream of typed server-sent events
331
+
332
+ **Example:**
333
+ ```typescript
334
+ sseClient.start<MessageData>('/api/events', { withCredentials: true })
335
+ .subscribe(data => console.log('Received:', data));
336
+ ```
337
+
338
+ ---
339
+
340
+ ### `withSseInterceptors(...interceptors: SseInterceptorFn<T>[]): SseFeature[]`
341
+
342
+ Configures functional interceptors for the SSE client.
343
+
344
+ **Parameters:**
345
+ - `interceptors` - One or more functional interceptor functions
346
+
347
+ **Returns:** Array of SSE features to be used with `provideSseClient()`
348
+
349
+ **Example:**
350
+ ```typescript
351
+ withSseInterceptors(
352
+ loggingInterceptor,
353
+ authInterceptor,
354
+ retryInterceptor
355
+ )
356
+ ```
357
+
358
+ ---
359
+
360
+ ### `SseInterceptorFn<T>`
361
+
362
+ Type definition for functional interceptors.
363
+
364
+ ```typescript
365
+ type SseInterceptorFn<T> = (
366
+ request: SseRequest,
367
+ next: SseNextFn<T>
368
+ ) => Observable<T>;
369
+ ```
370
+
371
+ **Parameters:**
372
+ - `request` - The SSE request containing `url` and `init`
373
+ - `next` - Function to call the next interceptor in the chain
374
+
375
+ **Returns:** Observable of the intercepted request
376
+
377
+ ---
378
+
379
+ ### `SseRequest`
380
+
381
+ Interface representing an SSE request.
382
+
383
+ ```typescript
384
+ interface SseRequest {
385
+ url: string;
386
+ init: EventSourceInit;
387
+ }
388
+ ```
389
+
390
+ ---
391
+
392
+ ### `SseInterceptor<T>`
393
+
394
+ Class-based interceptor interface (for advanced use cases).
395
+
396
+ ```typescript
397
+ interface SseInterceptor<T = unknown> {
398
+ sseIntercept<U = T>(
399
+ request: SseRequest,
400
+ next: SseNextFn<U>
401
+ ): Observable<U>;
402
+ }
403
+ ```
404
+
405
+ ## Best Practices
406
+
407
+ ### 1. Always Unsubscribe
408
+
409
+ Use the `AsyncPipe` or manually unsubscribe to prevent memory leaks and close connections:
410
+
411
+ ```typescript
412
+ // ✅ Good - using AsyncPipe (automatic cleanup)
413
+ data$ = this.sseClient.start('/api/feed');
414
+
415
+ // ✅ Good - manual subscription with cleanup
416
+ ngOnInit() {
417
+ this.subscription = this.sseClient.start('/api/feed').subscribe(...);
418
+ }
419
+
420
+ ngOnDestroy() {
421
+ this.subscription?.unsubscribe();
422
+ }
423
+
424
+ // ❌ Bad - no cleanup
425
+ ngOnInit() {
426
+ this.sseClient.start('/api/feed').subscribe(...);
427
+ }
428
+ ```
429
+
430
+ ### 2. Handle Errors Gracefully
431
+
432
+ Always implement error handling for SSE connections:
433
+
434
+ ```typescript
435
+ data$ = this.sseClient.start('/api/feed').pipe(
436
+ catchError(err => {
437
+ console.error('SSE error:', err);
438
+ // Return fallback value or retry
439
+ return of(null);
440
+ })
441
+ );
442
+ ```
443
+
444
+ ### 3. Use Type Safety
445
+
446
+ Leverage TypeScript generics for type-safe event data:
447
+
448
+ ```typescript
449
+ // ✅ Good - typed
450
+ interface StockUpdate {
451
+ symbol: string;
452
+ price: number;
453
+ }
454
+ data$ = this.sseClient.start<StockUpdate>('/api/stocks');
455
+
456
+ // ❌ Bad - untyped
457
+ data$ = this.sseClient.start('/api/stocks');
458
+ ```
459
+
460
+ ### 4. Interceptor Order Matters
461
+
462
+ Place interceptors in logical order. Generally: logging → authentication → error handling → retry:
463
+
464
+ ```typescript
465
+ provideSseClient(
466
+ withSseInterceptors(
467
+ loggingInterceptor, // First: log everything
468
+ authInterceptor, // Second: add auth
469
+ errorHandlerInterceptor, // Third: handle errors
470
+ retryInterceptor // Last: retry failed connections
471
+ )
472
+ )
473
+ ```
474
+
475
+ ### 5. Consider Reconnection Strategies
476
+
477
+ For production applications, implement retry logic with exponential backoff:
478
+
479
+ ```typescript
480
+ const retryInterceptor: SseInterceptorFn<any> = (req, next) => {
481
+ return next(req).pipe(
482
+ retry({
483
+ count: 5,
484
+ delay: (error, retryCount) => timer(Math.min(1000 * Math.pow(2, retryCount), 30000))
485
+ })
486
+ );
487
+ };
488
+ ```
489
+
490
+ ### 6. Use Environment-Specific Configuration
491
+
492
+ Configure different interceptors for development and production:
493
+
494
+ ```typescript
495
+ // app.config.ts
496
+ const interceptors = environment.production
497
+ ? [authInterceptor, retryInterceptor]
498
+ : [loggingInterceptor, authInterceptor, retryInterceptor];
499
+
500
+ export const appConfig: ApplicationConfig = {
501
+ providers: [
502
+ provideSseClient(withSseInterceptors(...interceptors))
503
+ ]
504
+ };
505
+ ```
506
+
507
+ ## Common Use Cases
508
+
509
+ ### Real-time Chat
510
+
511
+ ```typescript
512
+ interface ChatMessage {
513
+ id: string;
514
+ user: string;
515
+ message: string;
516
+ timestamp: number;
517
+ }
518
+
519
+ messages$ = this.sseClient.start<ChatMessage>('/api/chat/stream');
520
+ ```
521
+
522
+ ### Live Dashboard Metrics
523
+
524
+ ```typescript
525
+ interface Metrics {
526
+ cpu: number;
527
+ memory: number;
528
+ activeUsers: number;
529
+ }
530
+
531
+ metrics$ = this.sseClient.start<Metrics>('/api/dashboard/metrics');
532
+ ```
533
+
534
+ ### Stock Price Updates
535
+
536
+ ```typescript
537
+ interface StockPrice {
538
+ symbol: string;
539
+ price: number;
540
+ volume: number;
541
+ }
542
+
543
+ stocks$ = this.sseClient.start<StockPrice>('/api/stocks/live');
544
+ ```
545
+
546
+ ### Server Logs Streaming
547
+
548
+ ```typescript
549
+ interface LogEntry {
550
+ level: 'info' | 'warn' | 'error';
551
+ message: string;
552
+ timestamp: number;
553
+ }
554
+
555
+ logs$ = this.sseClient.start<LogEntry>('/api/logs/stream');
556
+ ```
557
+
558
+ ## Comparison with HttpClient
559
+
560
+ The SSE client deliberately mirrors Angular's HttpClient pattern:
561
+
562
+ | Feature | HttpClient | SseClient |
563
+ |---------|------------|-----------|
564
+ | **Return Type** | `Observable` | `Observable` |
565
+ | **Interceptors** | `HttpInterceptor` | `SseInterceptor` |
566
+ | **Provider** | `provideHttpClient()` | `provideSseClient()` |
567
+ | **Features** | `withInterceptors()` | `withSseInterceptors()` |
568
+ | **Connection** | Request/Response | Persistent stream |
569
+ | **Use Case** | REST APIs | Real-time updates |
570
+
571
+ This familiar pattern makes it easy for Angular developers to adopt SSE for real-time features.
572
+
573
+ ## License
574
+
575
+ This library is part of the Telperion monorepo.
@@ -0,0 +1,141 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, Provider } from '@angular/core';
3
+ import { Observable } from 'rxjs';
4
+
5
+ /**
6
+ * Enum identifying different types of SSE features.
7
+ */
8
+ declare enum SseFeatureKind {
9
+ InterceptorFunction = 0
10
+ }
11
+ /**
12
+ * Base interface for SSE feature configurations.
13
+ */
14
+ interface SseFeature {
15
+ kind: SseFeatureKind;
16
+ }
17
+
18
+ /**
19
+ * Represents an SSE request with URL and initialization options.
20
+ */
21
+ interface SseRequest {
22
+ url: string;
23
+ init: EventSourceInit;
24
+ }
25
+ /**
26
+ * Function type for passing the request to the next handler in the interceptor chain.
27
+ */
28
+ type SseNextFn<T> = (request: SseRequest) => Observable<T>;
29
+ /**
30
+ * Functional interceptor type for intercepting SSE requests.
31
+ * Interceptors can modify requests, handle errors, add logging, etc.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const loggingInterceptor: SseInterceptorFn<any> = (req, next) => {
36
+ * console.log('SSE Request:', req.url);
37
+ * return next(req);
38
+ * };
39
+ * ```
40
+ */
41
+ type SseInterceptorFn<T> = (request: SseRequest, next: SseNextFn<T>) => Observable<T>;
42
+ /**
43
+ * Class-based interceptor interface for SSE requests.
44
+ * Implements the same pattern as Angular's HttpInterceptor.
45
+ */
46
+ interface SseInterceptor<T = unknown> {
47
+ sseIntercept<U = T>(...args: Parameters<SseInterceptorFn<U>>): ReturnType<SseInterceptorFn<U>>;
48
+ }
49
+ /**
50
+ * Injection token for providing SSE interceptors.
51
+ * Use with multi: true to provide multiple interceptors.
52
+ */
53
+ declare const SSE_INTERCEPTORS: InjectionToken<SseInterceptor<unknown>[]>;
54
+
55
+ /**
56
+ * Feature interface for functional interceptors.
57
+ * @internal
58
+ */
59
+ interface SseInterceptorFunctionFeature extends SseFeature {
60
+ kind: SseFeatureKind.InterceptorFunction;
61
+ interceptor: SseInterceptorFn<unknown>;
62
+ }
63
+ /**
64
+ * Configures functional interceptors for the SSE client.
65
+ * Interceptors are executed in the order they are provided.
66
+ *
67
+ * @param interceptors - One or more functional interceptor functions
68
+ * @returns Array of SSE features to be used with provideSseClient()
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const loggingInterceptor: SseInterceptorFn<any> = (req, next) => {
73
+ * console.log('Connecting to:', req.url);
74
+ * return next(req);
75
+ * };
76
+ *
77
+ * provideSseClient(
78
+ * withSseInterceptors(loggingInterceptor)
79
+ * )
80
+ * ```
81
+ */
82
+ declare function withSseInterceptors<T = unknown>(...interceptors: SseInterceptorFn<T>[]): SseFeature[];
83
+
84
+ /**
85
+ * Angular service for managing Server-Sent Events (SSE) connections.
86
+ * Wraps the EventSource API with RxJS Observables and an HttpClient-inspired interceptor chain.
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * class MyComponent {
91
+ * private sseClient = inject(SseClient);
92
+ *
93
+ * messages$ = this.sseClient.start<string>('/api/events');
94
+ * }
95
+ * ```
96
+ */
97
+ declare class SseClient {
98
+ #private;
99
+ constructor();
100
+ /**
101
+ * Starts a new Server-Sent Events connection and returns an Observable stream.
102
+ *
103
+ * @param url - The URL endpoint for the SSE connection
104
+ * @param init - Optional EventSource initialization configuration
105
+ * @returns Observable stream of server-sent events
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * this.sseClient.start<MessageData>('/api/notifications', { withCredentials: true })
110
+ * .subscribe(data => console.log('Received:', data));
111
+ * ```
112
+ */
113
+ start<T>(url: string, init?: EventSourceInit): Observable<T>;
114
+ static ɵfac: i0.ɵɵFactoryDeclaration<SseClient, never>;
115
+ static ɵprov: i0.ɵɵInjectableDeclaration<SseClient>;
116
+ }
117
+
118
+ /**
119
+ * Provides the SseClient service with optional features.
120
+ * Configure SSE client with interceptors and other features using a functional API.
121
+ *
122
+ * @param features - Optional feature configurations (e.g., withSseInterceptors())
123
+ * @returns Array of Angular providers
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * import { provideSseClient, withSseInterceptors } from '@telperion/ng-pack/sse-client';
128
+ *
129
+ * export const appConfig: ApplicationConfig = {
130
+ * providers: [
131
+ * provideSseClient(
132
+ * withSseInterceptors(loggingInterceptor, authInterceptor)
133
+ * )
134
+ * ]
135
+ * };
136
+ * ```
137
+ */
138
+ declare function provideSseClient(...features: SseFeature[][]): Provider[];
139
+
140
+ export { SSE_INTERCEPTORS, SseClient, SseFeatureKind, provideSseClient, withSseInterceptors };
141
+ export type { SseFeature, SseInterceptor, SseInterceptorFn, SseInterceptorFunctionFeature, SseNextFn, SseRequest };