@breadstone/archipel-platform-feature-flags 0.0.32
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 +10 -0
- package/package.json +100 -0
- package/src/FeatureFlagModule.d.ts +20 -0
- package/src/FeatureFlagModule.js +59 -0
- package/src/azure/AzureFeatureFlagReader.d.ts +46 -0
- package/src/azure/AzureFeatureFlagReader.js +190 -0
- package/src/azure/AzureFeatureFlagWriter.d.ts +43 -0
- package/src/azure/AzureFeatureFlagWriter.js +231 -0
- package/src/azure/env.d.ts +11 -0
- package/src/azure/env.js +44 -0
- package/src/azure/index.d.ts +3 -0
- package/src/azure/index.js +14 -0
- package/src/contracts/FeatureFlagReaderPort.d.ts +54 -0
- package/src/contracts/FeatureFlagReaderPort.js +27 -0
- package/src/contracts/FeatureFlagWriterPort.d.ts +64 -0
- package/src/contracts/FeatureFlagWriterPort.js +31 -0
- package/src/contracts/index.d.ts +2 -0
- package/src/contracts/index.js +8 -0
- package/src/decorators/FeatureFlag.d.ts +11 -0
- package/src/decorators/FeatureFlag.js +18 -0
- package/src/decorators/index.d.ts +1 -0
- package/src/decorators/index.js +7 -0
- package/src/env.d.ts +5 -0
- package/src/env.js +35 -0
- package/src/errors/FeatureFlagError.d.ts +10 -0
- package/src/errors/FeatureFlagError.js +21 -0
- package/src/errors/index.d.ts +1 -0
- package/src/errors/index.js +6 -0
- package/src/guards/FeatureFlagGuard.d.ts +30 -0
- package/src/guards/FeatureFlagGuard.js +69 -0
- package/src/guards/index.d.ts +1 -0
- package/src/guards/index.js +6 -0
- package/src/index.d.ts +11 -0
- package/src/index.js +19 -0
- package/src/models/IFeatureFlagContext.d.ts +13 -0
- package/src/models/IFeatureFlagContext.js +3 -0
- package/src/models/IFeatureFlagDefinition.d.ts +17 -0
- package/src/models/IFeatureFlagDefinition.js +3 -0
- package/src/models/IFeatureFlagEvaluation.d.ts +13 -0
- package/src/models/IFeatureFlagEvaluation.js +3 -0
- package/src/models/IFeatureFlagModuleOptions.d.ts +30 -0
- package/src/models/IFeatureFlagModuleOptions.js +4 -0
- package/src/models/index.d.ts +4 -0
- package/src/models/index.js +3 -0
- package/src/vercel/VercelFeatureFlagReader.d.ts +54 -0
- package/src/vercel/VercelFeatureFlagReader.js +158 -0
- package/src/vercel/VercelFeatureFlagWriter.d.ts +71 -0
- package/src/vercel/VercelFeatureFlagWriter.js +308 -0
- package/src/vercel/env.d.ts +17 -0
- package/src/vercel/env.js +69 -0
- package/src/vercel/index.d.ts +3 -0
- package/src/vercel/index.js +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# @breadstone/archipel-platform-feature-flags
|
|
2
|
+
|
|
3
|
+
> Provider-agnostic feature flag infrastructure for NestJS with support for Azure App Configuration and Vercel Edge Config.
|
|
4
|
+
|
|
5
|
+
## Providers
|
|
6
|
+
|
|
7
|
+
| Provider | Subpath Import | SDK |
|
|
8
|
+
| ----------------------- | ---------------------------------------------------- | --------------------------------------------------------------------- |
|
|
9
|
+
| Azure App Configuration | `@breadstone/archipel-platform-feature-flags/azure` | `@azure/app-configuration-provider` + `@microsoft/feature-management` |
|
|
10
|
+
| Vercel Edge Config | `@breadstone/archipel-platform-feature-flags/vercel` | `@vercel/edge-config` |
|
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@breadstone/archipel-platform-feature-flags",
|
|
3
|
+
"version": "0.0.32",
|
|
4
|
+
"description": "Provider-agnostic feature flag infrastructure for NestJS with support for Azure App Configuration and Vercel Edge Config.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"typesVersions": {
|
|
9
|
+
"*": {
|
|
10
|
+
"azure": [
|
|
11
|
+
"./src/azure/index.d.ts"
|
|
12
|
+
],
|
|
13
|
+
"vercel": [
|
|
14
|
+
"./src/vercel/index.d.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./src/index.d.ts",
|
|
21
|
+
"default": "./src/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./azure": {
|
|
24
|
+
"types": "./src/azure/index.d.ts",
|
|
25
|
+
"default": "./src/azure/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./vercel": {
|
|
28
|
+
"types": "./src/vercel/index.d.ts",
|
|
29
|
+
"default": "./src/vercel/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json",
|
|
32
|
+
"./azure/index": "./src/azure/index.js",
|
|
33
|
+
"./vercel/index": "./src/vercel/index.js"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/RueDeRennes/archipel",
|
|
39
|
+
"directory": "libs/platform-feature-flags"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"nestjs",
|
|
43
|
+
"feature-flags",
|
|
44
|
+
"feature-toggles",
|
|
45
|
+
"azure",
|
|
46
|
+
"azure-app-configuration",
|
|
47
|
+
"vercel",
|
|
48
|
+
"edge-config",
|
|
49
|
+
"archipel"
|
|
50
|
+
],
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=20"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"src/**/*.js",
|
|
56
|
+
"src/**/*.d.ts",
|
|
57
|
+
"README.md"
|
|
58
|
+
],
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@breadstone/archipel-platform-configuration": "0.0.32",
|
|
61
|
+
"tslib": "2.8.1"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@azure/app-configuration": ">=1.8.0",
|
|
65
|
+
"@azure/app-configuration-provider": ">=2.0.0",
|
|
66
|
+
"@azure/identity": ">=4.0.0",
|
|
67
|
+
"@microsoft/feature-management": ">=2.0.0",
|
|
68
|
+
"@nestjs/common": ">=11.0.0",
|
|
69
|
+
"@nestjs/core": ">=11.0.0",
|
|
70
|
+
"@vercel/edge-config": ">=1.0.0"
|
|
71
|
+
},
|
|
72
|
+
"peerDependenciesMeta": {
|
|
73
|
+
"@azure/app-configuration": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"@azure/app-configuration-provider": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"@azure/identity": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
82
|
+
"@microsoft/feature-management": {
|
|
83
|
+
"optional": true
|
|
84
|
+
},
|
|
85
|
+
"@vercel/edge-config": {
|
|
86
|
+
"optional": true
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"@azure/app-configuration": "1.11.0",
|
|
91
|
+
"@azure/app-configuration-provider": "2.4.1",
|
|
92
|
+
"@azure/identity": "4.13.1",
|
|
93
|
+
"@microsoft/feature-management": "2.3.1",
|
|
94
|
+
"@nestjs/common": "11.1.19",
|
|
95
|
+
"@nestjs/core": "11.1.19",
|
|
96
|
+
"@vercel/edge-config": "1.4.3",
|
|
97
|
+
"rxjs": "7.8.2",
|
|
98
|
+
"vitest": "4.1.5"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type DynamicModule } from '@nestjs/common';
|
|
2
|
+
import type { IFeatureFlagModuleOptions } from './models/IFeatureFlagModuleOptions';
|
|
3
|
+
/**
|
|
4
|
+
* NestJS module providing feature flag infrastructure.
|
|
5
|
+
* Exposes a configurable feature flag reader and a route guard.
|
|
6
|
+
* Optionally exposes a writer port for CRUD operations when `featureFlagWriter` is provided.
|
|
7
|
+
* The specific provider (Azure App Configuration, Vercel Edge Config) is selected via the options.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export declare class FeatureFlagModule {
|
|
12
|
+
/**
|
|
13
|
+
* Configures the module with the specified options.
|
|
14
|
+
*
|
|
15
|
+
* @public
|
|
16
|
+
* @param options - The module configuration options.
|
|
17
|
+
* @returns Dynamic module definition.
|
|
18
|
+
*/
|
|
19
|
+
static register(options: IFeatureFlagModuleOptions): DynamicModule;
|
|
20
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// #region Imports
|
|
3
|
+
var FeatureFlagModule_1;
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.FeatureFlagModule = void 0;
|
|
6
|
+
const tslib_1 = require("tslib");
|
|
7
|
+
const archipel_platform_configuration_1 = require("@breadstone/archipel-platform-configuration");
|
|
8
|
+
const common_1 = require("@nestjs/common");
|
|
9
|
+
const FeatureFlagReaderPort_1 = require("./contracts/FeatureFlagReaderPort");
|
|
10
|
+
const FeatureFlagWriterPort_1 = require("./contracts/FeatureFlagWriterPort");
|
|
11
|
+
const FeatureFlagGuard_1 = require("./guards/FeatureFlagGuard");
|
|
12
|
+
// #endregion
|
|
13
|
+
/**
|
|
14
|
+
* NestJS module providing feature flag infrastructure.
|
|
15
|
+
* Exposes a configurable feature flag reader and a route guard.
|
|
16
|
+
* Optionally exposes a writer port for CRUD operations when `featureFlagWriter` is provided.
|
|
17
|
+
* The specific provider (Azure App Configuration, Vercel Edge Config) is selected via the options.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
let FeatureFlagModule = FeatureFlagModule_1 = class FeatureFlagModule {
|
|
22
|
+
// #region Methods
|
|
23
|
+
/**
|
|
24
|
+
* Configures the module with the specified options.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
* @param options - The module configuration options.
|
|
28
|
+
* @returns Dynamic module definition.
|
|
29
|
+
*/
|
|
30
|
+
static register(options) {
|
|
31
|
+
const providers = [
|
|
32
|
+
{
|
|
33
|
+
provide: FeatureFlagReaderPort_1.FeatureFlagReaderPort,
|
|
34
|
+
useClass: options.featureFlagReader,
|
|
35
|
+
},
|
|
36
|
+
FeatureFlagGuard_1.FeatureFlagGuard,
|
|
37
|
+
];
|
|
38
|
+
const exports = [FeatureFlagReaderPort_1.FeatureFlagReaderPort, FeatureFlagGuard_1.FeatureFlagGuard];
|
|
39
|
+
if (options.featureFlagWriter) {
|
|
40
|
+
providers.push({
|
|
41
|
+
provide: FeatureFlagWriterPort_1.FeatureFlagWriterPort,
|
|
42
|
+
useClass: options.featureFlagWriter,
|
|
43
|
+
});
|
|
44
|
+
exports.push(FeatureFlagWriterPort_1.FeatureFlagWriterPort);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
global: options.isGlobal ?? true,
|
|
48
|
+
module: FeatureFlagModule_1,
|
|
49
|
+
imports: [archipel_platform_configuration_1.ConfigModule.register('platform-feature-flags', options.configEntries ?? [])],
|
|
50
|
+
providers,
|
|
51
|
+
exports,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
exports.FeatureFlagModule = FeatureFlagModule;
|
|
56
|
+
exports.FeatureFlagModule = FeatureFlagModule = FeatureFlagModule_1 = tslib_1.__decorate([
|
|
57
|
+
(0, common_1.Module)({})
|
|
58
|
+
], FeatureFlagModule);
|
|
59
|
+
//# sourceMappingURL=FeatureFlagModule.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ConfigService } from '@breadstone/archipel-platform-configuration';
|
|
2
|
+
import { type OnModuleDestroy, type OnModuleInit } from '@nestjs/common';
|
|
3
|
+
import { FeatureFlagReaderPort } from '../contracts/FeatureFlagReaderPort';
|
|
4
|
+
import type { IFeatureFlagContext } from '../models/IFeatureFlagContext';
|
|
5
|
+
import type { IFeatureFlagEvaluation } from '../models/IFeatureFlagEvaluation';
|
|
6
|
+
/**
|
|
7
|
+
* Azure App Configuration implementation of the {@link FeatureFlagReaderPort}.
|
|
8
|
+
* Uses `@azure/app-configuration-provider` for connecting to Azure App Configuration
|
|
9
|
+
* and `@microsoft/feature-management` for evaluating feature flags.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export declare class AzureFeatureFlagReader extends FeatureFlagReaderPort implements OnModuleInit, OnModuleDestroy {
|
|
14
|
+
private readonly _configService;
|
|
15
|
+
private readonly _logger;
|
|
16
|
+
private _appConfig;
|
|
17
|
+
private _featureManager;
|
|
18
|
+
private _refreshInterval;
|
|
19
|
+
constructor(configService: ConfigService);
|
|
20
|
+
/**
|
|
21
|
+
* Initializes the Azure App Configuration connection and feature manager.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
onModuleInit(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Cleans up the refresh interval on module destruction.
|
|
28
|
+
*
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
onModuleDestroy(): void;
|
|
32
|
+
/** @inheritdoc */
|
|
33
|
+
isEnabled(key: string, _context?: IFeatureFlagContext): Promise<boolean>;
|
|
34
|
+
/** @inheritdoc */
|
|
35
|
+
getVariant<T = unknown>(key: string, defaultValue: T, _context?: IFeatureFlagContext): Promise<T>;
|
|
36
|
+
/** @inheritdoc */
|
|
37
|
+
getAllFlags(_context?: IFeatureFlagContext): Promise<ReadonlyArray<IFeatureFlagEvaluation>>;
|
|
38
|
+
/** @inheritdoc */
|
|
39
|
+
refresh(): Promise<void>;
|
|
40
|
+
/** @inheritdoc */
|
|
41
|
+
ping(): Promise<boolean>;
|
|
42
|
+
/**
|
|
43
|
+
* Ensures the feature manager and app config have been initialized.
|
|
44
|
+
*/
|
|
45
|
+
private ensureInitialized;
|
|
46
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// #region Imports
|
|
3
|
+
var AzureFeatureFlagReader_1;
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.AzureFeatureFlagReader = void 0;
|
|
6
|
+
const tslib_1 = require("tslib");
|
|
7
|
+
const app_configuration_provider_1 = require("@azure/app-configuration-provider");
|
|
8
|
+
const identity_1 = require("@azure/identity");
|
|
9
|
+
const archipel_platform_configuration_1 = require("@breadstone/archipel-platform-configuration");
|
|
10
|
+
const feature_management_1 = require("@microsoft/feature-management");
|
|
11
|
+
const common_1 = require("@nestjs/common");
|
|
12
|
+
const FeatureFlagReaderPort_1 = require("../contracts/FeatureFlagReaderPort");
|
|
13
|
+
const FeatureFlagError_1 = require("../errors/FeatureFlagError");
|
|
14
|
+
const env_1 = require("./env");
|
|
15
|
+
// #endregion
|
|
16
|
+
/** Default refresh interval in milliseconds. */
|
|
17
|
+
const DEFAULT_REFRESH_INTERVAL = 30_000;
|
|
18
|
+
/** Default key filter for feature flags. */
|
|
19
|
+
const DEFAULT_KEY_FILTER = '*';
|
|
20
|
+
/**
|
|
21
|
+
* Azure App Configuration implementation of the {@link FeatureFlagReaderPort}.
|
|
22
|
+
* Uses `@azure/app-configuration-provider` for connecting to Azure App Configuration
|
|
23
|
+
* and `@microsoft/feature-management` for evaluating feature flags.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
let AzureFeatureFlagReader = AzureFeatureFlagReader_1 = class AzureFeatureFlagReader extends FeatureFlagReaderPort_1.FeatureFlagReaderPort {
|
|
28
|
+
// #endregion
|
|
29
|
+
// #region Ctor
|
|
30
|
+
constructor(configService) {
|
|
31
|
+
super();
|
|
32
|
+
this._logger = new common_1.Logger(AzureFeatureFlagReader_1.name);
|
|
33
|
+
this._configService = configService;
|
|
34
|
+
}
|
|
35
|
+
// #endregion
|
|
36
|
+
// #region Methods
|
|
37
|
+
/**
|
|
38
|
+
* Initializes the Azure App Configuration connection and feature manager.
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
async onModuleInit() {
|
|
43
|
+
try {
|
|
44
|
+
const endpoint = this._configService.get(env_1.AZURE_APPCONFIG_ENDPOINT.key);
|
|
45
|
+
const connectionString = this._configService.get(env_1.AZURE_APPCONFIG_CONNECTION_STRING.key);
|
|
46
|
+
const refreshIntervalInMs = this._configService.get(env_1.AZURE_FEATURE_FLAG_REFRESH_INTERVAL.key) ?? DEFAULT_REFRESH_INTERVAL;
|
|
47
|
+
const keyFilter = this._configService.get(env_1.AZURE_FEATURE_FLAG_KEY_FILTER.key) ?? DEFAULT_KEY_FILTER;
|
|
48
|
+
if (!endpoint && !connectionString) {
|
|
49
|
+
throw new FeatureFlagError_1.FeatureFlagError('azure', 'Either AZURE_APPCONFIG_ENDPOINT or AZURE_APPCONFIG_CONNECTION_STRING must be configured.');
|
|
50
|
+
}
|
|
51
|
+
const featureFlagOptions = {
|
|
52
|
+
enabled: true,
|
|
53
|
+
selectors: [{ keyFilter }],
|
|
54
|
+
refresh: {
|
|
55
|
+
enabled: true,
|
|
56
|
+
refreshIntervalInMs,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
let appConfig;
|
|
60
|
+
if (endpoint) {
|
|
61
|
+
const credential = new identity_1.DefaultAzureCredential();
|
|
62
|
+
appConfig = await (0, app_configuration_provider_1.load)(endpoint, credential, { featureFlagOptions });
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
appConfig = await (0, app_configuration_provider_1.load)(connectionString, { featureFlagOptions });
|
|
66
|
+
}
|
|
67
|
+
this._appConfig = appConfig;
|
|
68
|
+
this._featureManager = new feature_management_1.FeatureManager(new feature_management_1.ConfigurationMapFeatureFlagProvider(appConfig));
|
|
69
|
+
this._refreshInterval = setInterval(async () => {
|
|
70
|
+
try {
|
|
71
|
+
await this.refresh();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
this._logger.warn('[AzureFeatureFlagReader] Background refresh failed', {
|
|
75
|
+
error: error.message,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}, refreshIntervalInMs);
|
|
79
|
+
this._logger.log('[AzureFeatureFlagReader] Initialized successfully');
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error instanceof FeatureFlagError_1.FeatureFlagError) {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
throw new FeatureFlagError_1.FeatureFlagError('azure', 'Failed to initialize Azure App Configuration', error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Cleans up the refresh interval on module destruction.
|
|
90
|
+
*
|
|
91
|
+
* @public
|
|
92
|
+
*/
|
|
93
|
+
onModuleDestroy() {
|
|
94
|
+
if (this._refreshInterval) {
|
|
95
|
+
clearInterval(this._refreshInterval);
|
|
96
|
+
this._refreshInterval = undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** @inheritdoc */
|
|
100
|
+
async isEnabled(key, _context) {
|
|
101
|
+
this.ensureInitialized();
|
|
102
|
+
try {
|
|
103
|
+
return await this._featureManager.isEnabled(key);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
this._logger.error(`[AzureFeatureFlagReader] isEnabled failed for flag "${key}"`, {
|
|
107
|
+
key,
|
|
108
|
+
error: error.message,
|
|
109
|
+
});
|
|
110
|
+
throw new FeatureFlagError_1.FeatureFlagError('azure', `Failed to evaluate feature flag "${key}"`, error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** @inheritdoc */
|
|
114
|
+
async getVariant(key, defaultValue, _context) {
|
|
115
|
+
this.ensureInitialized();
|
|
116
|
+
try {
|
|
117
|
+
const variant = await this._featureManager.getVariant(key);
|
|
118
|
+
if (!variant) {
|
|
119
|
+
return defaultValue;
|
|
120
|
+
}
|
|
121
|
+
return variant.configuration ?? defaultValue;
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
this._logger.error(`[AzureFeatureFlagReader] getVariant failed for flag "${key}"`, {
|
|
125
|
+
key,
|
|
126
|
+
error: error.message,
|
|
127
|
+
});
|
|
128
|
+
throw new FeatureFlagError_1.FeatureFlagError('azure', `Failed to get variant for feature flag "${key}"`, error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** @inheritdoc */
|
|
132
|
+
async getAllFlags(_context) {
|
|
133
|
+
this.ensureInitialized();
|
|
134
|
+
try {
|
|
135
|
+
const flags = [];
|
|
136
|
+
for (const [rawKey] of this._appConfig) {
|
|
137
|
+
if (!rawKey.startsWith('.appconfig.featureflag/')) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const key = rawKey.replace('.appconfig.featureflag/', '');
|
|
141
|
+
const enabled = await this._featureManager.isEnabled(key);
|
|
142
|
+
flags.push({ key, enabled });
|
|
143
|
+
}
|
|
144
|
+
return flags;
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
this._logger.error('[AzureFeatureFlagReader] getAllFlags failed', {
|
|
148
|
+
error: error.message,
|
|
149
|
+
});
|
|
150
|
+
throw new FeatureFlagError_1.FeatureFlagError('azure', 'Failed to retrieve all feature flags', error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** @inheritdoc */
|
|
154
|
+
async refresh() {
|
|
155
|
+
this.ensureInitialized();
|
|
156
|
+
try {
|
|
157
|
+
await this._appConfig.refresh();
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
this._logger.error('[AzureFeatureFlagReader] refresh failed', {
|
|
161
|
+
error: error.message,
|
|
162
|
+
});
|
|
163
|
+
throw new FeatureFlagError_1.FeatureFlagError('azure', 'Failed to refresh feature flags from Azure App Configuration', error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** @inheritdoc */
|
|
167
|
+
async ping() {
|
|
168
|
+
try {
|
|
169
|
+
await this.refresh();
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Ensures the feature manager and app config have been initialized.
|
|
178
|
+
*/
|
|
179
|
+
ensureInitialized() {
|
|
180
|
+
if (!this._featureManager || !this._appConfig) {
|
|
181
|
+
throw new FeatureFlagError_1.FeatureFlagError('azure', 'AzureFeatureFlagReader has not been initialized. Ensure onModuleInit() ran.');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
exports.AzureFeatureFlagReader = AzureFeatureFlagReader;
|
|
186
|
+
exports.AzureFeatureFlagReader = AzureFeatureFlagReader = AzureFeatureFlagReader_1 = tslib_1.__decorate([
|
|
187
|
+
(0, common_1.Injectable)(),
|
|
188
|
+
tslib_1.__metadata("design:paramtypes", [archipel_platform_configuration_1.ConfigService])
|
|
189
|
+
], AzureFeatureFlagReader);
|
|
190
|
+
//# sourceMappingURL=AzureFeatureFlagReader.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ConfigService } from '@breadstone/archipel-platform-configuration';
|
|
2
|
+
import { type OnModuleInit } from '@nestjs/common';
|
|
3
|
+
import { FeatureFlagWriterPort } from '../contracts/FeatureFlagWriterPort';
|
|
4
|
+
import type { IFeatureFlagDefinition } from '../models/IFeatureFlagDefinition';
|
|
5
|
+
/**
|
|
6
|
+
* Azure App Configuration implementation of the {@link FeatureFlagWriterPort}.
|
|
7
|
+
* Uses `@azure/app-configuration` (`AppConfigurationClient`) for CRUD operations
|
|
8
|
+
* on feature flags stored in Azure App Configuration.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export declare class AzureFeatureFlagWriter extends FeatureFlagWriterPort implements OnModuleInit {
|
|
13
|
+
private readonly _configService;
|
|
14
|
+
private readonly _logger;
|
|
15
|
+
private _client;
|
|
16
|
+
constructor(configService: ConfigService);
|
|
17
|
+
/**
|
|
18
|
+
* Initializes the Azure App Configuration management client.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
onModuleInit(): Promise<void>;
|
|
23
|
+
/** @inheritdoc */
|
|
24
|
+
getFlag(key: string): Promise<IFeatureFlagDefinition | undefined>;
|
|
25
|
+
/** @inheritdoc */
|
|
26
|
+
listFlags(): Promise<ReadonlyArray<IFeatureFlagDefinition>>;
|
|
27
|
+
/** @inheritdoc */
|
|
28
|
+
createFlag(definition: IFeatureFlagDefinition): Promise<void>;
|
|
29
|
+
/** @inheritdoc */
|
|
30
|
+
updateFlag(key: string, updates: Partial<Omit<IFeatureFlagDefinition, 'key'>>): Promise<void>;
|
|
31
|
+
/** @inheritdoc */
|
|
32
|
+
deleteFlag(key: string): Promise<void>;
|
|
33
|
+
/** @inheritdoc */
|
|
34
|
+
ping(): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Checks whether the error is a 404 (resource not found).
|
|
37
|
+
*/
|
|
38
|
+
private isNotFoundError;
|
|
39
|
+
/**
|
|
40
|
+
* Ensures the management client has been initialized.
|
|
41
|
+
*/
|
|
42
|
+
private ensureInitialized;
|
|
43
|
+
}
|