@tehw0lf/yaft 0.0.15 → 0.0.17

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 (51) hide show
  1. package/FeatureToggle.d.ts +20 -0
  2. package/FeatureToggle.js +72 -0
  3. package/README.md +25 -3
  4. package/evaluate.d.ts +42 -0
  5. package/evaluate.js +102 -0
  6. package/examples/ApiServiceBooleanProvider.d.ts +11 -0
  7. package/examples/ApiServiceBooleanProvider.js +85 -0
  8. package/examples/ApiServiceFeatureProvider.d.ts +17 -0
  9. package/examples/ApiServiceFeatureProvider.js +54 -0
  10. package/examples/LocalStorageBooleanProvider.d.ts +7 -0
  11. package/examples/LocalStorageBooleanProvider.js +23 -0
  12. package/examples/LocalStorageFeatureProvider.d.ts +14 -0
  13. package/examples/LocalStorageFeatureProvider.js +31 -0
  14. package/index.d.ts +3 -0
  15. package/index.js +14 -0
  16. package/mapping.d.ts +35 -0
  17. package/mapping.js +98 -0
  18. package/package.json +21 -9
  19. package/.github/workflows/build.yml +0 -28
  20. package/.github/workflows/security-scan.yml +0 -79
  21. package/CLAUDE.md +0 -151
  22. package/LICENSE +0 -21
  23. package/conformance.lock +0 -2
  24. package/jest.config.js +0 -16
  25. package/scripts/fetch-conformance.sh +0 -67
  26. package/src/FeatureToggle.ts +0 -95
  27. package/src/evaluate.ts +0 -122
  28. package/src/examples/ApiServiceBooleanProvider.ts +0 -82
  29. package/src/examples/ApiServiceFeatureProvider.ts +0 -52
  30. package/src/examples/LocalStorageBooleanProvider.ts +0 -25
  31. package/src/examples/LocalStorageFeatureProvider.ts +0 -31
  32. package/src/index.ts +0 -8
  33. package/src/mapping.ts +0 -92
  34. package/src/test/conformance-adapter/cases.ts +0 -104
  35. package/src/test/conformance-adapter/decorator.spec.ts +0 -376
  36. package/src/test/conformance-adapter/evaluation.spec.ts +0 -31
  37. package/src/test/conformance-adapter/mapping.spec.ts +0 -79
  38. package/src/test/decorator-behavior.spec.ts +0 -359
  39. package/src/test/error-handling.spec.ts +0 -396
  40. package/src/test/evaluate.spec.ts +0 -260
  41. package/src/test/feature-data.spec.ts +0 -102
  42. package/src/test/injectable-clock.spec.ts +0 -112
  43. package/src/test/integration.spec.ts +0 -472
  44. package/src/test/providers.spec.ts +0 -218
  45. package/src/test/test-boolean.json +0 -1
  46. package/src/test/test-feature.json +0 -20
  47. package/src/test/test-setup.ts +0 -2
  48. package/src/test/time-based-logic.spec.ts +0 -169
  49. package/src/test/yaft.demo.spec.ts +0 -53
  50. package/tsconfig.json +0 -14
  51. package/tsconfig.spec.json +0 -10
