@application-kit/feature-flags 0.0.0-stage → 0.0.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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +194 -2
  3. package/dist/cjs/constants.d.ts +1 -0
  4. package/dist/cjs/constants.js +4 -0
  5. package/dist/cjs/define-feature-flags.d.ts +13 -0
  6. package/dist/cjs/define-feature-flags.js +39 -0
  7. package/dist/cjs/environment-feature-flags.d.ts +10 -0
  8. package/dist/cjs/environment-feature-flags.js +18 -0
  9. package/dist/cjs/errors/index.d.ts +3 -0
  10. package/dist/cjs/errors/index.js +19 -0
  11. package/dist/cjs/errors/missing-feature-flag.error.d.ts +3 -0
  12. package/dist/cjs/errors/missing-feature-flag.error.js +10 -0
  13. package/dist/cjs/errors/unknown-feature-flag-name.error.d.ts +3 -0
  14. package/dist/cjs/errors/unknown-feature-flag-name.error.js +10 -0
  15. package/dist/cjs/errors/unknown-feature-flag.error.d.ts +3 -0
  16. package/dist/cjs/errors/unknown-feature-flag.error.js +10 -0
  17. package/dist/cjs/feature-flag-registry.d.ts +7 -0
  18. package/dist/cjs/feature-flag-registry.js +2 -0
  19. package/dist/cjs/feature-flags.interface.d.ts +5 -0
  20. package/dist/cjs/feature-flags.interface.js +2 -0
  21. package/dist/cjs/index.d.ts +6 -0
  22. package/dist/cjs/index.js +22 -0
  23. package/dist/cjs/package.json +3 -0
  24. package/dist/cjs/testing/fake-feature-flags.d.ts +6 -0
  25. package/dist/cjs/testing/fake-feature-flags.js +10 -0
  26. package/dist/cjs/testing/index.d.ts +1 -0
  27. package/dist/cjs/testing/index.js +17 -0
  28. package/dist/cjs/vue/index.d.ts +2 -0
  29. package/dist/cjs/vue/index.js +18 -0
  30. package/dist/cjs/vue/parse-feature-flag-state.d.ts +3 -0
  31. package/dist/cjs/vue/parse-feature-flag-state.js +17 -0
  32. package/dist/cjs/vue/use-feature-flag.d.ts +4 -0
  33. package/dist/cjs/vue/use-feature-flag.js +14 -0
  34. package/dist/esm/constants.d.ts +1 -0
  35. package/dist/esm/constants.js +1 -0
  36. package/dist/esm/define-feature-flags.d.ts +13 -0
  37. package/dist/esm/define-feature-flags.js +35 -0
  38. package/dist/esm/environment-feature-flags.d.ts +10 -0
  39. package/dist/esm/environment-feature-flags.js +14 -0
  40. package/dist/esm/errors/index.d.ts +3 -0
  41. package/dist/esm/errors/index.js +3 -0
  42. package/dist/esm/errors/missing-feature-flag.error.d.ts +3 -0
  43. package/dist/esm/errors/missing-feature-flag.error.js +6 -0
  44. package/dist/esm/errors/unknown-feature-flag-name.error.d.ts +3 -0
  45. package/dist/esm/errors/unknown-feature-flag-name.error.js +6 -0
  46. package/dist/esm/errors/unknown-feature-flag.error.d.ts +3 -0
  47. package/dist/esm/errors/unknown-feature-flag.error.js +6 -0
  48. package/dist/esm/feature-flag-registry.d.ts +7 -0
  49. package/dist/esm/feature-flag-registry.js +1 -0
  50. package/dist/esm/feature-flags.interface.d.ts +5 -0
  51. package/dist/esm/feature-flags.interface.js +1 -0
  52. package/dist/esm/index.d.ts +6 -0
  53. package/dist/esm/index.js +6 -0
  54. package/dist/esm/testing/fake-feature-flags.d.ts +6 -0
  55. package/dist/esm/testing/fake-feature-flags.js +6 -0
  56. package/dist/esm/testing/index.d.ts +1 -0
  57. package/dist/esm/testing/index.js +1 -0
  58. package/dist/esm/vue/index.d.ts +2 -0
  59. package/dist/esm/vue/index.js +2 -0
  60. package/dist/esm/vue/parse-feature-flag-state.d.ts +3 -0
  61. package/dist/esm/vue/parse-feature-flag-state.js +13 -0
  62. package/dist/esm/vue/use-feature-flag.d.ts +4 -0
  63. package/dist/esm/vue/use-feature-flag.js +10 -0
  64. package/package.json +91 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 application-kit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,195 @@
