@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 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/vue` | `useFeatureFlag`, ключ `FEATURE_FLAG_STATE`, `parseFeatureFlagState` | `vue` |
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-состояния), поэтому связки с Nest в пакете нет:
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 { EnvironmentFeatureFlags } from '@application-kit/feature-flags'
83
+ import { FeatureFlagsRootModule } from '@application-kit/feature-flags/nest'
84
84
 
85
- export class FeatureFlagsEnvironment {
86
- @Expose()
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,5 @@
1
+ import type { DynamicModule } from '@nestjs/common';
2
+ import type { FeatureFlagSetInterface } from '../define-feature-flags.js';
3
+ export declare class FeatureFlagsRootModule {
4
+ static forRoot(featureFlags: FeatureFlagSetInterface): DynamicModule;
5
+ }
@@ -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;
@@ -1,2 +1,3 @@
1
+ export * from './feature-flag-channel.js';
1
2
  export * from './parse-feature-flag-state.js';
2
3
  export * from './use-feature-flag.js';
@@ -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,5 @@
1
+ import type { DynamicModule } from '@nestjs/common';
2
+ import type { FeatureFlagSetInterface } from '../define-feature-flags.js';
3
+ export declare class FeatureFlagsRootModule {
4
+ static forRoot(featureFlags: FeatureFlagSetInterface): DynamicModule;
5
+ }
@@ -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
+ });
@@ -1,2 +1,3 @@
1
+ export * from './feature-flag-channel.js';
1
2
  export * from './parse-feature-flag-state.js';
2
3
  export * from './use-feature-flag.js';
@@ -1,2 +1,3 @@
1
+ export * from './feature-flag-channel.js';
1
2
  export * from './parse-feature-flag-state.js';
2
3
  export * from './use-feature-flag.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@application-kit/feature-flags",
3
- "version": "0.1.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
  }