@savvagent/angular 1.0.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/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +484 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +131 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +131 -0
- package/coverage/lcov-report/module.ts.html +289 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/service.ts.html +1846 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +242 -0
- package/coverage/module.ts.html +289 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/service.ts.html +1846 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/dist/README.md +484 -0
- package/dist/esm2022/index.mjs +15 -0
- package/dist/esm2022/module.mjs +75 -0
- package/dist/esm2022/savvagent-angular.mjs +5 -0
- package/dist/esm2022/service.mjs +473 -0
- package/dist/fesm2022/savvagent-angular.mjs +563 -0
- package/dist/fesm2022/savvagent-angular.mjs.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/module.d.ts +57 -0
- package/dist/service.d.ts +319 -0
- package/jest.config.js +40 -0
- package/ng-package.json +8 -0
- package/package.json +73 -0
- package/setup-jest.ts +2 -0
- package/src/index.spec.ts +144 -0
- package/src/index.ts +38 -0
- package/src/module.spec.ts +283 -0
- package/src/module.ts +68 -0
- package/src/service.spec.ts +945 -0
- package/src/service.ts +587 -0
- package/test-utils/angular-core-mock.ts +28 -0
- package/test-utils/angular-testing-mock.ts +87 -0
- package/tsconfig.json +33 -0
- package/tsconfig.spec.json +11 -0
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ModuleWithProviders } from '@angular/core';
|
|
2
|
+
import { SavvagentConfig } from './service';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* Angular module for Savvagent feature flags.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // app.module.ts
|
|
10
|
+
* import { SavvagentModule } from '@savvagent/angular';
|
|
11
|
+
*
|
|
12
|
+
* @NgModule({
|
|
13
|
+
* imports: [
|
|
14
|
+
* SavvagentModule.forRoot({
|
|
15
|
+
* config: {
|
|
16
|
+
* apiKey: 'sdk_your_api_key',
|
|
17
|
+
* baseUrl: 'https://api.savvagent.com'
|
|
18
|
+
* },
|
|
19
|
+
* defaultContext: {
|
|
20
|
+
* applicationId: 'my-app',
|
|
21
|
+
* environment: 'production',
|
|
22
|
+
* userId: 'user-123'
|
|
23
|
+
* }
|
|
24
|
+
* })
|
|
25
|
+
* ]
|
|
26
|
+
* })
|
|
27
|
+
* export class AppModule {}
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // For standalone components (Angular 14+)
|
|
33
|
+
* import { SavvagentModule } from '@savvagent/angular';
|
|
34
|
+
*
|
|
35
|
+
* bootstrapApplication(AppComponent, {
|
|
36
|
+
* providers: [
|
|
37
|
+
* importProvidersFrom(
|
|
38
|
+
* SavvagentModule.forRoot({
|
|
39
|
+
* config: { apiKey: 'sdk_...' }
|
|
40
|
+
* })
|
|
41
|
+
* )
|
|
42
|
+
* ]
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare class SavvagentModule {
|
|
47
|
+
/**
|
|
48
|
+
* Configure the Savvagent module with API key and default context.
|
|
49
|
+
*
|
|
50
|
+
* @param savvagentConfig - Configuration including API key and optional default context
|
|
51
|
+
* @returns Module with providers
|
|
52
|
+
*/
|
|
53
|
+
static forRoot(savvagentConfig: SavvagentConfig): ModuleWithProviders<SavvagentModule>;
|
|
54
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SavvagentModule, never>;
|
|
55
|
+
static ɵmod: i0.ɵɵNgModuleDeclaration<SavvagentModule, never, never, never>;
|
|
56
|
+
static ɵinj: i0.ɵɵInjectorDeclaration<SavvagentModule>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { OnDestroy, InjectionToken } from '@angular/core';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
import { FlagClient, FlagClientConfig, FlagContext, FlagEvaluationResult, FlagDefinition } from '@savvagent/sdk';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
/**
|
|
6
|
+
* Default context values that apply to all flag evaluations
|
|
7
|
+
* Per SDK Developer Guide: https://docs.savvagent.com/sdk-developer-guide
|
|
8
|
+
*/
|
|
9
|
+
export interface DefaultFlagContext {
|
|
10
|
+
/** Application ID for application-scoped flags */
|
|
11
|
+
applicationId?: string;
|
|
12
|
+
/** Environment (development, staging, production) */
|
|
13
|
+
environment?: string;
|
|
14
|
+
/** Organization ID for multi-tenant apps */
|
|
15
|
+
organizationId?: string;
|
|
16
|
+
/** Default user ID (required for percentage rollouts) */
|
|
17
|
+
userId?: string;
|
|
18
|
+
/** Default anonymous ID (alternative to userId for anonymous users) */
|
|
19
|
+
anonymousId?: string;
|
|
20
|
+
/** Session ID as fallback identifier */
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
/** User's language code (e.g., "en", "es") */
|
|
23
|
+
language?: string;
|
|
24
|
+
/** Default attributes for targeting */
|
|
25
|
+
attributes?: Record<string, any>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Configuration for the Savvagent Angular service
|
|
29
|
+
*/
|
|
30
|
+
export interface SavvagentConfig {
|
|
31
|
+
/** SDK API key configuration */
|
|
32
|
+
config: FlagClientConfig;
|
|
33
|
+
/** Default context values applied to all flag evaluations */
|
|
34
|
+
defaultContext?: DefaultFlagContext;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Injection token for Savvagent configuration
|
|
38
|
+
*/
|
|
39
|
+
export declare const SAVVAGENT_CONFIG: InjectionToken<SavvagentConfig>;
|
|
40
|
+
/**
|
|
41
|
+
* Result from flag evaluation as an Observable
|
|
42
|
+
*/
|
|
43
|
+
export interface FlagObservableResult {
|
|
44
|
+
/** Current flag value */
|
|
45
|
+
value: boolean;
|
|
46
|
+
/** Whether the flag is currently being evaluated */
|
|
47
|
+
loading: boolean;
|
|
48
|
+
/** Error if evaluation failed */
|
|
49
|
+
error: Error | null;
|
|
50
|
+
/** Detailed evaluation result */
|
|
51
|
+
result: FlagEvaluationResult | null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Options for flag evaluation
|
|
55
|
+
*/
|
|
56
|
+
export interface FlagOptions {
|
|
57
|
+
/** Context for flag evaluation (user_id, attributes, etc.) */
|
|
58
|
+
context?: FlagContext;
|
|
59
|
+
/** Default value to use while loading or on error */
|
|
60
|
+
defaultValue?: boolean;
|
|
61
|
+
/** Enable real-time updates for this flag */
|
|
62
|
+
realtime?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Angular service for Savvagent feature flags.
|
|
66
|
+
* Provides reactive flag evaluation using RxJS Observables.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* // In your component
|
|
71
|
+
* @Component({...})
|
|
72
|
+
* export class MyComponent {
|
|
73
|
+
* newFeature$ = this.savvagent.flag$('new-feature');
|
|
74
|
+
*
|
|
75
|
+
* constructor(private savvagent: SavvagentService) {}
|
|
76
|
+
* }
|
|
77
|
+
*
|
|
78
|
+
* // In your template
|
|
79
|
+
* <div *ngIf="(newFeature$ | async)?.value">
|
|
80
|
+
* New feature content!
|
|
81
|
+
* </div>
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare class SavvagentService implements OnDestroy {
|
|
85
|
+
private client;
|
|
86
|
+
private destroy$;
|
|
87
|
+
private isReady$;
|
|
88
|
+
private defaultContext;
|
|
89
|
+
private flagSubjects;
|
|
90
|
+
constructor(config?: SavvagentConfig);
|
|
91
|
+
/**
|
|
92
|
+
* Initialize the Savvagent client with configuration.
|
|
93
|
+
* Call this if not using the SAVVAGENT_CONFIG injection token.
|
|
94
|
+
*
|
|
95
|
+
* @param savvagentConfig - Configuration including API key and default context
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* @Component({...})
|
|
100
|
+
* export class AppComponent implements OnInit {
|
|
101
|
+
* constructor(private savvagent: SavvagentService) {}
|
|
102
|
+
*
|
|
103
|
+
* ngOnInit() {
|
|
104
|
+
* this.savvagent.initialize({
|
|
105
|
+
* config: { apiKey: 'sdk_...' },
|
|
106
|
+
* defaultContext: {
|
|
107
|
+
* applicationId: 'my-app',
|
|
108
|
+
* environment: 'development',
|
|
109
|
+
* userId: 'user-123'
|
|
110
|
+
* }
|
|
111
|
+
* });
|
|
112
|
+
* }
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
initialize(savvagentConfig: SavvagentConfig): void;
|
|
117
|
+
/**
|
|
118
|
+
* Observable that emits true when the client is ready.
|
|
119
|
+
*/
|
|
120
|
+
get ready$(): Observable<boolean>;
|
|
121
|
+
/**
|
|
122
|
+
* Check if the client is ready.
|
|
123
|
+
*/
|
|
124
|
+
get isReady(): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Get the underlying FlagClient instance for advanced use cases.
|
|
127
|
+
*/
|
|
128
|
+
get flagClient(): FlagClient | null;
|
|
129
|
+
/**
|
|
130
|
+
* Merge default context with per-call context.
|
|
131
|
+
*/
|
|
132
|
+
private mergeContext;
|
|
133
|
+
/**
|
|
134
|
+
* Get a reactive Observable for a feature flag.
|
|
135
|
+
* Automatically updates when the flag value changes.
|
|
136
|
+
*
|
|
137
|
+
* @param flagKey - The feature flag key to evaluate
|
|
138
|
+
* @param options - Configuration options
|
|
139
|
+
* @returns Observable of flag evaluation state
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* // In your component
|
|
144
|
+
* newFeature$ = this.savvagent.flag$('new-feature', {
|
|
145
|
+
* defaultValue: false,
|
|
146
|
+
* realtime: true,
|
|
147
|
+
* context: { attributes: { plan: 'pro' } }
|
|
148
|
+
* });
|
|
149
|
+
*
|
|
150
|
+
* // In template
|
|
151
|
+
* <ng-container *ngIf="newFeature$ | async as flag">
|
|
152
|
+
* <app-loading *ngIf="flag.loading"></app-loading>
|
|
153
|
+
* <app-new-feature *ngIf="flag.value"></app-new-feature>
|
|
154
|
+
* <app-old-feature *ngIf="!flag.value && !flag.loading"></app-old-feature>
|
|
155
|
+
* </ng-container>
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
flag$(flagKey: string, options?: FlagOptions): Observable<FlagObservableResult>;
|
|
159
|
+
/**
|
|
160
|
+
* Generate a cache key for a flag+context combination.
|
|
161
|
+
*/
|
|
162
|
+
private getCacheKey;
|
|
163
|
+
/**
|
|
164
|
+
* Evaluate a flag and emit the result to a subject.
|
|
165
|
+
*/
|
|
166
|
+
private evaluateAndEmit;
|
|
167
|
+
/**
|
|
168
|
+
* Re-evaluate all active flag subscriptions.
|
|
169
|
+
* Called when overrides change.
|
|
170
|
+
*/
|
|
171
|
+
private reEvaluateAllFlags;
|
|
172
|
+
/**
|
|
173
|
+
* Get a flag value as a simple Observable<boolean>.
|
|
174
|
+
* Useful when you only need the value without loading/error states.
|
|
175
|
+
*
|
|
176
|
+
* @param flagKey - The feature flag key to evaluate
|
|
177
|
+
* @param options - Configuration options
|
|
178
|
+
* @returns Observable of boolean flag value
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* isFeatureEnabled$ = this.savvagent.flagValue$('my-feature');
|
|
183
|
+
*
|
|
184
|
+
* // In template
|
|
185
|
+
* <button *ngIf="isFeatureEnabled$ | async">New Button</button>
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
flagValue$(flagKey: string, options?: FlagOptions): Observable<boolean>;
|
|
189
|
+
/**
|
|
190
|
+
* Evaluate a feature flag once (non-reactive).
|
|
191
|
+
* For reactive updates, use flag$() instead.
|
|
192
|
+
*
|
|
193
|
+
* @param flagKey - The feature flag key to evaluate
|
|
194
|
+
* @param context - Optional context for targeting
|
|
195
|
+
* @returns Promise with detailed evaluation result
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* async checkFeature() {
|
|
200
|
+
* const result = await this.savvagent.evaluate('new-feature');
|
|
201
|
+
* if (result.value) {
|
|
202
|
+
* // Feature is enabled
|
|
203
|
+
* }
|
|
204
|
+
* }
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
evaluate(flagKey: string, context?: FlagContext): Promise<FlagEvaluationResult>;
|
|
208
|
+
/**
|
|
209
|
+
* Check if a feature flag is enabled (non-reactive).
|
|
210
|
+
*
|
|
211
|
+
* @param flagKey - The feature flag key to evaluate
|
|
212
|
+
* @param context - Optional context for targeting
|
|
213
|
+
* @returns Promise<boolean>
|
|
214
|
+
*/
|
|
215
|
+
isEnabled(flagKey: string, context?: FlagContext): Promise<boolean>;
|
|
216
|
+
/**
|
|
217
|
+
* Execute code conditionally based on flag value.
|
|
218
|
+
*
|
|
219
|
+
* @param flagKey - The flag key to check
|
|
220
|
+
* @param callback - Function to execute if flag is enabled
|
|
221
|
+
* @param context - Optional context for targeting
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* await this.savvagent.withFlag('analytics-enabled', async () => {
|
|
226
|
+
* await this.analytics.track('page_view');
|
|
227
|
+
* });
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
withFlag<T>(flagKey: string, callback: () => T | Promise<T>, context?: FlagContext): Promise<T | null>;
|
|
231
|
+
/**
|
|
232
|
+
* Track an error with flag context.
|
|
233
|
+
*
|
|
234
|
+
* @param flagKey - The flag key associated with the error
|
|
235
|
+
* @param error - The error that occurred
|
|
236
|
+
* @param context - Optional context
|
|
237
|
+
*/
|
|
238
|
+
trackError(flagKey: string, error: Error, context?: FlagContext): void;
|
|
239
|
+
/**
|
|
240
|
+
* Set the user ID for logged-in users.
|
|
241
|
+
*
|
|
242
|
+
* @param userId - The user ID (or null to clear)
|
|
243
|
+
*/
|
|
244
|
+
setUserId(userId: string | null): void;
|
|
245
|
+
/**
|
|
246
|
+
* Get the current user ID.
|
|
247
|
+
*/
|
|
248
|
+
getUserId(): string | null;
|
|
249
|
+
/**
|
|
250
|
+
* Get the current anonymous ID.
|
|
251
|
+
*/
|
|
252
|
+
getAnonymousId(): string | null;
|
|
253
|
+
/**
|
|
254
|
+
* Set a custom anonymous ID.
|
|
255
|
+
*/
|
|
256
|
+
setAnonymousId(id: string): void;
|
|
257
|
+
/**
|
|
258
|
+
* Set a local override for a flag.
|
|
259
|
+
* Overrides take precedence over server values.
|
|
260
|
+
*
|
|
261
|
+
* @param flagKey - The flag key to override
|
|
262
|
+
* @param value - The override value
|
|
263
|
+
*/
|
|
264
|
+
setOverride(flagKey: string, value: boolean): void;
|
|
265
|
+
/**
|
|
266
|
+
* Clear a local override for a flag.
|
|
267
|
+
*/
|
|
268
|
+
clearOverride(flagKey: string): void;
|
|
269
|
+
/**
|
|
270
|
+
* Clear all local overrides.
|
|
271
|
+
*/
|
|
272
|
+
clearAllOverrides(): void;
|
|
273
|
+
/**
|
|
274
|
+
* Check if a flag has a local override.
|
|
275
|
+
*/
|
|
276
|
+
hasOverride(flagKey: string): boolean;
|
|
277
|
+
/**
|
|
278
|
+
* Get the override value for a flag.
|
|
279
|
+
*/
|
|
280
|
+
getOverride(flagKey: string): boolean | undefined;
|
|
281
|
+
/**
|
|
282
|
+
* Get all current overrides.
|
|
283
|
+
*/
|
|
284
|
+
getOverrides(): Record<string, boolean>;
|
|
285
|
+
/**
|
|
286
|
+
* Set multiple overrides at once.
|
|
287
|
+
*/
|
|
288
|
+
setOverrides(overrides: Record<string, boolean>): void;
|
|
289
|
+
/**
|
|
290
|
+
* Get all flags for the application.
|
|
291
|
+
*
|
|
292
|
+
* @param environment - Environment to evaluate (default: 'development')
|
|
293
|
+
* @returns Observable of flag definitions
|
|
294
|
+
*/
|
|
295
|
+
getAllFlags$(environment?: string): Observable<FlagDefinition[]>;
|
|
296
|
+
/**
|
|
297
|
+
* Get all flags for the application (Promise-based).
|
|
298
|
+
*/
|
|
299
|
+
getAllFlags(environment?: string): Promise<FlagDefinition[]>;
|
|
300
|
+
/**
|
|
301
|
+
* Get enterprise-scoped flags only.
|
|
302
|
+
*/
|
|
303
|
+
getEnterpriseFlags(environment?: string): Promise<FlagDefinition[]>;
|
|
304
|
+
/**
|
|
305
|
+
* Clear the flag cache.
|
|
306
|
+
*/
|
|
307
|
+
clearCache(): void;
|
|
308
|
+
/**
|
|
309
|
+
* Check if real-time connection is active.
|
|
310
|
+
*/
|
|
311
|
+
isRealtimeConnected(): boolean;
|
|
312
|
+
/**
|
|
313
|
+
* Close the client and cleanup resources.
|
|
314
|
+
*/
|
|
315
|
+
close(): void;
|
|
316
|
+
ngOnDestroy(): void;
|
|
317
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SavvagentService, [{ optional: true; }]>;
|
|
318
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<SavvagentService>;
|
|
319
|
+
}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'jsdom',
|
|
4
|
+
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
|
|
5
|
+
roots: ['<rootDir>/src'],
|
|
6
|
+
testMatch: ['**/*.spec.ts'],
|
|
7
|
+
collectCoverageFrom: [
|
|
8
|
+
'src/**/*.ts',
|
|
9
|
+
'!src/**/*.d.ts',
|
|
10
|
+
'!src/index.ts'
|
|
11
|
+
],
|
|
12
|
+
coverageDirectory: 'coverage',
|
|
13
|
+
coverageReporters: ['text', 'lcov', 'html'],
|
|
14
|
+
moduleNameMapper: {
|
|
15
|
+
'^@savvagent/sdk$': '<rootDir>/../typescript/src/index.ts',
|
|
16
|
+
'^@angular/core/testing$': '<rootDir>/test-utils/angular-testing-mock.ts',
|
|
17
|
+
'^@angular/core$': '<rootDir>/test-utils/angular-core-mock.ts',
|
|
18
|
+
},
|
|
19
|
+
transform: {
|
|
20
|
+
'^.+\\.ts$': ['ts-jest', {
|
|
21
|
+
tsconfig: {
|
|
22
|
+
target: 'ES2022',
|
|
23
|
+
module: 'CommonJS',
|
|
24
|
+
lib: ['ES2022', 'DOM'],
|
|
25
|
+
esModuleInterop: true,
|
|
26
|
+
skipLibCheck: true,
|
|
27
|
+
experimentalDecorators: true,
|
|
28
|
+
emitDecoratorMetadata: true,
|
|
29
|
+
useDefineForClassFields: false,
|
|
30
|
+
moduleResolution: 'node',
|
|
31
|
+
noUnusedLocals: false,
|
|
32
|
+
noUnusedParameters: false,
|
|
33
|
+
},
|
|
34
|
+
}],
|
|
35
|
+
},
|
|
36
|
+
transformIgnorePatterns: [
|
|
37
|
+
'node_modules/(?!(@angular|rxjs|tslib)/)'
|
|
38
|
+
],
|
|
39
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
40
|
+
};
|
package/ng-package.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@savvagent/angular",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Angular SDK for Savvagent feature flags",
|
|
5
|
+
"author": "Savvagent",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/fesm2022/savvagent-angular.mjs",
|
|
8
|
+
"module": "./dist/fesm2022/savvagent-angular.mjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"esm2022": "./dist/esm2022/savvagent-angular.mjs",
|
|
14
|
+
"esm": "./dist/esm2022/savvagent-angular.mjs",
|
|
15
|
+
"default": "./dist/fesm2022/savvagent-angular.mjs"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": {
|
|
18
|
+
"default": "./package.json"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@angular/common": ">=14.0.0",
|
|
23
|
+
"@angular/core": ">=14.0.0",
|
|
24
|
+
"rxjs": ">=7.0.0"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"tslib": "^2.8.1",
|
|
28
|
+
"@savvagent/sdk": "1.0.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@angular/common": "^18.2.14",
|
|
32
|
+
"@angular/compiler": "^18.2.14",
|
|
33
|
+
"@angular/compiler-cli": "^18.2.14",
|
|
34
|
+
"@angular/core": "^18.2.14",
|
|
35
|
+
"@types/jest": "^29.5.14",
|
|
36
|
+
"jest": "^29.7.0",
|
|
37
|
+
"jest-environment-jsdom": "30.2.0",
|
|
38
|
+
"jest-preset-angular": "15.0.3",
|
|
39
|
+
"ng-packagr": "^18.2.1",
|
|
40
|
+
"reflect-metadata": "0.2.2",
|
|
41
|
+
"rxjs": "^7.8.2",
|
|
42
|
+
"ts-jest": "29.4.5",
|
|
43
|
+
"ts-node": "^10.9.2",
|
|
44
|
+
"typescript": "~5.4.5"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"savvagent",
|
|
48
|
+
"feature-flags",
|
|
49
|
+
"angular",
|
|
50
|
+
"service",
|
|
51
|
+
"feature-toggles"
|
|
52
|
+
],
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "https://github.com/savvagent/savvagent-sdks",
|
|
56
|
+
"directory": "packages/angular"
|
|
57
|
+
},
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/savvagent/savvagent-sdks/issues"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/savvagent/savvagent-sdks/tree/main/packages/angular#readme",
|
|
62
|
+
"publishConfig": {
|
|
63
|
+
"access": "public"
|
|
64
|
+
},
|
|
65
|
+
"sideEffects": false,
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "ng-packagr -p ng-package.json",
|
|
68
|
+
"dev": "ng-packagr -p ng-package.json --watch",
|
|
69
|
+
"test": "jest",
|
|
70
|
+
"lint": "eslint src --ext .ts",
|
|
71
|
+
"format": "prettier --write \"src/**/*.ts\""
|
|
72
|
+
}
|
|
73
|
+
}
|
package/setup-jest.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for package exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as AngularSDK from './index';
|
|
6
|
+
|
|
7
|
+
describe('Package Exports', () => {
|
|
8
|
+
describe('Module Exports', () => {
|
|
9
|
+
it('should export SavvagentModule', () => {
|
|
10
|
+
expect(AngularSDK.SavvagentModule).toBeDefined();
|
|
11
|
+
expect(typeof AngularSDK.SavvagentModule).toBe('function');
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('Service Exports', () => {
|
|
16
|
+
it('should export SavvagentService', () => {
|
|
17
|
+
expect(AngularSDK.SavvagentService).toBeDefined();
|
|
18
|
+
expect(typeof AngularSDK.SavvagentService).toBe('function');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should export SAVVAGENT_CONFIG injection token', () => {
|
|
22
|
+
expect(AngularSDK.SAVVAGENT_CONFIG).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('Type Exports', () => {
|
|
27
|
+
it('should have correct type structure for SavvagentConfig', () => {
|
|
28
|
+
const config: AngularSDK.SavvagentConfig = {
|
|
29
|
+
config: {
|
|
30
|
+
apiKey: 'test',
|
|
31
|
+
},
|
|
32
|
+
defaultContext: {
|
|
33
|
+
applicationId: 'test-app',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
expect(config).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should have correct type structure for DefaultFlagContext', () => {
|
|
40
|
+
const context: AngularSDK.DefaultFlagContext = {
|
|
41
|
+
applicationId: 'app',
|
|
42
|
+
environment: 'production',
|
|
43
|
+
userId: 'user-123',
|
|
44
|
+
attributes: { plan: 'pro' },
|
|
45
|
+
};
|
|
46
|
+
expect(context).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should have correct type structure for FlagObservableResult', () => {
|
|
50
|
+
const result: AngularSDK.FlagObservableResult = {
|
|
51
|
+
value: true,
|
|
52
|
+
loading: false,
|
|
53
|
+
error: null,
|
|
54
|
+
result: {
|
|
55
|
+
key: 'test-flag',
|
|
56
|
+
value: true,
|
|
57
|
+
reason: 'evaluated',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
expect(result).toBeDefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should have correct type structure for FlagOptions', () => {
|
|
64
|
+
const options: AngularSDK.FlagOptions = {
|
|
65
|
+
context: { user_id: 'user-123' },
|
|
66
|
+
defaultValue: false,
|
|
67
|
+
realtime: true,
|
|
68
|
+
};
|
|
69
|
+
expect(options).toBeDefined();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('Re-exported Types from @savvagent/sdk', () => {
|
|
74
|
+
it('should re-export FlagClient', () => {
|
|
75
|
+
expect(AngularSDK.FlagClient).toBeDefined();
|
|
76
|
+
expect(typeof AngularSDK.FlagClient).toBe('function');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Type existence checks - these ensure types are exported
|
|
80
|
+
it('should provide FlagClientConfig type', () => {
|
|
81
|
+
const config: AngularSDK.FlagClientConfig = {
|
|
82
|
+
apiKey: 'test',
|
|
83
|
+
};
|
|
84
|
+
expect(config).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should provide FlagContext type', () => {
|
|
88
|
+
const context: AngularSDK.FlagContext = {
|
|
89
|
+
user_id: 'user-123',
|
|
90
|
+
attributes: { key: 'value' },
|
|
91
|
+
};
|
|
92
|
+
expect(context).toBeDefined();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should provide FlagEvaluationResult type', () => {
|
|
96
|
+
const result: AngularSDK.FlagEvaluationResult = {
|
|
97
|
+
key: 'test-flag',
|
|
98
|
+
value: true,
|
|
99
|
+
reason: 'evaluated',
|
|
100
|
+
};
|
|
101
|
+
expect(result).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('Package Structure', () => {
|
|
106
|
+
it('should export all documented APIs', () => {
|
|
107
|
+
const expectedExports = [
|
|
108
|
+
'SavvagentModule',
|
|
109
|
+
'SavvagentService',
|
|
110
|
+
'SAVVAGENT_CONFIG',
|
|
111
|
+
'FlagClient',
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
expectedExports.forEach((exportName) => {
|
|
115
|
+
expect(AngularSDK).toHaveProperty(exportName);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should not export internal implementation details', () => {
|
|
120
|
+
// Ensure we're not accidentally exposing things we shouldn't
|
|
121
|
+
const internalNames = ['private', 'internal'];
|
|
122
|
+
const exportKeys = Object.keys(AngularSDK);
|
|
123
|
+
|
|
124
|
+
internalNames.forEach((internalName) => {
|
|
125
|
+
const hasInternal = exportKeys.some((key) =>
|
|
126
|
+
key.toLowerCase().includes(internalName)
|
|
127
|
+
);
|
|
128
|
+
expect(hasInternal).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('Version and Metadata', () => {
|
|
134
|
+
it('should be a valid package', () => {
|
|
135
|
+
expect(AngularSDK).toBeDefined();
|
|
136
|
+
expect(typeof AngularSDK).toBe('object');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should have non-empty exports', () => {
|
|
140
|
+
const exportKeys = Object.keys(AngularSDK);
|
|
141
|
+
expect(exportKeys.length).toBeGreaterThan(0);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @savvagent/angular - Angular SDK for Savvagent feature flags
|
|
3
|
+
*
|
|
4
|
+
* This package provides Angular services and modules for easy integration
|
|
5
|
+
* of Savvagent feature flags into Angular applications.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Module
|
|
11
|
+
export { SavvagentModule } from './module';
|
|
12
|
+
|
|
13
|
+
// Service and types
|
|
14
|
+
export { SavvagentService, SAVVAGENT_CONFIG } from './service';
|
|
15
|
+
export type {
|
|
16
|
+
SavvagentConfig,
|
|
17
|
+
DefaultFlagContext,
|
|
18
|
+
FlagObservableResult,
|
|
19
|
+
FlagOptions,
|
|
20
|
+
} from './service';
|
|
21
|
+
|
|
22
|
+
// Re-export types from core SDK
|
|
23
|
+
export type {
|
|
24
|
+
FlagClientConfig,
|
|
25
|
+
FlagContext,
|
|
26
|
+
FlagEvaluationResult,
|
|
27
|
+
EvaluationEvent,
|
|
28
|
+
ErrorEvent,
|
|
29
|
+
FlagUpdateEvent,
|
|
30
|
+
FlagDefinition,
|
|
31
|
+
FlagListResponse,
|
|
32
|
+
// Generated API types for advanced users
|
|
33
|
+
ApiTypes,
|
|
34
|
+
components,
|
|
35
|
+
} from '@savvagent/sdk';
|
|
36
|
+
|
|
37
|
+
// Re-export FlagClient for advanced use cases
|
|
38
|
+
export { FlagClient } from '@savvagent/sdk';
|