1
- # Temporary Holding Version
1
+ # @application-kit/feature-flags
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Фичефлаги из окружения: набор флагов объявляет приложение, пакет даёт порт чтения с адаптером окружения,
4
+ SSR-состояние для Vue и фейк для спек. Флагов продукта в пакете нет.
5
+
6
+ Два правила держат всё остальное:
7
+
8
+ 1. **Имя флага объявляется ровно один раз — в наборе приложения.** Из него выводится тип имени, из него же
9
+ адаптер узнаёт, какие переменные окружения существуют.
10
+ 2. **Состояния «по умолчанию» у флага нет.** Значение каждого флага приходит из окружения, а пропущенная
11
+ переменная роняет старт процесса. Дефолт в коде сделал бы забытую настройку окружения неотличимой от
12
+ сознательного выбора — а ищут как раз её, когда стенд ведёт себя не так, как договаривались.
13
+
14
+ ## Установка
15
+
16
+ ```sh
17
+ yarn add @application-kit/feature-flags @libs-for-dev/nestjs-config-module
18
+ ```
19
+
20
+ Peer: `@libs-for-dev/nestjs-config-module` (подпуть `/parse-boolean` — разбор булевых переменных, без
21
+ зависимостей); `vue` — только для входа `/vue`.
22
+
23
+ ## Входы
24
+
25
+ | Вход | Что внутри | Зависимости |
26
+ |---|---|---|
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` |
29
+ | `@application-kit/feature-flags/testing` | `FakeFeatureFlags` | — |
30
+
31
+ Корень грузится и в клиентском бандле (набор нужен разбору SSR-состояния), поэтому связки с Nest в пакете нет:
32
+ класс окружения и регистрацию адаптера под токеном делает приложение (пример ниже).
33
+
34
+ ## Набор флагов
35
+
36
+ Тип и значение объявляются рядом, в одном файле приложения:
37
+
38
+ ```ts
39
+ import { defineFeatureFlags } from '@application-kit/feature-flags'
40
+
41
+ declare module '@application-kit/feature-flags' {
42
+ interface FeatureFlagRegistryInterface {
43
+ NEW_CHECKOUT: true
44
+ }
45
+ }
46
+
47
+ export const featureFlags = defineFeatureFlags({
48
+ NEW_CHECKOUT: { isPublic: true },
49
+ })
50
+ ```
51
+
52
+ Ключи записи проверяются против реестра в обе стороны: флаг реестра без записи и запись без флага в реестре
53
+ не компилируются.
54
+
55
+ Единственное решение в записи — **`isPublic`**: уезжает ли флаг на клиент в SSR-состоянии. Наружу идёт явный
56
+ список, а не всё состояние: серверный флаг (доступ к интеграции, аварийный рубильник) в разметке страницы — это
57
+ утечка плана работ.
58
+
59
+ Префикс переменных — опция, по умолчанию `FEATURE_`: `defineFeatureFlags({ … }, { environmentPrefix: 'FLAG_' })`.
60
+ Пустой префикс — `RangeError`.
61
+
62
+ ## Окружение
63
+
64
+ Имя переменной — префикс плюс имя флага: `FEATURE_NEW_CHECKOUT`. `featureFlags.readEnvironment(source)` разбирает
65
+ окружение в состояние:
66
+
67
+ | Значение | Результат |
68
+ |---|---|
69
+ | `false`, `0`, пустая строка (регистр и пробелы по краям не важны) | выключен |
70
+ | любая другая непустая строка — `true`, `1`, `on`, `yes` | **включён** |
71
+ | переменной нет | **отказ** — `MissingFeatureFlagError` со списком недостающих переменных |
72
+ | переменная с префиксом, которой нет в наборе | **отказ** — `UnknownFeatureFlagError` со списком |
73
+
74
+ Разбор значения — `parseBoolean` конфиг-пакета: список выключающих строк один на все булевы переменные. Обе
75
+ проверки собирают полный список до броска, чтобы отказ назвал все проблемы разом; незнакомая переменная
76
+ проверяется первой — опечатка даёт сразу и лишнее имя, и недостающее.
77
+
78
+ ### Связка с Nest (в приложении)
79
+
80
+ Разбор ставится в класс окружения, чтобы отказ пришёл при сборке графа конфигов, а не на первом запросе:
81
+
82
+ ```ts
83
+ import { EnvironmentFeatureFlags } from '@application-kit/feature-flags'
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 }
105
+ ```
106
+
107
+ ## Чтение на сервере
108
+
109
+ Через порт по токену, а не чтением `process.env`:
110
+
111
+ ```ts
112
+ import type { FeatureFlagsInterface } from '@application-kit/feature-flags'
113
+ import { FEATURE_FLAGS } from '@application-kit/feature-flags'
114
+
115
+ export class SomeHandler {
116
+ public constructor(@Inject(FEATURE_FLAGS) private readonly featureFlags: FeatureFlagsInterface) {}
117
+
118
+ public execute(): void {
119
+ if (this.featureFlags.isEnabled('NEW_CHECKOUT')) { /* … */ }
120
+ }
121
+ }
122
+ ```
123
+
124
+ Чтение синхронное, и это решение: асинхронный `isEnabled` заставил бы ждать каждого потребителя, включая шаблон
125
+ Vue, где `await` в разметке невозможен. Адаптер с хранением в БД остаётся возможным — он держит обновляемый
126
+ снимок в памяти и встаёт под тот же токен без правок потребителей.
127
+
128
+ ## Клиент (Vue)
129
+
130
+ Публичный срез уезжает на страницу SSR-состоянием:
131
+
132
+ ```ts
133
+ // сервер, перед renderToString
134
+ const state = featureFlagsPort.publicState()
135
+ app.provide(FEATURE_FLAG_STATE, state)
136
+ // … состояние сериализуется в страницу строкой (JSON.stringify)
137
+
138
+ // клиент, до app.mount()
139
+ app.provide(FEATURE_FLAG_STATE, parseFeatureFlagState(featureFlags, window.ssrFeatureFlags))
140
+ ```
141
+
142
+ В компоненте:
143
+
144
+ ```vue
145
+ <script setup lang="ts">
146
+ import { useFeatureFlag } from '@application-kit/feature-flags/vue'
147
+
148
+ const isNewCheckoutEnabled = useFeatureFlag('NEW_CHECKOUT')
149
+ </script>
150
+
151
+ <template>
152
+ <NewCheckout v-if="isNewCheckoutEnabled" />
153
+ </template>
154
+ ```
155
+
156
+ - Без `provide` всё выключено: до прихода SSR-состояния помеченного фрагмента на странице быть не должно —
157
+ показать лишнее и убрать при гидрации хуже, чем не показать.
158
+ - Имя, которого нет в предоставленном состоянии, — `UnknownFeatureFlagNameError` со списком известных: имя из
159
+ данных без проверки дало бы `undefined`, и фрагмент молча исчез бы со страницы.
160
+ - `parseFeatureFlagState` не бросает ни на какой форме испорченного провода: разбор стоит до `app.mount()`, и
161
+ бросок убил бы весь бутстрап клиента. Незнакомое имя на проводе игнорируется, пропущенное и не-булево —
162
+ выключено: страница старой версии не роняет клиент новой во время выкатки.
163
+
164
+ ## Тесты потребителя
165
+
166
+ Через фейк порта, без `process.env` и `vi.stubEnv`. База фейка — «всё выключено», поэтому спека, которой флаг
167
+ нужен включённым, говорит это явно:
168
+
169
+ ```ts
170
+ import { FakeFeatureFlags } from '@application-kit/feature-flags/testing'
171
+
172
+ const port = new FakeFeatureFlags(featureFlags, { NEW_CHECKOUT: true })
173
+ ```
174
+
175
+ Снимок `publicState()` фейка строится как у адаптера: непубличные флаги в нём выключены. Для компонента —
176
+ `provide` состояния:
177
+
178
+ ```ts
179
+ mount(SomeComponent, {
180
+ global: { provide: { [FEATURE_FLAG_STATE]: featureFlags.stateOf((name) => name === 'NEW_CHECKOUT') } },
181
+ })
182
+ ```
183
+
184
+ ## Типовые отказы
185
+
186
+ | Симптом | Причина | Что делать |
187
+ |---|---|---|
188
+ | `MissingFeatureFlagError: Missing feature flags in environment: FEATURE_X` на старте | флаг есть в наборе, переменная в окружении не задана | дописать переменную в окружение каждого процесса |
189
+ | `UnknownFeatureFlagError: Unknown feature flags in environment: FEATURE_X` на старте | переменная есть, флага в наборе нет — опечатка или флаг снят из кода и забыт в окружении | поправить имя или убрать переменную |
190
+ | `UnknownFeatureFlagNameError: Unknown feature flag requested: X. Known flags: …` при рендере | опечатка в имени у `useFeatureFlag` | поправить имя по списку в тексте ошибки |
191
+ | Переменная выставлена, а на клиенте флаг выключен | флаг непубличный (`isPublic: false`) | проверить `isPublic` в наборе |
192
+
193
+ ## Чего здесь нет
194
+
195
+ Адаптера с хранением в БД, процента раскатки, таргетинга по пользователю, UI управления флагами.
@@ -0,0 +1 @@
1
+ export declare const FEATURE_FLAGS: unique symbol;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FEATURE_FLAGS = void 0;
4
+ exports.FEATURE_FLAGS = Symbol('FEATURE_FLAGS');
@@ -0,0 +1,13 @@
1
+ import type { FeatureFlagDeclaration, FeatureFlagName, FeatureFlagState } from './feature-flag-registry.js';
2
+ export interface FeatureFlagSetInterface {
3
+ readonly environmentPrefix: string;
4
+ isKnown(name: string): name is FeatureFlagName;
5
+ isPublic(name: FeatureFlagName): boolean;
6
+ readonly names: readonly FeatureFlagName[];
7
+ readEnvironment(source: Readonly<Record<string, unknown>>): FeatureFlagState;
8
+ stateOf(isEnabled: (name: FeatureFlagName) => boolean): FeatureFlagState;
9
+ }
10
+ export interface FeatureFlagSetOptionsInterface {
11
+ readonly environmentPrefix?: string;
12
+ }
13
+ export declare const defineFeatureFlags: (declarations: Readonly<Record<FeatureFlagName, FeatureFlagDeclaration>>, options?: FeatureFlagSetOptionsInterface) => FeatureFlagSetInterface;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineFeatureFlags = void 0;
4
+ const parse_boolean_1 = require("@libs-for-dev/nestjs-config-module/parse-boolean");
5
+ const missing_feature_flag_error_js_1 = require("./errors/missing-feature-flag.error.js");
6
+ const unknown_feature_flag_error_js_1 = require("./errors/unknown-feature-flag.error.js");
7
+ const DEFAULT_ENVIRONMENT_PREFIX = 'FEATURE_';
8
+ const defineFeatureFlags = (declarations, options = {}) => {
9
+ const environmentPrefix = options.environmentPrefix ?? DEFAULT_ENVIRONMENT_PREFIX;
10
+ if (environmentPrefix === '') {
11
+ throw new RangeError('Префикс переменных окружения флагов не может быть пустым');
12
+ }
13
+ const isKnown = (name) => Object.hasOwn(declarations, name);
14
+ const names = Object.keys(declarations);
15
+ const variableOf = (name) => `${environmentPrefix}${name}`;
16
+ const publicityByName = new Map(Object.entries(declarations).map(([name, { isPublic }]) => [name, isPublic]));
17
+ const stateOf = (isEnabled) => Object.fromEntries(names.map((name) => [name, isEnabled(name)]));
18
+ const readEnvironment = (source) => {
19
+ const unknownVariables = Object.keys(source)
20
+ .filter((key) => key.startsWith(environmentPrefix) && !isKnown(key.slice(environmentPrefix.length)));
21
+ if (unknownVariables.length > 0) {
22
+ throw new unknown_feature_flag_error_js_1.UnknownFeatureFlagError(unknownVariables);
23
+ }
24
+ const missingVariables = names.map((name) => variableOf(name)).filter((variable) => source[variable] === undefined);
25
+ if (missingVariables.length > 0) {
26
+ throw new missing_feature_flag_error_js_1.MissingFeatureFlagError(missingVariables);
27
+ }
28
+ return stateOf((name) => (0, parse_boolean_1.parseBoolean)(source[variableOf(name)]) ?? false);
29
+ };
30
+ return {
31
+ environmentPrefix,
32
+ isKnown,
33
+ isPublic: (name) => publicityByName.get(name) === true,
34
+ names,
35
+ readEnvironment,
36
+ stateOf,
37
+ };
38
+ };
39
+ exports.defineFeatureFlags = defineFeatureFlags;
@@ -0,0 +1,10 @@
1
+ import type { FeatureFlagSetInterface } from './define-feature-flags.js';
2
+ import type { FeatureFlagName, FeatureFlagState } from './feature-flag-registry.js';
3
+ import type { FeatureFlagsInterface } from './feature-flags.interface.js';
4
+ export declare class EnvironmentFeatureFlags implements FeatureFlagsInterface {
5
+ private readonly featureFlags;
6
+ private readonly state;
7
+ constructor(featureFlags: FeatureFlagSetInterface, state: FeatureFlagState);
8
+ isEnabled(name: FeatureFlagName): boolean;
9
+ publicState(): FeatureFlagState;
10
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EnvironmentFeatureFlags = void 0;
4
+ class EnvironmentFeatureFlags {
5
+ featureFlags;
6
+ state;
7
+ constructor(featureFlags, state) {
8
+ this.featureFlags = featureFlags;
9
+ this.state = state;
10
+ }
11
+ isEnabled(name) {
12
+ return this.state[name];
13
+ }
14
+ publicState() {
15
+ return this.featureFlags.stateOf((name) => this.featureFlags.isPublic(name) && this.state[name]);
16
+ }
17
+ }
18
+ exports.EnvironmentFeatureFlags = EnvironmentFeatureFlags;
@@ -0,0 +1,3 @@
1
+ export * from './missing-feature-flag.error.js';
2
+ export * from './unknown-feature-flag-name.error.js';
3
+ export * from './unknown-feature-flag.error.js';
@@ -0,0 +1,19 @@
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("./missing-feature-flag.error.js"), exports);
18
+ __exportStar(require("./unknown-feature-flag-name.error.js"), exports);
19
+ __exportStar(require("./unknown-feature-flag.error.js"), exports);
@@ -0,0 +1,3 @@
1
+ export declare class MissingFeatureFlagError extends Error {
2
+ constructor(missingVariables: readonly string[], options?: ErrorOptions);
3
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MissingFeatureFlagError = void 0;
4
+ class MissingFeatureFlagError extends Error {
5
+ constructor(missingVariables, options) {
6
+ super(`Missing feature flags in environment: ${missingVariables.join(', ')}`, options);
7
+ this.name = 'MissingFeatureFlagError';
8
+ }
9
+ }
10
+ exports.MissingFeatureFlagError = MissingFeatureFlagError;
@@ -0,0 +1,3 @@
1
+ export declare class UnknownFeatureFlagNameError extends Error {
2
+ constructor(requestedName: string, knownNames: readonly string[], options?: ErrorOptions);
3
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnknownFeatureFlagNameError = void 0;
4
+ class UnknownFeatureFlagNameError extends Error {
5
+ constructor(requestedName, knownNames, options) {
6
+ super(`Unknown feature flag requested: ${requestedName}. Known flags: ${knownNames.join(', ')}`, options);
7
+ this.name = 'UnknownFeatureFlagNameError';
8
+ }
9
+ }
10
+ exports.UnknownFeatureFlagNameError = UnknownFeatureFlagNameError;
@@ -0,0 +1,3 @@
1
+ export declare class UnknownFeatureFlagError extends Error {
2
+ constructor(unknownVariables: readonly string[], options?: ErrorOptions);
3
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnknownFeatureFlagError = void 0;
4
+ class UnknownFeatureFlagError extends Error {
5
+ constructor(unknownVariables, options) {
6
+ super(`Unknown feature flags in environment: ${unknownVariables.join(', ')}`, options);
7
+ this.name = 'UnknownFeatureFlagError';
8
+ }
9
+ }
10
+ exports.UnknownFeatureFlagError = UnknownFeatureFlagError;
@@ -0,0 +1,7 @@
1
+ export type FeatureFlagDeclaration = Readonly<{
2
+ isPublic: boolean;
3
+ }>;
4
+ export type FeatureFlagName = Extract<keyof FeatureFlagRegistryInterface, string>;
5
+ export interface FeatureFlagRegistryInterface {
6
+ }
7
+ export type FeatureFlagState = Readonly<Record<FeatureFlagName, boolean>>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ import type { FeatureFlagName, FeatureFlagState } from './feature-flag-registry.js';
2
+ export interface FeatureFlagsInterface {
3
+ isEnabled(name: FeatureFlagName): boolean;
4
+ publicState(): FeatureFlagState;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ export * from './errors/index.js';
2
+ export * from './constants.js';
3
+ export * from './define-feature-flags.js';
4
+ export * from './environment-feature-flags.js';
5
+ export * from './feature-flag-registry.js';
6
+ export * from './feature-flags.interface.js';
@@ -0,0 +1,22 @@
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("./errors/index.js"), exports);
18
+ __exportStar(require("./constants.js"), exports);
19
+ __exportStar(require("./define-feature-flags.js"), exports);
20
+ __exportStar(require("./environment-feature-flags.js"), exports);
21
+ __exportStar(require("./feature-flag-registry.js"), exports);
22
+ __exportStar(require("./feature-flags.interface.js"), exports);
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,6 @@
1
+ import type { FeatureFlagSetInterface } from '../define-feature-flags.js';
2
+ import type { FeatureFlagName } from '../feature-flag-registry.js';
3
+ import { EnvironmentFeatureFlags } from '../environment-feature-flags.js';
4
+ export declare class FakeFeatureFlags extends EnvironmentFeatureFlags {
5
+ constructor(featureFlags: FeatureFlagSetInterface, overrides?: Partial<Record<FeatureFlagName, boolean>>);
6
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FakeFeatureFlags = void 0;
4
+ const environment_feature_flags_js_1 = require("../environment-feature-flags.js");
5
+ class FakeFeatureFlags extends environment_feature_flags_js_1.EnvironmentFeatureFlags {
6
+ constructor(featureFlags, overrides = {}) {
7
+ super(featureFlags, { ...featureFlags.stateOf(() => false), ...overrides });
8
+ }
9
+ }
10
+ exports.FakeFeatureFlags = FakeFeatureFlags;
@@ -0,0 +1 @@
1
+ export * from './fake-feature-flags.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("./fake-feature-flags.js"), exports);
@@ -0,0 +1,2 @@
1
+ export * from './parse-feature-flag-state.js';
2
+ export * from './use-feature-flag.js';
@@ -0,0 +1,18 @@
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("./parse-feature-flag-state.js"), exports);
18
+ __exportStar(require("./use-feature-flag.js"), exports);
@@ -0,0 +1,3 @@
1
+ import type { FeatureFlagSetInterface } from '../define-feature-flags.js';
2
+ import type { FeatureFlagState } from '../feature-flag-registry.js';
3
+ export declare const parseFeatureFlagState: (featureFlags: FeatureFlagSetInterface, raw: unknown) => FeatureFlagState;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseFeatureFlagState = void 0;
4
+ const parseFeatureFlagState = (featureFlags, raw) => {
5
+ if (typeof raw !== 'string') {
6
+ return featureFlags.stateOf(() => false);
7
+ }
8
+ try {
9
+ const parsed = JSON.parse(raw);
10
+ const state = new Map(Object.entries(parsed ?? {}));
11
+ return featureFlags.stateOf((name) => state.get(name) === true);
12
+ }
13
+ catch {
14
+ return featureFlags.stateOf(() => false);
15
+ }
16
+ };
17
+ exports.parseFeatureFlagState = parseFeatureFlagState;
@@ -0,0 +1,4 @@
1
+ import type { ComputedRef, InjectionKey } from 'vue';
2
+ import type { FeatureFlagName, FeatureFlagState } from '../feature-flag-registry.js';
3
+ export declare const FEATURE_FLAG_STATE: InjectionKey<FeatureFlagState>;
4
+ export declare const useFeatureFlag: (name: FeatureFlagName) => ComputedRef<boolean>;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useFeatureFlag = exports.FEATURE_FLAG_STATE = void 0;
4
+ const vue_1 = require("vue");
5
+ const unknown_feature_flag_name_error_js_1 = require("../errors/unknown-feature-flag-name.error.js");
6
+ exports.FEATURE_FLAG_STATE = Symbol('FEATURE_FLAG_STATE');
7
+ const useFeatureFlag = (name) => {
8
+ const state = (0, vue_1.inject)(exports.FEATURE_FLAG_STATE, undefined);
9
+ if (state !== undefined && !Object.hasOwn(state, name)) {
10
+ throw new unknown_feature_flag_name_error_js_1.UnknownFeatureFlagNameError(name, Object.keys(state));
11
+ }
12
+ return (0, vue_1.computed)(() => state?.[name] === true);
13
+ };
14
+ exports.useFeatureFlag = useFeatureFlag;
@@ -0,0 +1 @@
1
+ export declare const FEATURE_FLAGS: unique symbol;
@@ -0,0 +1 @@
1
+ export const FEATURE_FLAGS = Symbol('FEATURE_FLAGS');
@@ -0,0 +1,13 @@
1
+ import type { FeatureFlagDeclaration, FeatureFlagName, FeatureFlagState } from './feature-flag-registry.js';
2
+ export interface FeatureFlagSetInterface {
3
+ readonly environmentPrefix: string;
4
+ isKnown(name: string): name is FeatureFlagName;
5
+ isPublic(name: FeatureFlagName): boolean;
6
+ readonly names: readonly FeatureFlagName[];
7
+ readEnvironment(source: Readonly<Record<string, unknown>>): FeatureFlagState;
8
+ stateOf(isEnabled: (name: FeatureFlagName) => boolean): FeatureFlagState;
9
+ }
10
+ export interface FeatureFlagSetOptionsInterface {
11
+ readonly environmentPrefix?: string;
12
+ }
13
+ export declare const defineFeatureFlags: (declarations: Readonly<Record<FeatureFlagName, FeatureFlagDeclaration>>, options?: FeatureFlagSetOptionsInterface) => FeatureFlagSetInterface;
@@ -0,0 +1,35 @@
1
+ import { parseBoolean } from '@libs-for-dev/nestjs-config-module/parse-boolean';
2
+ import { MissingFeatureFlagError } from './errors/missing-feature-flag.error.js';
3
+ import { UnknownFeatureFlagError } from './errors/unknown-feature-flag.error.js';
4
+ const DEFAULT_ENVIRONMENT_PREFIX = 'FEATURE_';
5
+ export const defineFeatureFlags = (declarations, options = {}) => {
6
+ const environmentPrefix = options.environmentPrefix ?? DEFAULT_ENVIRONMENT_PREFIX;
7
+ if (environmentPrefix === '') {
8
+ throw new RangeError('Префикс переменных окружения флагов не может быть пустым');
9
+ }
10
+ const isKnown = (name) => Object.hasOwn(declarations, name);
11
+ const names = Object.keys(declarations);
12
+ const variableOf = (name) => `${environmentPrefix}${name}`;
13
+ const publicityByName = new Map(Object.entries(declarations).map(([name, { isPublic }]) => [name, isPublic]));
14
+ const stateOf = (isEnabled) => Object.fromEntries(names.map((name) => [name, isEnabled(name)]));
15
+ const readEnvironment = (source) => {
16
+ const unknownVariables = Object.keys(source)
17
+ .filter((key) => key.startsWith(environmentPrefix) && !isKnown(key.slice(environmentPrefix.length)));
18
+ if (unknownVariables.length > 0) {
19
+ throw new UnknownFeatureFlagError(unknownVariables);
20
+ }
21
+ const missingVariables = names.map((name) => variableOf(name)).filter((variable) => source[variable] === undefined);
22
+ if (missingVariables.length > 0) {
23
+ throw new MissingFeatureFlagError(missingVariables);
24
+ }
25
+ return stateOf((name) => parseBoolean(source[variableOf(name)]) ?? false);
26
+ };
27
+ return {
28
+ environmentPrefix,
29
+ isKnown,
30
+ isPublic: (name) => publicityByName.get(name) === true,
31
+ names,
32
+ readEnvironment,
33
+ stateOf,
34
+ };
35
+ };
@@ -0,0 +1,10 @@
1
+ import type { FeatureFlagSetInterface } from './define-feature-flags.js';
2
+ import type { FeatureFlagName, FeatureFlagState } from './feature-flag-registry.js';
3
+ import type { FeatureFlagsInterface } from './feature-flags.interface.js';
4
+ export declare class EnvironmentFeatureFlags implements FeatureFlagsInterface {
5
+ private readonly featureFlags;
6
+ private readonly state;
7
+ constructor(featureFlags: FeatureFlagSetInterface, state: FeatureFlagState);
8
+ isEnabled(name: FeatureFlagName): boolean;
9
+ publicState(): FeatureFlagState;
10
+ }
@@ -0,0 +1,14 @@
1
+ export class EnvironmentFeatureFlags {
2
+ featureFlags;
3
+ state;
4
+ constructor(featureFlags, state) {
5
+ this.featureFlags = featureFlags;
6
+ this.state = state;
7
+ }
8
+ isEnabled(name) {
9
+ return this.state[name];
10
+ }
11
+ publicState() {
12
+ return this.featureFlags.stateOf((name) => this.featureFlags.isPublic(name) && this.state[name]);
13
+ }
14
+ }
@@ -0,0 +1,3 @@
1
+ export * from './missing-feature-flag.error.js';
2
+ export * from './unknown-feature-flag-name.error.js';
3
+ export * from './unknown-feature-flag.error.js';
@@ -0,0 +1,3 @@
1
+ export * from './missing-feature-flag.error.js';
2
+ export * from './unknown-feature-flag-name.error.js';
3
+ export * from './unknown-feature-flag.error.js';
@@ -0,0 +1,3 @@
1
+ export declare class MissingFeatureFlagError extends Error {
2
+ constructor(missingVariables: readonly string[], options?: ErrorOptions);
3
+ }
@@ -0,0 +1,6 @@
1
+ export class MissingFeatureFlagError extends Error {
2
+ constructor(missingVariables, options) {
3
+ super(`Missing feature flags in environment: ${missingVariables.join(', ')}`, options);
4
+ this.name = 'MissingFeatureFlagError';
5
+ }
6
+ }
@@ -0,0 +1,3 @@
1
+ export declare class UnknownFeatureFlagNameError extends Error {
2
+ constructor(requestedName: string, knownNames: readonly string[], options?: ErrorOptions);
3
+ }
@@ -0,0 +1,6 @@
1
+ export class UnknownFeatureFlagNameError extends Error {
2
+ constructor(requestedName, knownNames, options) {
3
+ super(`Unknown feature flag requested: ${requestedName}. Known flags: ${knownNames.join(', ')}`, options);
4
+ this.name = 'UnknownFeatureFlagNameError';
5
+ }
6
+ }
@@ -0,0 +1,3 @@
1
+ export declare class UnknownFeatureFlagError extends Error {
2
+ constructor(unknownVariables: readonly string[], options?: ErrorOptions);
3
+ }
@@ -0,0 +1,6 @@
1
+ export class UnknownFeatureFlagError extends Error {
2
+ constructor(unknownVariables, options) {
3
+ super(`Unknown feature flags in environment: ${unknownVariables.join(', ')}`, options);
4
+ this.name = 'UnknownFeatureFlagError';
5
+ }
6
+ }
@@ -0,0 +1,7 @@
1
+ export type FeatureFlagDeclaration = Readonly<{
2
+ isPublic: boolean;
3
+ }>;
4
+ export type FeatureFlagName = Extract<keyof FeatureFlagRegistryInterface, string>;
5
+ export interface FeatureFlagRegistryInterface {
6
+ }
7
+ export type FeatureFlagState = Readonly<Record<FeatureFlagName, boolean>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { FeatureFlagName, FeatureFlagState } from './feature-flag-registry.js';
2
+ export interface FeatureFlagsInterface {
3
+ isEnabled(name: FeatureFlagName): boolean;
4
+ publicState(): FeatureFlagState;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ export * from './errors/index.js';
2
+ export * from './constants.js';
3
+ export * from './define-feature-flags.js';
4
+ export * from './environment-feature-flags.js';
5
+ export * from './feature-flag-registry.js';
6
+ export * from './feature-flags.interface.js';
@@ -0,0 +1,6 @@
1
+ export * from './errors/index.js';
2
+ export * from './constants.js';
3
+ export * from './define-feature-flags.js';
4
+ export * from './environment-feature-flags.js';
5
+ export * from './feature-flag-registry.js';
6
+ export * from './feature-flags.interface.js';
@@ -0,0 +1,6 @@
1
+ import type { FeatureFlagSetInterface } from '../define-feature-flags.js';
2
+ import type { FeatureFlagName } from '../feature-flag-registry.js';
3
+ import { EnvironmentFeatureFlags } from '../environment-feature-flags.js';
4
+ export declare class FakeFeatureFlags extends EnvironmentFeatureFlags {
5
+ constructor(featureFlags: FeatureFlagSetInterface, overrides?: Partial<Record<FeatureFlagName, boolean>>);
6
+ }
@@ -0,0 +1,6 @@
1
+ import { EnvironmentFeatureFlags } from '../environment-feature-flags.js';
2
+ export class FakeFeatureFlags extends EnvironmentFeatureFlags {
3
+ constructor(featureFlags, overrides = {}) {
4
+ super(featureFlags, { ...featureFlags.stateOf(() => false), ...overrides });
5
+ }
6
+ }
@@ -0,0 +1 @@
1
+ export * from './fake-feature-flags.js';
@@ -0,0 +1 @@
1
+ export * from './fake-feature-flags.js';
@@ -0,0 +1,2 @@
1
+ export * from './parse-feature-flag-state.js';
2
+ export * from './use-feature-flag.js';
@@ -0,0 +1,2 @@
1
+ export * from './parse-feature-flag-state.js';
2
+ export * from './use-feature-flag.js';
@@ -0,0 +1,3 @@
1
+ import type { FeatureFlagSetInterface } from '../define-feature-flags.js';
2
+ import type { FeatureFlagState } from '../feature-flag-registry.js';
3
+ export declare const parseFeatureFlagState: (featureFlags: FeatureFlagSetInterface, raw: unknown) => FeatureFlagState;
@@ -0,0 +1,13 @@
1
+ export const parseFeatureFlagState = (featureFlags, raw) => {
2
+ if (typeof raw !== 'string') {
3
+ return featureFlags.stateOf(() => false);
4
+ }
5
+ try {
6
+ const parsed = JSON.parse(raw);
7
+ const state = new Map(Object.entries(parsed ?? {}));
8
+ return featureFlags.stateOf((name) => state.get(name) === true);
9
+ }
10
+ catch {
11
+ return featureFlags.stateOf(() => false);
12
+ }
13
+ };
@@ -0,0 +1,4 @@
1
+ import type { ComputedRef, InjectionKey } from 'vue';
2
+ import type { FeatureFlagName, FeatureFlagState } from '../feature-flag-registry.js';
3
+ export declare const FEATURE_FLAG_STATE: InjectionKey<FeatureFlagState>;
4
+ export declare const useFeatureFlag: (name: FeatureFlagName) => ComputedRef<boolean>;
@@ -0,0 +1,10 @@
1
+ import { computed, inject } from 'vue';
2
+ import { UnknownFeatureFlagNameError } from '../errors/unknown-feature-flag-name.error.js';
3
+ export const FEATURE_FLAG_STATE = Symbol('FEATURE_FLAG_STATE');
4
+ export const useFeatureFlag = (name) => {
5
+ const state = inject(FEATURE_FLAG_STATE, undefined);
6
+ if (state !== undefined && !Object.hasOwn(state, name)) {
7
+ throw new UnknownFeatureFlagNameError(name, Object.keys(state));
8
+ }
9
+ return computed(() => state?.[name] === true);
10
+ };
package/package.json CHANGED
@@ -1,6 +1,94 @@
1
1
  {
2
2
  "name": "@application-kit/feature-flags",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.0",
4
+ "description": "Фичефлаги из окружения: набор флагов параметром, порт с адаптером окружения, SSR-состояние для Vue и фейк для спек",
5
+ "keywords": [
6
+ "feature-flags",
7
+ "feature-toggles",
8
+ "ssr",
9
+ "vue"
10
+ ],
11
+ "homepage": "https://gitlab.com/application-kit/application-kit/-/tree/main/packages/feature-flags",
12
+ "bugs": "https://gitlab.com/application-kit/application-kit/-/issues",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://gitlab.com/application-kit/application-kit.git",
16
+ "directory": "packages/feature-flags"
17
+ },
18
+ "license": "MIT",
19
+ "author": {
20
+ "name": "application-kit"
21
+ },
22
+ "sideEffects": false,
23
+ "type": "module",
24
+ "exports": {
25
+ ".": {
26
+ "import": {
27
+ "types": "./dist/esm/index.d.ts",
28
+ "default": "./dist/esm/index.js"
29
+ },
30
+ "require": {
31
+ "types": "./dist/cjs/index.d.ts",
32
+ "default": "./dist/cjs/index.js"
33
+ }
34
+ },
35
+ "./package.json": "./package.json",
36
+ "./testing": {
37
+ "import": {
38
+ "types": "./dist/esm/testing/index.d.ts",
39
+ "default": "./dist/esm/testing/index.js"
40
+ },
41
+ "require": {
42
+ "types": "./dist/cjs/testing/index.d.ts",
43
+ "default": "./dist/cjs/testing/index.js"
44
+ }
45
+ },
46
+ "./vue": {
47
+ "import": {
48
+ "types": "./dist/esm/vue/index.d.ts",
49
+ "default": "./dist/esm/vue/index.js"
50
+ },
51
+ "require": {
52
+ "types": "./dist/cjs/vue/index.d.ts",
53
+ "default": "./dist/cjs/vue/index.js"
54
+ }
55
+ }
56
+ },
57
+ "main": "./dist/cjs/index.js",
58
+ "module": "./dist/esm/index.js",
59
+ "types": "./dist/cjs/index.d.ts",
60
+ "files": [
61
+ "dist"
62
+ ],
63
+ "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",
65
+ "lint:tsc": "tsc --noEmit --pretty --project .",
66
+ "test:mutation": "stryker run",
67
+ "test:unit": "vitest run"
68
+ },
69
+ "devDependencies": {
70
+ "@libs-for-dev/nestjs-config-module": "3.0.0",
71
+ "@stryker-mutator/core": "^10.0.0",
72
+ "@stryker-mutator/vitest-runner": "^10.0.0",
73
+ "@types/node": "^26.2.0",
74
+ "jsdom": "^30.0.1",
75
+ "typescript": "^6.0.3",
76
+ "vitest": "^4.1.11",
77
+ "vue": "^3.5.41"
78
+ },
79
+ "peerDependencies": {
80
+ "@libs-for-dev/nestjs-config-module": "^3.0.0",
81
+ "vue": "^3.5.0"
82
+ },
83
+ "peerDependenciesMeta": {
84
+ "vue": {
85
+ "optional": true
86
+ }
87
+ },
88
+ "engines": {
89
+ "node": ">=24"
90
+ },
91
+ "publishConfig": {
92
+ "access": "public"
93
+ }
6
94
  }