@@ -1,218 +0,0 @@
1
- import { Feature, FeatureProvider } from '../FeatureToggle';
2
- import { LocalStorageFeatureProvider } from '../examples/LocalStorageFeatureProvider';
3
- import { LocalStorageBooleanProvider } from '../examples/LocalStorageBooleanProvider';
4
-
5
- describe('Feature Providers', () => {
6
- describe('LocalStorageFeatureProvider', () => {
7
- let provider: LocalStorageFeatureProvider;
8
-
9
- beforeEach(() => {
10
- provider = new (class extends LocalStorageFeatureProvider {
11
- constructor() {
12
- super('');
13
- this.data = {
14
- 'myMethodToggle': {
15
- key: 'myMethodToggle',
16
- value: 'true',
17
- activeAt: '',
18
- disabledAt: ''
19
- },
20
- 'myClassToggle': {
21
- key: 'myClassToggle',
22
- value: 'false',
23
- activeAt: '',
24
- disabledAt: ''
25
- },
26
- 'myOtherMethodToggle': {
27
- key: 'myOtherMethodToggle',
28
- value: 'false',
29
- activeAt: '',
30
- disabledAt: ''
31
- }
32
- };
33
- }
34
- })();
35
- });
36
-
37
- it('should load configuration from file on construction', () => {
38
- expect(provider.data).toBeDefined();
39
- expect(typeof provider.data).toBe('object');
40
- });
41
-
42
- it('should return correct isEnabled for existing features', () => {
43
- expect(provider.isEnabled('myMethodToggle')).toBe(true);
44
- expect(provider.isEnabled('myClassToggle')).toBe(false);
45
- expect(provider.isEnabled('myOtherMethodToggle')).toBe(false);
46
- });
47
-
48
- it('should return false for non-existent features', () => {
49
- expect(provider.isEnabled('nonExistentFeature')).toBe(false);
50
- expect(provider.isEnabled('')).toBe(false);
51
- });
52
-
53
- it('should handle null/undefined features', () => {
54
- const mockProvider = new LocalStorageFeatureProvider('./non-existent-file.json');
55
- expect(mockProvider.isEnabled('anyKey')).toBe(false);
56
- });
57
-
58
- describe('time-based feature evaluation', () => {
59
- let timeProvider: LocalStorageFeatureProvider;
60
-
61
- beforeEach(() => {
62
- const pastDate = new Date(Date.now() - 86400000).toISOString(); // 1 day ago
63
- const futureDate = new Date(Date.now() + 86400000).toISOString(); // 1 day from now
64
-
65
- // Mock require to return time-based features
66
- const originalRequire = require;
67
- jest.doMock('./time-test-features.json', () => ({
68
- 'activeFeature': {
69
- key: 'activeFeature',
70
- value: 'true',
71
- activeAt: pastDate,
72
- disabledAt: futureDate
73
- },
74
- 'disabledFeature': {
75
- key: 'disabledFeature',
76
- value: 'true',
77
- activeAt: pastDate,
78
- disabledAt: pastDate // Already disabled
79
- },
80
- 'futureFeature': {
81
- key: 'futureFeature',
82
- value: 'true',
83
- activeAt: futureDate, // Not yet active
84
- disabledAt: ''
85
- }
86
- }), { virtual: true });
87
-
88
- timeProvider = new LocalStorageFeatureProvider('./time-test-features.json');
89
- });
90
-
91
- afterEach(() => {
92
- jest.clearAllMocks();
93
- });
94
-
95
- it('should respect activeAt timestamps', () => {
96
- // This test would need actual time-based test data
97
- // For now, test the logic with the existing test data
98
- expect(timeProvider.isEnabled).toBeDefined();
99
- });
100
-
101
- it('should respect disabledAt timestamps', () => {
102
- // This test would need actual time-based test data
103
- expect(timeProvider.isEnabled).toBeDefined();
104
- });
105
- });
106
- });
107
-
108
- describe('LocalStorageBooleanProvider', () => {
109
- let provider: LocalStorageBooleanProvider;
110
-
111
- beforeEach(() => {
112
- provider = new (class extends LocalStorageBooleanProvider {
113
- constructor() {
114
- super('');
115
- this.data = { 'myToggle': true };
116
- }
117
- })();
118
- });
119
-
120
- it('should load boolean configuration from file', () => {
121
- expect(provider.data).toBeDefined();
122
- expect(typeof provider.data).toBe('object');
123
- });
124
-
125
- it('should return correct boolean values', () => {
126
- expect(provider.isEnabled('myToggle')).toBe(true);
127
- });
128
-
129
- it('should return false for non-existent boolean features', () => {
130
- expect(provider.isEnabled('nonExistentToggle')).toBe(false);
131
- });
132
-
133
- it('should handle various boolean representations', () => {
134
- // Test with mock data containing different boolean formats
135
- const mockProvider = new (class extends LocalStorageBooleanProvider {
136
- constructor() {
137
- super('');
138
- this.data = {
139
- 'trueString': true,
140
- 'falseString': false,
141
- 'trueBoolean': true,
142
- 'falseBoolean': false,
143
- 'truthy': true,
144
- 'falsy': false
145
- };
146
- }
147
- })();
148
-
149
- expect(mockProvider.isEnabled('trueBoolean')).toBe(true);
150
- expect(mockProvider.isEnabled('falseBoolean')).toBe(false);
151
- });
152
- });
153
-
154
- describe('FeatureProvider Interface', () => {
155
- it('should define correct interface structure', () => {
156
- class TestProvider implements FeatureProvider<boolean> {
157
- data: Record<string, boolean> = {};
158
-
159
- getConfig(configPathOrUrl: string): void {
160
- this.data = { test: true };
161
- }
162
-
163
- isEnabled(key: string): boolean {
164
- return this.data[key] || false;
165
- }
166
- }
167
-
168
- const provider = new TestProvider();
169
- provider.getConfig('test-path');
170
-
171
- expect(provider.isEnabled('test')).toBe(true);
172
- expect(provider.isEnabled('missing')).toBe(false);
173
- });
174
-
175
- it('should support optional properties', () => {
176
- class TestProviderWithOptional implements FeatureProvider<Feature> {
177
- apiUrl = 'https://api.example.com';
178
- baseUUID = 'test-uuid';
179
- data: Record<string, Feature> = {};
180
-
181
- getConfig(configPathOrUrl: string): void {}
182
-
183
- getCollectionHash(configPathOrUrl: string): void {}
184
-
185
- isEnabled(key: string): boolean {
186
- return false;
187
- }
188
- }
189
-
190
- const provider = new TestProviderWithOptional();
191
- expect(provider.apiUrl).toBe('https://api.example.com');
192
- expect(provider.baseUUID).toBe('test-uuid');
193
- expect(provider.getCollectionHash).toBeDefined();
194
- });
195
- });
196
-
197
- describe('Provider Error Handling', () => {
198
- it('should handle file not found gracefully', () => {
199
- const provider = new LocalStorageFeatureProvider('./non-existent-file.json');
200
- expect(provider.data).toEqual({});
201
- expect(provider.isEnabled('anyKey')).toBe(false);
202
- });
203
-
204
- it('should handle invalid JSON gracefully', () => {
205
- // Mock require to throw error
206
- const originalConsoleError = console.error;
207
- console.error = jest.fn();
208
-
209
- try {
210
- const provider = new LocalStorageFeatureProvider('./invalid-json-file.json');
211
- expect(provider.data).toEqual({});
212
- expect(console.error).toHaveBeenCalled();
213
- } finally {
214
- console.error = originalConsoleError;
215
- }
216
- });
217
- });
218
- });
@@ -1 +0,0 @@
1
- { "myToggle": true }
@@ -1,20 +0,0 @@
1
- {
2
- "myClassToggle": {
3
- "key": "myClassToggle",
4
- "value": "false",
5
- "activeAt": "",
6
- "disabledAt": ""
7
- },
8
- "myMethodToggle": {
9
- "key": "myMethodToggle",
10
- "value": "true",
11
- "activeAt": "",
12
- "disabledAt": ""
13
- },
14
- "myOtherMethodToggle": {
15
- "key": "myOtherMethodToggle",
16
- "value": "false",
17
- "activeAt": "",
18
- "disabledAt": ""
19
- }
20
- }
@@ -1,2 +0,0 @@
1
- import 'reflect-metadata';
2
-
@@ -1,169 +0,0 @@
1
- import { Feature } from '../FeatureToggle';
2
- import { LocalStorageFeatureProvider } from '../examples/LocalStorageFeatureProvider';
3
-
4
- describe('Time-based Feature Evaluation Logic', () => {
5
- describe('Current Time Logic', () => {
6
- it('should handle feature with value=true and no dates (always enabled)', () => {
7
- const provider = new (class extends LocalStorageFeatureProvider {
8
- constructor() {
9
- super('');
10
- this.data = {
11
- 'always-enabled': {
12
- key: 'always-enabled',
13
- value: 'true',
14
- activeAt: '',
15
- disabledAt: ''
16
- }
17
- };
18
- }
19
- })();
20
-
21
- expect(provider.isEnabled('always-enabled')).toBe(true);
22
- });
23
-
24
- it('should handle feature with value=false (always disabled)', () => {
25
- const provider = new (class extends LocalStorageFeatureProvider {
26
- constructor() {
27
- super('');
28
- this.data = {
29
- 'always-disabled': {
30
- key: 'always-disabled',
31
- value: 'false',
32
- activeAt: '',
33
- disabledAt: ''
34
- }
35
- };
36
- }
37
- })();
38
-
39
- expect(provider.isEnabled('always-disabled')).toBe(false);
40
- });
41
-
42
- it('should disable feature if disabledAt is in the past', () => {
43
- const pastDate = new Date(Date.now() - 86400000).toISOString(); // 1 day ago
44
-
45
- const provider = new (class extends LocalStorageFeatureProvider {
46
- constructor() {
47
- super('');
48
- this.data = {
49
- 'past-disabled': {
50
- key: 'past-disabled',
51
- value: 'true',
52
- activeAt: '',
53
- disabledAt: pastDate
54
- }
55
- };
56
- }
57
- })();
58
-
59
- expect(provider.isEnabled('past-disabled')).toBe(false);
60
- });
61
-
62
- it('should enable feature if activeAt is in the past and no disabledAt', () => {
63
- const pastDate = new Date(Date.now() - 86400000).toISOString(); // 1 day ago
64
-
65
- const provider = new (class extends LocalStorageFeatureProvider {
66
- constructor() {
67
- super('');
68
- this.data = {
69
- 'past-active': {
70
- key: 'past-active',
71
- value: 'true',
72
- activeAt: pastDate,
73
- disabledAt: ''
74
- }
75
- };
76
- }
77
- })();
78
-
79
- expect(provider.isEnabled('past-active')).toBe(true);
80
- });
81
-
82
- it('BUG: should NOT enable feature if activeAt is in the future', () => {
83
- const futureDate = new Date(Date.now() + 86400000).toISOString(); // 1 day from now
84
-
85
- const provider = new (class extends LocalStorageFeatureProvider {
86
- constructor() {
87
- super('');
88
- this.data = {
89
- 'future-active': {
90
- key: 'future-active',
91
- value: 'true',
92
- activeAt: futureDate,
93
- disabledAt: ''
94
- }
95
- };
96
- }
97
- })();
98
-
99
- // CURRENT BUGGY BEHAVIOR: Returns true because it falls through to line 28
100
- // Date.now() >= Date.parse(futureDate) is false, so it skips line 26
101
- // Falls through to: return JSON.parse(feature.value); which returns true
102
-
103
- // EXPECTED BEHAVIOR: Should return false because feature is not yet active
104
- expect(provider.isEnabled('future-active')).toBe(false);
105
- });
106
-
107
- it('should enable feature if within activeAt and disabledAt window', () => {
108
- const pastDate = new Date(Date.now() - 86400000).toISOString(); // 1 day ago
109
- const futureDate = new Date(Date.now() + 86400000).toISOString(); // 1 day from now
110
-
111
- const provider = new (class extends LocalStorageFeatureProvider {
112
- constructor() {
113
- super('');
114
- this.data = {
115
- 'windowed-feature': {
116
- key: 'windowed-feature',
117
- value: 'true',
118
- activeAt: pastDate,
119
- disabledAt: futureDate
120
- }
121
- };
122
- }
123
- })();
124
-
125
- expect(provider.isEnabled('windowed-feature')).toBe(true);
126
- });
127
-
128
- it('should handle NaN dates gracefully (empty strings)', () => {
129
- const provider = new (class extends LocalStorageFeatureProvider {
130
- constructor() {
131
- super('');
132
- this.data = {
133
- 'empty-dates': {
134
- key: 'empty-dates',
135
- value: 'true',
136
- activeAt: '',
137
- disabledAt: ''
138
- }
139
- };
140
- }
141
- })();
142
-
143
- // Date.parse('') returns NaN
144
- // Date.now() >= NaN is false for both checks
145
- // Should fall through to: return JSON.parse(feature.value);
146
- expect(provider.isEnabled('empty-dates')).toBe(true);
147
- });
148
-
149
- it('should handle invalid date strings gracefully', () => {
150
- const provider = new (class extends LocalStorageFeatureProvider {
151
- constructor() {
152
- super('');
153
- this.data = {
154
- 'invalid-dates': {
155
- key: 'invalid-dates',
156
- value: 'true',
157
- activeAt: 'not-a-date',
158
- disabledAt: 'also-not-a-date'
159
- }
160
- };
161
- }
162
- })();
163
-
164
- // Date.parse('not-a-date') returns NaN
165
- // Similar behavior to empty strings
166
- expect(provider.isEnabled('invalid-dates')).toBe(true);
167
- });
168
- });
169
- });
@@ -1,53 +0,0 @@
1
- import { FeatureToggle, FeatureToggleBase } from "..";
2
- import { LocalStorageFeatureProvider } from "../examples/LocalStorageFeatureProvider";
3
-
4
- FeatureToggleBase.featureProvider = new LocalStorageFeatureProvider(
5
- "../test/test-feature.json"
6
- );
7
-
8
- class Demo3 {
9
- constructor() {}
10
-
11
- async five() {
12
- return Promise.resolve(9);
13
- }
14
- }
15
-
16
- @FeatureToggle("myClassToggle", Demo3)
17
- class Demo2 {
18
- constructor() {}
19
-
20
- async five() {
21
- return Promise.resolve(5);
22
- }
23
- }
24
-
25
- function myFallback() {
26
- console.log("hiyaa");
27
- }
28
-
29
- export class Demo {
30
- demo: Demo2;
31
- constructor() {
32
- this.demo = new Demo2();
33
- }
34
-
35
- @FeatureToggle("myMethodToggle")
36
- method() {
37
- console.log("Hello world!");
38
- this.demo.five().then((val) => console.log(val));
39
- }
40
-
41
- @FeatureToggle("myOtherMethodToggle", myFallback)
42
- othermethod() {
43
- console.log("Hello other world!");
44
- }
45
- }
46
-
47
- describe("YaFT Demo", () => {
48
- it("should create test", () => {
49
- const test = new Demo();
50
- test.method();
51
- test.othermethod();
52
- });
53
- });
package/tsconfig.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "commonjs",
4
- "target": "ES2022",
5
- "experimentalDecorators": true,
6
- "declaration": true,
7
- "rootDir": "./src",
8
- "outDir": "./dist/yaft",
9
- "types": ["node"],
10
- "lib": ["ES2022"]
11
- },
12
- "include": ["src/**/*.ts"],
13
- "exclude": ["**/*.spec.ts", "**/*.test.ts", "**/test/**/*"]
14
- }
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../dist/out-tsc",
5
- "module": "esnext",
6
- "types": ["jest", "node"]
7
- },
8
- "files": ["src/test/test-setup.ts"],
9
- "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"]
10
- }