@application-kit/feature-flags 0.1.0 → 0.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/CHANGELOG.md +12 -0
- package/README.md +27 -27
- package/dist/cjs/nest/feature-flags-root.module.d.ts +5 -0
- package/dist/cjs/nest/feature-flags-root.module.js +27 -0
- package/dist/cjs/nest/index.d.ts +1 -0
- package/dist/cjs/nest/index.js +17 -0
- package/dist/cjs/vue/feature-flag-channel.d.ts +14 -0
- package/dist/cjs/vue/feature-flag-channel.js +20 -0
- package/dist/cjs/vue/index.d.ts +1 -0
- package/dist/cjs/vue/index.js +1 -0
- package/dist/esm/nest/feature-flags-root.module.d.ts +5 -0
- package/dist/esm/nest/feature-flags-root.module.js +24 -0
- package/dist/esm/nest/index.d.ts +1 -0
- package/dist/esm/nest/index.js +1 -0
- package/dist/esm/vue/feature-flag-channel.d.ts +14 -0
- package/dist/esm/vue/feature-flag-channel.js +16 -0
- package/dist/esm/vue/index.d.ts +1 -0
- package/dist/esm/vue/index.js +1 -0
- package/package.json +18 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @application-kit/feature-flags
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1032c06: Вход `/nest`: `FeatureFlagsRootModule.forRoot(featureFlags)` разбирает окружение и глобально отдаёт порт `FEATURE_FLAGS`. Класс окружения и конфиг-адаптер в приложении больше не нужны; отказы на неверном `FEATURE_*` прежние. Новый необязательный peer `@nestjs/common` — только для этого входа.
|
|
8
|
+
|
|
9
|
+
## 0.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 2a51c82: Каналы значений «сервер → клиент». `vue-ssr`: опция `channels` у `createSsrEntry` и `hydrateSsrApplication`, контракт `SsrChannelInterface`, `PiniaPlugin`. Ломает: `describePage` заменена на `resolveLang`, значения `window.*` идут только каналами; `channels` обязательна у обеих функций, `beforeMount` стал необязательным. `i18n/vue`: `createLocaleChannel`. `feature-flags/vue`: `createFeatureFlagChannel`.
|
|
14
|
+
|
|
3
15
|
## 0.1.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -18,18 +18,18 @@ yarn add @application-kit/feature-flags @libs-for-dev/nestjs-config-module
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Peer: `@libs-for-dev/nestjs-config-module` (подпуть `/parse-boolean` — разбор булевых переменных, без
|
|
21
|
-
зависимостей); `vue` — только для входа `/vue`.
|
|
21
|
+
зависимостей); `vue` — только для входа `/vue`, `@nestjs/common` — только для входа `/nest`.
|
|
22
22
|
|
|
23
23
|
## Входы
|
|
24
24
|
|
|
25
25
|
| Вход | Что внутри | Зависимости |
|
|
26
26
|
|---|---|---|
|
|
27
27
|
| `@application-kit/feature-flags` | `defineFeatureFlags`, реестр `FeatureFlagRegistryInterface`, порт `FeatureFlagsInterface`, токен `FEATURE_FLAGS`, адаптер `EnvironmentFeatureFlags`, ошибки | изоморфно, без Nest и DOM |
|
|
28
|
-
| `@application-kit/feature-flags/
|
|
28
|
+
| `@application-kit/feature-flags/nest` | `FeatureFlagsRootModule` | `@nestjs/common` |
|
|
29
|
+
| `@application-kit/feature-flags/vue` | `useFeatureFlag`, ключ `FEATURE_FLAG_STATE`, `parseFeatureFlagState`, `createFeatureFlagChannel` | `vue` |
|
|
29
30
|
| `@application-kit/feature-flags/testing` | `FakeFeatureFlags` | — |
|
|
30
31
|
|
|
31
|
-
Корень грузится и в клиентском бандле (набор нужен разбору SSR-состояния), поэтому
|
|
32
|
-
класс окружения и регистрацию адаптера под токеном делает приложение (пример ниже).
|
|
32
|
+
Корень грузится и в клиентском бандле (набор нужен разбору SSR-состояния), поэтому связка с Nest — отдельный вход.
|
|
33
33
|
|
|
34
34
|
## Набор флагов
|
|
35
35
|
|
|
@@ -75,35 +75,21 @@ export const featureFlags = defineFeatureFlags({
|
|
|
75
75
|
проверки собирают полный список до броска, чтобы отказ назвал все проблемы разом; незнакомая переменная
|
|
76
76
|
проверяется первой — опечатка даёт сразу и лишнее имя, и недостающее.
|
|
77
77
|
|
|
78
|
-
### Связка с Nest
|
|
78
|
+
### Связка с Nest
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Корневой модуль процесса подключает порт одной строкой:
|
|
81
81
|
|
|
82
82
|
```ts
|
|
83
|
-
import {
|
|
83
|
+
import { FeatureFlagsRootModule } from '@application-kit/feature-flags/nest'
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@IsObject()
|
|
88
|
-
public FEATURE_FLAGS: FeatureFlagState
|
|
89
|
-
|
|
90
|
-
// Конфиг-модуль конструирует класс без аргументов: переменных столько, сколько флагов, и объявлять их
|
|
91
|
-
// по одной `@Expose()` значило бы держать вторую копию набора.
|
|
92
|
-
public constructor(source: Record<string, unknown> = process.env) {
|
|
93
|
-
this.FEATURE_FLAGS = featureFlags.readEnvironment(source)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export class FeatureFlagsConfig extends EnvironmentFeatureFlags {
|
|
98
|
-
public constructor(@Inject(FeatureFlagsEnvironment) environment: FeatureFlagsEnvironment) {
|
|
99
|
-
super(featureFlags, environment.FEATURE_FLAGS)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// провайдер модуля
|
|
104
|
-
{ provide: FEATURE_FLAGS, useExisting: FeatureFlagsConfig }
|
|
85
|
+
@Module({ imports: [FeatureFlagsRootModule.forRoot(featureFlags)] })
|
|
86
|
+
export class CommonModule {}
|
|
105
87
|
```
|
|
106
88
|
|
|
89
|
+
`forRoot` разбирает `process.env` сразу, на импорте модуля-носителя: отказ приходит до сборки графа, а не на первом
|
|
90
|
+
запросе. Поэтому окружение обязано быть готово до импорта: правка из `beforeAll` спеки опоздает. Модуль глобальный:
|
|
91
|
+
`FEATURE_FLAGS` виден всему графу без своей регистрации.
|
|
92
|
+
|
|
107
93
|
## Чтение на сервере
|
|
108
94
|
|
|
109
95
|
Через порт по токену, а не чтением `process.env`:
|
|
@@ -129,6 +115,20 @@ Vue, где `await` в разметке невозможен. Адаптер с
|
|
|
129
115
|
|
|
130
116
|
Публичный срез уезжает на страницу SSR-состоянием:
|
|
131
117
|
|
|
118
|
+
С `@application-kit/vue-ssr` состояние едет каналом — ставится до рендера и уезжает на страницу одним значением:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const featureFlagChannel = createFeatureFlagChannel(featureFlags)
|
|
122
|
+
|
|
123
|
+
// сервер, опция createSsrEntry
|
|
124
|
+
channels: () => [featureFlagChannel.withValue(featureFlagsPort.publicState())],
|
|
125
|
+
|
|
126
|
+
// клиент, опция hydrateSsrApplication
|
|
127
|
+
channels: [featureFlagChannel],
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Без него — те же шаги руками:
|
|
131
|
+
|
|
132
132
|
```ts
|
|
133
133
|
// сервер, перед renderToString
|
|
134
134
|
const state = featureFlagsPort.publicState()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.FeatureFlagsRootModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const constants_js_1 = require("../constants.js");
|
|
12
|
+
const environment_feature_flags_js_1 = require("../environment-feature-flags.js");
|
|
13
|
+
let FeatureFlagsRootModule = class FeatureFlagsRootModule {
|
|
14
|
+
static forRoot(featureFlags) {
|
|
15
|
+
const state = featureFlags.readEnvironment(process.env);
|
|
16
|
+
return {
|
|
17
|
+
exports: [constants_js_1.FEATURE_FLAGS],
|
|
18
|
+
global: true,
|
|
19
|
+
module: this,
|
|
20
|
+
providers: [{ provide: constants_js_1.FEATURE_FLAGS, useValue: new environment_feature_flags_js_1.EnvironmentFeatureFlags(featureFlags, state) }],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
exports.FeatureFlagsRootModule = FeatureFlagsRootModule;
|
|
25
|
+
exports.FeatureFlagsRootModule = FeatureFlagsRootModule = __decorate([
|
|
26
|
+
(0, common_1.Module)({})
|
|
27
|
+
], FeatureFlagsRootModule);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './feature-flags-root.module.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./feature-flags-root.module.js"), exports);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { App } from 'vue';
|
|
2
|
+
import type { FeatureFlagSetInterface } from '../define-feature-flags.js';
|
|
3
|
+
import type { FeatureFlagState } from '../feature-flag-registry.js';
|
|
4
|
+
export interface FeatureFlagChannelInterface {
|
|
5
|
+
readonly key: string;
|
|
6
|
+
restore(app: App, serialized: string | undefined): void;
|
|
7
|
+
withValue(state: FeatureFlagState): FeatureFlagChannelValueInterface;
|
|
8
|
+
}
|
|
9
|
+
export interface FeatureFlagChannelValueInterface {
|
|
10
|
+
apply(app: App): void;
|
|
11
|
+
readonly key: string;
|
|
12
|
+
readonly serialized: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const createFeatureFlagChannel: (featureFlags: FeatureFlagSetInterface) => FeatureFlagChannelInterface;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createFeatureFlagChannel = void 0;
|
|
4
|
+
const parse_feature_flag_state_js_1 = require("./parse-feature-flag-state.js");
|
|
5
|
+
const use_feature_flag_js_1 = require("./use-feature-flag.js");
|
|
6
|
+
const FEATURE_FLAG_CHANNEL_KEY = 'ssrFeatureFlags';
|
|
7
|
+
const createFeatureFlagChannel = (featureFlags) => ({
|
|
8
|
+
key: FEATURE_FLAG_CHANNEL_KEY,
|
|
9
|
+
restore: (app, serialized) => {
|
|
10
|
+
app.provide(use_feature_flag_js_1.FEATURE_FLAG_STATE, (0, parse_feature_flag_state_js_1.parseFeatureFlagState)(featureFlags, serialized));
|
|
11
|
+
},
|
|
12
|
+
withValue: (state) => ({
|
|
13
|
+
apply: (app) => {
|
|
14
|
+
app.provide(use_feature_flag_js_1.FEATURE_FLAG_STATE, state);
|
|
15
|
+
},
|
|
16
|
+
key: FEATURE_FLAG_CHANNEL_KEY,
|
|
17
|
+
serialized: JSON.stringify(state),
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
exports.createFeatureFlagChannel = createFeatureFlagChannel;
|
package/dist/cjs/vue/index.d.ts
CHANGED
package/dist/cjs/vue/index.js
CHANGED
|
@@ -14,5 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./feature-flag-channel.js"), exports);
|
|
17
18
|
__exportStar(require("./parse-feature-flag-state.js"), exports);
|
|
18
19
|
__exportStar(require("./use-feature-flag.js"), exports);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Module } from '@nestjs/common';
|
|
8
|
+
import { FEATURE_FLAGS } from '../constants.js';
|
|
9
|
+
import { EnvironmentFeatureFlags } from '../environment-feature-flags.js';
|
|
10
|
+
let FeatureFlagsRootModule = class FeatureFlagsRootModule {
|
|
11
|
+
static forRoot(featureFlags) {
|
|
12
|
+
const state = featureFlags.readEnvironment(process.env);
|
|
13
|
+
return {
|
|
14
|
+
exports: [FEATURE_FLAGS],
|
|
15
|
+
global: true,
|
|
16
|
+
module: this,
|
|
17
|
+
providers: [{ provide: FEATURE_FLAGS, useValue: new EnvironmentFeatureFlags(featureFlags, state) }],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
FeatureFlagsRootModule = __decorate([
|
|
22
|
+
Module({})
|
|
23
|
+
], FeatureFlagsRootModule);
|
|
24
|
+
export { FeatureFlagsRootModule };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './feature-flags-root.module.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './feature-flags-root.module.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { App } from 'vue';
|
|
2
|
+
import type { FeatureFlagSetInterface } from '../define-feature-flags.js';
|
|
3
|
+
import type { FeatureFlagState } from '../feature-flag-registry.js';
|
|
4
|
+
export interface FeatureFlagChannelInterface {
|
|
5
|
+
readonly key: string;
|
|
6
|
+
restore(app: App, serialized: string | undefined): void;
|
|
7
|
+
withValue(state: FeatureFlagState): FeatureFlagChannelValueInterface;
|
|
8
|
+
}
|
|
9
|
+
export interface FeatureFlagChannelValueInterface {
|
|
10
|
+
apply(app: App): void;
|
|
11
|
+
readonly key: string;
|
|
12
|
+
readonly serialized: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const createFeatureFlagChannel: (featureFlags: FeatureFlagSetInterface) => FeatureFlagChannelInterface;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { parseFeatureFlagState } from './parse-feature-flag-state.js';
|
|
2
|
+
import { FEATURE_FLAG_STATE } from './use-feature-flag.js';
|
|
3
|
+
const FEATURE_FLAG_CHANNEL_KEY = 'ssrFeatureFlags';
|
|
4
|
+
export const createFeatureFlagChannel = (featureFlags) => ({
|
|
5
|
+
key: FEATURE_FLAG_CHANNEL_KEY,
|
|
6
|
+
restore: (app, serialized) => {
|
|
7
|
+
app.provide(FEATURE_FLAG_STATE, parseFeatureFlagState(featureFlags, serialized));
|
|
8
|
+
},
|
|
9
|
+
withValue: (state) => ({
|
|
10
|
+
apply: (app) => {
|
|
11
|
+
app.provide(FEATURE_FLAG_STATE, state);
|
|
12
|
+
},
|
|
13
|
+
key: FEATURE_FLAG_CHANNEL_KEY,
|
|
14
|
+
serialized: JSON.stringify(state),
|
|
15
|
+
}),
|
|
16
|
+
});
|
package/dist/esm/vue/index.d.ts
CHANGED
package/dist/esm/vue/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@application-kit/feature-flags",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Фичефлаги из окружения: набор флагов параметром, порт с адаптером окружения, SSR-состояние для Vue и фейк для спек",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"feature-flags",
|
|
@@ -32,6 +32,16 @@
|
|
|
32
32
|
"default": "./dist/cjs/index.js"
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
|
+
"./nest": {
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./dist/esm/nest/index.d.ts",
|
|
38
|
+
"default": "./dist/esm/nest/index.js"
|
|
39
|
+
},
|
|
40
|
+
"require": {
|
|
41
|
+
"types": "./dist/cjs/nest/index.d.ts",
|
|
42
|
+
"default": "./dist/cjs/nest/index.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
35
45
|
"./package.json": "./package.json",
|
|
36
46
|
"./testing": {
|
|
37
47
|
"import": {
|
|
@@ -61,13 +71,15 @@
|
|
|
61
71
|
"dist"
|
|
62
72
|
],
|
|
63
73
|
"scripts": {
|
|
64
|
-
"build": "rm -rf ./dist && node ../../scripts/barrels.mjs create ./src --entry-only=testing,vue && tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json && node ../../scripts/barrels.mjs clean ./src && node ../../scripts/mark-cjs.mjs ./dist/cjs",
|
|
74
|
+
"build": "rm -rf ./dist && node ../../scripts/barrels.mjs create ./src --entry-only=nest,testing,vue && tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json && node ../../scripts/barrels.mjs clean ./src && node ../../scripts/mark-cjs.mjs ./dist/cjs",
|
|
65
75
|
"lint:tsc": "tsc --noEmit --pretty --project .",
|
|
66
76
|
"test:mutation": "stryker run",
|
|
67
77
|
"test:unit": "vitest run"
|
|
68
78
|
},
|
|
69
79
|
"devDependencies": {
|
|
70
80
|
"@libs-for-dev/nestjs-config-module": "3.0.0",
|
|
81
|
+
"@nestjs/common": "^11.2.7",
|
|
82
|
+
"@nestjs/testing": "^11.2.7",
|
|
71
83
|
"@stryker-mutator/core": "^10.0.0",
|
|
72
84
|
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
73
85
|
"@types/node": "^26.2.0",
|
|
@@ -78,9 +90,13 @@
|
|
|
78
90
|
},
|
|
79
91
|
"peerDependencies": {
|
|
80
92
|
"@libs-for-dev/nestjs-config-module": "^3.0.0",
|
|
93
|
+
"@nestjs/common": "^11.0.0",
|
|
81
94
|
"vue": "^3.5.0"
|
|
82
95
|
},
|
|
83
96
|
"peerDependenciesMeta": {
|
|
97
|
+
"@nestjs/common": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
84
100
|
"vue": {
|
|
85
101
|
"optional": true
|
|
86
102
|
}
|