@flipflag/sdk 1.1.1 → 1.1.3
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 +52 -96
- package/dist/index.min.js +1 -1
- package/dist/provider.js +1 -1
- package/dist/provider.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
# FlipFlag SDK
|
|
2
2
|
|
|
3
|
-
A lightweight client-side SDK for working with **FlipFlag
|
|
4
|
-
It allows you to:
|
|
3
|
+
A lightweight client-side SDK for working with **FlipFlag** (https://flipflag.dev).
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- Check whether a feature is enabled
|
|
9
|
-
- Periodically sync feature states
|
|
10
|
-
- Manage feature definitions (options + metadata)
|
|
5
|
+
The SDK is designed to be simple, declarative, and safe by default.
|
|
6
|
+
It supports read-only usage as well as full feature management when a private key is provided.
|
|
11
7
|
|
|
12
|
-
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- Load feature flags (`enabled`) using a **public key**
|
|
13
|
+
- Declare feature activation windows via `.flipflag.yml`
|
|
14
|
+
- Automatically create features on the server (requires `privateKey`)
|
|
15
|
+
- Periodically sync:
|
|
16
|
+
- feature flags
|
|
17
|
+
- feature timing declarations
|
|
18
|
+
- feature usage events
|
|
19
|
+
- Local in-memory cache for flags, declarations, and usage
|
|
20
|
+
- Full TypeScript support
|
|
13
21
|
|
|
14
22
|
---
|
|
15
23
|
|
|
@@ -23,133 +31,81 @@ yarn add @flipflag/sdk
|
|
|
23
31
|
|
|
24
32
|
---
|
|
25
33
|
|
|
26
|
-
##
|
|
27
|
-
|
|
28
|
-
### Initialize the manager
|
|
34
|
+
## Quick Start
|
|
29
35
|
|
|
30
36
|
```ts
|
|
31
|
-
import { FlipFlag } from
|
|
37
|
+
import { FlipFlag } from "@flipflag/sdk";
|
|
32
38
|
|
|
33
39
|
const manager = new FlipFlag({
|
|
34
|
-
publicKey:
|
|
35
|
-
privateKey:
|
|
40
|
+
publicKey: "YOUR_PUBLIC_KEY",
|
|
41
|
+
privateKey: "YOUR_PRIVATE_KEY", // optional (read-only mode without it)
|
|
36
42
|
});
|
|
37
43
|
|
|
38
44
|
await manager.init();
|
|
45
|
+
|
|
46
|
+
if (manager.isEnabled("newFeature")) {
|
|
47
|
+
console.log("Feature is enabled!");
|
|
48
|
+
}
|
|
39
49
|
```
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
---
|
|
42
52
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
## Configuration via `.flipflag.yml`
|
|
54
|
+
|
|
55
|
+
By default, the SDK looks for a `.flipflag.yml` file in the project root
|
|
56
|
+
(`process.cwd()`), which is loaded during `init()`.
|
|
57
|
+
|
|
58
|
+
### Example `.flipflag.yml`
|
|
59
|
+
|
|
60
|
+
```yml
|
|
61
|
+
newFeature:
|
|
62
|
+
contributor: epolevov@emd.one
|
|
63
|
+
times:
|
|
64
|
+
- started: "2025-12-01T00:00:00.000Z"
|
|
65
|
+
finished: "2025-12-31T23:59:59.000Z"
|
|
66
|
+
|
|
67
|
+
anotherFeature:
|
|
68
|
+
contributor: dev@company.com
|
|
69
|
+
times:
|
|
70
|
+
- started: "2026-01-01T00:00:00.000Z"
|
|
51
71
|
```
|
|
52
72
|
|
|
53
|
-
|
|
54
|
-
the SDK will automatically create it.
|
|
73
|
+
---
|
|
55
74
|
|
|
56
|
-
|
|
75
|
+
## Checking Feature State
|
|
57
76
|
|
|
58
77
|
```ts
|
|
59
|
-
|
|
60
|
-
console.log('Feature is enabled!');
|
|
61
|
-
}
|
|
78
|
+
manager.isEnabled("newFeature");
|
|
62
79
|
```
|
|
63
80
|
|
|
64
|
-
If the feature has not been declared yet, a placeholder is created automatically
|
|
65
|
-
and `false` is returned.
|
|
66
|
-
|
|
67
81
|
---
|
|
68
82
|
|
|
69
83
|
## Automatic Syncing
|
|
70
84
|
|
|
71
|
-
After calling `init()`, the
|
|
85
|
+
After calling `init()`, the SDK starts a **10-second polling loop**.
|
|
72
86
|
|
|
73
|
-
|
|
74
|
-
- Starts a **10-second interval** that refreshes feature states
|
|
75
|
-
- Keeps local cache in `declaredFeatures`
|
|
76
|
-
|
|
77
|
-
To stop syncing:
|
|
87
|
+
To stop syncing and clear all local state:
|
|
78
88
|
|
|
79
89
|
```ts
|
|
80
90
|
manager.destroy();
|
|
81
91
|
```
|
|
82
92
|
|
|
83
|
-
This clears the interval and resets the cache.
|
|
84
|
-
|
|
85
93
|
---
|
|
86
94
|
|
|
87
|
-
##
|
|
95
|
+
## Manager Options
|
|
88
96
|
|
|
89
97
|
```ts
|
|
90
98
|
export interface IManagerOptions {
|
|
91
99
|
apiUrl?: string;
|
|
92
100
|
publicKey: string;
|
|
93
101
|
privateKey?: string;
|
|
102
|
+
configPath?: string;
|
|
103
|
+
ignoreMissingConfig?: boolean;
|
|
94
104
|
}
|
|
95
|
-
|
|
96
|
-
export interface IDeclareFeatureTime {
|
|
97
|
-
start: Date;
|
|
98
|
-
end?: Date;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export interface IDeclareFeatureOptions {
|
|
102
|
-
times: IDeclareFeatureTime[];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export interface IFeatureFlag {
|
|
106
|
-
enabled: boolean;
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
## Running Tests
|
|
113
|
-
|
|
114
|
-
This SDK comes with full Jest test coverage, including:
|
|
115
|
-
|
|
116
|
-
- Mocked `fetch` requests
|
|
117
|
-
- Interval polling behavior
|
|
118
|
-
- Feature creation
|
|
119
|
-
- Error handling
|
|
120
|
-
- Local cache logic
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## Example: Full Integration
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
import { FlipFlag } from '@flipflag/sdk';
|
|
128
|
-
|
|
129
|
-
async function main() {
|
|
130
|
-
const manager = new FlipFlag({
|
|
131
|
-
publicKey: 'pk_live_123',
|
|
132
|
-
privateKey: 'sk_live_123',
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
await manager.init();
|
|
136
|
-
|
|
137
|
-
manager.declareFeature('demoFeature', {
|
|
138
|
-
times: [
|
|
139
|
-
{ start: new Date() },
|
|
140
|
-
],
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
if (manager.isEnabled('demoFeature')) {
|
|
144
|
-
console.log('Demo feature is active!');
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
main();
|
|
149
105
|
```
|
|
150
106
|
|
|
151
107
|
---
|
|
152
108
|
|
|
153
109
|
## License
|
|
154
110
|
|
|
155
|
-
MIT License
|
|
111
|
+
MIT License
|
package/dist/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as p from"node:fs/promises";import*as g from"node:path";import*as d from"js-yaml";class y{constructor(e){this.opts=e,this.inited=!1,this.interval=null,this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[],this.options={apiUrl:"https://api.flipflag.dev",...e},this.interval=null}async init(){await this.loadConfigFromYaml(),await this.getFeaturesFlags(),await this.syncFeaturesTimes(),this.interval=setInterval(()=>{this.getFeaturesFlags(),this.syncFeaturesTimes(),this.syncFeaturesUsage()},1e4),this.inited=!0}async loadConfigFromYaml(){var e,t,s,r;const n=(e=this.options.configPath)!=null?e:g.resolve(process.cwd(),".flipflag.yml");let l;try{l=await p.readFile(n,"utf8")}catch(i){if(i?.code==="ENOENT"&&this.options.ignoreMissingConfig)return;throw new Error(`FlipFlag: cannot read config at ${n}: ${(t=i?.message)!=null?t:i}`)}let o;try{o=d.load(l)}catch(i){throw new Error(`FlipFlag: invalid YAML in ${n}: ${(s=i?.message)!=null?s:i}`)}if(!o||typeof o!="object"||Array.isArray(o))
|
|
1
|
+
import*as p from"node:fs/promises";import*as g from"node:path";import*as d from"js-yaml";class y{constructor(e){this.opts=e,this.inited=!1,this.interval=null,this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[],this.options={apiUrl:"https://api.flipflag.dev",...e},this.interval=null}async init(){await this.loadConfigFromYaml(),await this.getFeaturesFlags(),await this.syncFeaturesTimes(),this.interval=setInterval(()=>{this.getFeaturesFlags(),this.syncFeaturesTimes(),this.syncFeaturesUsage()},1e4),this.inited=!0}async loadConfigFromYaml(){var e,t,s,r;const n=(e=this.options.configPath)!=null?e:g.resolve(process.cwd(),".flipflag.yml");let l;try{l=await p.readFile(n,"utf8")}catch(i){if(i?.code==="ENOENT"&&this.options.ignoreMissingConfig)return;throw new Error(`FlipFlag: cannot read config at ${n}: ${(t=i?.message)!=null?t:i}`)}let o;try{o=d.load(l)}catch(i){throw new Error(`FlipFlag: invalid YAML in ${n}: ${(s=i?.message)!=null?s:i}`)}if(!o||typeof o!="object"||Array.isArray(o)){console.warn("FlipFlag: YAML root must be an object (mapping featureName -> config)");return}const u=o;for(const[i,c]of Object.entries(u)){const h=((r=c?.times)!=null?r:[]).map(a=>{var f;return{email:u[i].contributor,start:a.started,end:(f=a.finished)!=null?f:null}});for(const a of h){if(Number.isNaN(Date.parse(a.start)))throw new Error(`FlipFlag: invalid "started" date in ${i}: ${a.start}`);if(a.end!==null&&Number.isNaN(Date.parse(String(a.end))))throw new Error(`FlipFlag: invalid "finished" date in ${i}: ${a.end}`)}this.featuresTimes[i]={times:h}}}destroy(){this.inited=!1,this.interval&&clearInterval(this.interval),this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[]}isEnabled(e){const t=this.getLocalFeatureFlag(e);return t?(this.upsertFeaturesUsage(e),t.enabled):(this.createFeature(e,{times:[]}),!1)}upsertFeaturesUsage(e){const t=this.featuresUsage.find(s=>s.featureName===e);if(t){t.usedAt=new Date;return}this.featuresUsage.push({featureName:e,usedAt:new Date})}async createFeature(e,t){if(!this.options.privateKey)return null;const s=this.getBaseUrl(),r=new URL("/v1/sdk/feature",s);fetch(r.toString(),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({featureName:e,privateKey:this.options.privateKey,...t})}).catch(n=>{console.error("Create Feature:",n)})}getBaseUrl(){if(this.options.apiUrl)return this.options.apiUrl.replace(/\/+$/,"");throw new Error("Base API URL is not configured. Please provide apiUrl in the SDK options.")}async getFeaturesFlags(){if(!this.options.publicKey)throw new Error("Public key is missing. Please provide a valid publicKey in the SDK configuration.");try{const e=this.getBaseUrl(),t=new URL("/v1/sdk/feature/flags",e);t.searchParams.append("publicKey",this.options.publicKey);const s=await fetch(t.toString(),{method:"GET",headers:{"Content-Type":"application/json"}});if(!s.ok&&!this.inited){const r=await s.text();throw new Error(`Failed to get features: ${s.status} - ${r}`)}this.featuresFlags=await s.json()}catch(e){console.error("Get list features flag:",e)}}async syncFeaturesTimes(){if(!this.options.privateKey)return null;Object.entries(this.featuresTimes).forEach(([t,s])=>{this.createFeature(t,s)})}async syncFeaturesUsage(){if(!this.options.publicKey)throw new Error("Public key is missing. Please provide a valid publicKey in the SDK configuration.");const e=this.getBaseUrl(),t=new URL("/v1/sdk/feature/usages",e);fetch(t.toString(),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({publicKey:this.options.publicKey,usages:this.featuresUsage})}).catch(s=>{console.error("Feature Usage Sync:",s)})}getLocalFeatureFlag(e){return this.featuresFlags[e]}}export{y as FlipFlag};
|
package/dist/provider.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import*as p from"node:fs/promises";import*as g from"node:path";import*as d from"js-yaml";class F{constructor(e){this.opts=e,this.inited=!1,this.interval=null,this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[],this.options={apiUrl:"https://api.flipflag.dev",...e},this.interval=null}async init(){await this.loadConfigFromYaml(),await this.getFeaturesFlags(),await this.syncFeaturesTimes(),this.interval=setInterval(()=>{this.getFeaturesFlags(),this.syncFeaturesTimes(),this.syncFeaturesUsage()},1e4),this.inited=!0}async loadConfigFromYaml(){var e,t,i,r;const n=(e=this.options.configPath)!=null?e:g.resolve(process.cwd(),".flipflag.yml");let l;try{l=await p.readFile(n,"utf8")}catch(s){if((s==null?void 0:s.code)==="ENOENT"&&this.options.ignoreMissingConfig)return;throw new Error(`FlipFlag: cannot read config at ${n}: ${(t=s==null?void 0:s.message)!=null?t:s}`)}let o;try{o=d.load(l)}catch(s){throw new Error(`FlipFlag: invalid YAML in ${n}: ${(i=s==null?void 0:s.message)!=null?i:s}`)}if(!o||typeof o!="object"||Array.isArray(o))
|
|
1
|
+
import*as p from"node:fs/promises";import*as g from"node:path";import*as d from"js-yaml";class F{constructor(e){this.opts=e,this.inited=!1,this.interval=null,this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[],this.options={apiUrl:"https://api.flipflag.dev",...e},this.interval=null}async init(){await this.loadConfigFromYaml(),await this.getFeaturesFlags(),await this.syncFeaturesTimes(),this.interval=setInterval(()=>{this.getFeaturesFlags(),this.syncFeaturesTimes(),this.syncFeaturesUsage()},1e4),this.inited=!0}async loadConfigFromYaml(){var e,t,i,r;const n=(e=this.options.configPath)!=null?e:g.resolve(process.cwd(),".flipflag.yml");let l;try{l=await p.readFile(n,"utf8")}catch(s){if((s==null?void 0:s.code)==="ENOENT"&&this.options.ignoreMissingConfig)return;throw new Error(`FlipFlag: cannot read config at ${n}: ${(t=s==null?void 0:s.message)!=null?t:s}`)}let o;try{o=d.load(l)}catch(s){throw new Error(`FlipFlag: invalid YAML in ${n}: ${(i=s==null?void 0:s.message)!=null?i:s}`)}if(!o||typeof o!="object"||Array.isArray(o)){console.warn("FlipFlag: YAML root must be an object (mapping featureName -> config)");return}const u=o;for(const[s,c]of Object.entries(u)){const h=((r=c==null?void 0:c.times)!=null?r:[]).map(a=>{var f;return{email:u[s].contributor,start:a.started,end:(f=a.finished)!=null?f:null}});for(const a of h){if(Number.isNaN(Date.parse(a.start)))throw new Error(`FlipFlag: invalid "started" date in ${s}: ${a.start}`);if(a.end!==null&&Number.isNaN(Date.parse(String(a.end))))throw new Error(`FlipFlag: invalid "finished" date in ${s}: ${a.end}`)}this.featuresTimes[s]={times:h}}}destroy(){this.inited=!1,this.interval&&clearInterval(this.interval),this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[]}isEnabled(e){const t=this.getLocalFeatureFlag(e);return t?(this.upsertFeaturesUsage(e),t.enabled):(this.createFeature(e,{times:[]}),!1)}upsertFeaturesUsage(e){const t=this.featuresUsage.find(i=>i.featureName===e);if(t){t.usedAt=new Date;return}this.featuresUsage.push({featureName:e,usedAt:new Date})}async createFeature(e,t){if(!this.options.privateKey)return null;const i=this.getBaseUrl(),r=new URL("/v1/sdk/feature",i);fetch(r.toString(),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({featureName:e,privateKey:this.options.privateKey,...t})}).catch(n=>{console.error("Create Feature:",n)})}getBaseUrl(){if(this.options.apiUrl)return this.options.apiUrl.replace(/\/+$/,"");throw new Error("Base API URL is not configured. Please provide apiUrl in the SDK options.")}async getFeaturesFlags(){if(!this.options.publicKey)throw new Error("Public key is missing. Please provide a valid publicKey in the SDK configuration.");try{const e=this.getBaseUrl(),t=new URL("/v1/sdk/feature/flags",e);t.searchParams.append("publicKey",this.options.publicKey);const i=await fetch(t.toString(),{method:"GET",headers:{"Content-Type":"application/json"}});if(!i.ok&&!this.inited){const r=await i.text();throw new Error(`Failed to get features: ${i.status} - ${r}`)}this.featuresFlags=await i.json()}catch(e){console.error("Get list features flag:",e)}}async syncFeaturesTimes(){if(!this.options.privateKey)return null;Object.entries(this.featuresTimes).forEach(([e,t])=>{this.createFeature(e,t)})}async syncFeaturesUsage(){if(!this.options.publicKey)throw new Error("Public key is missing. Please provide a valid publicKey in the SDK configuration.");const e=this.getBaseUrl(),t=new URL("/v1/sdk/feature/usages",e);fetch(t.toString(),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({publicKey:this.options.publicKey,usages:this.featuresUsage})}).catch(i=>{console.error("Feature Usage Sync:",i)})}getLocalFeatureFlag(e){return this.featuresFlags[e]}}export{F as FlipFlag};
|
|
2
2
|
//# sourceMappingURL=provider.js.map
|
package/dist/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sources":["../src/provider.ts"],"sourcesContent":["import {\n FlipFlagYaml,\n IDeclareFeatureOptions,\n IDeclareFeatureTime,\n IFeatureFlag,\n IFeatureFlagUsage,\n IManagerOptions,\n} from \"./types/provider\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport * as yaml from \"js-yaml\";\n\n/**\n * Manager for interacting with FlipFlag.\n * Handles feature declaration, loading remote states, and periodic syncing.\n */\nexport class FlipFlag {\n /** Indicates whether initialization has completed */\n private inited = false;\n /** Interval handler for periodic feature refreshing */\n private interval: NodeJS.Timeout | null = null;\n /** Options passed to the manager (merged with defaults) */\n private options: Partial<IManagerOptions>;\n /**\n * Local cache of declared features times.\n * Keyed by feature name.\n */\n private featuresTimes: Record<string, IDeclareFeatureOptions> = {};\n /**\n * Local cache of declared features flags and their metadata.\n * Keyed by feature name.\n */\n private featuresFlags: Record<string, IFeatureFlag> = {};\n /**\n * Local cache of feature-flag usage events.\n * Each entry represents an interaction with a specific feature flag\n * (read, update, check) along with related metadata.\n */\n private featuresUsage: IFeatureFlagUsage[] = [];\n\n /**\n * @param opts Manager configuration (publicKey, privateKey, apiUrl, etc.)\n */\n constructor(protected readonly opts: IManagerOptions) {\n this.options = { apiUrl: \"https://api.flipflag.dev\", ...opts };\n this.interval = null;\n }\n\n /**\n * Initializes the manager:\n * - Loads the initial feature flags from the server\n * - Starts a 10-second polling loop to:\n * • refresh feature flags\n * • synchronize feature activation times\n */\n public async init() {\n await this.loadConfigFromYaml();\n await this.getFeaturesFlags();\n await this.syncFeaturesTimes();\n\n this.interval = setInterval(() => {\n this.getFeaturesFlags();\n this.syncFeaturesTimes();\n this.syncFeaturesUsage();\n }, 10_000);\n\n this.inited = true;\n }\n\n private async loadConfigFromYaml() {\n const configPath =\n this.options.configPath ?? path.resolve(process.cwd(), \".flipflag.yml\");\n\n let raw: string;\n try {\n raw = await fs.readFile(configPath, \"utf8\");\n } catch (e: any) {\n if (e?.code === \"ENOENT\" && this.options.ignoreMissingConfig) return;\n throw new Error(\n `FlipFlag: cannot read config at ${configPath}: ${e?.message ?? e}`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = yaml.load(raw);\n } catch (e: any) {\n throw new Error(\n `FlipFlag: invalid YAML in ${configPath}: ${e?.message ?? e}`,\n );\n }\n\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\n `FlipFlag: YAML root must be an object (mapping featureName -> config)`,\n );\n }\n\n const doc = parsed as FlipFlagYaml;\n\n for (const [featureName, cfg] of Object.entries(doc)) {\n const times = (cfg?.times ?? []).map((t) => ({\n email: doc[featureName].contributor,\n start: t.started,\n end: t.finished ?? null,\n })) as IDeclareFeatureTime[];\n\n for (const t of times) {\n if (Number.isNaN(Date.parse(t.start))) {\n throw new Error(\n `FlipFlag: invalid \"started\" date in ${featureName}: ${t.start}`,\n );\n }\n if (t.end !== null && Number.isNaN(Date.parse(String(t.end)))) {\n throw new Error(\n `FlipFlag: invalid \"finished\" date in ${featureName}: ${t.end}`,\n );\n }\n }\n\n this.featuresTimes[featureName] = { times };\n }\n }\n\n /**\n * Destroys the manager:\n * - Stops the periodic sync\n * - Clears all locally declared features\n */\n public destroy() {\n this.inited = false;\n if (this.interval) clearInterval(this.interval);\n this.featuresTimes = {};\n this.featuresFlags = {};\n this.featuresUsage = [];\n }\n\n /**\n * Checks whether a feature is enabled.\n * If the feature does not exist locally, it will be created with empty options.\n *\n * @param featureName Name of the feature\n * @returns `true` if enabled, otherwise `false`\n */\n isEnabled(featureName: string) {\n const feature = this.getLocalFeatureFlag(featureName);\n\n if (!feature) {\n this.createFeature(featureName, { times: [] });\n\n return false;\n }\n\n this.upsertFeaturesUsage(featureName);\n\n return feature.enabled;\n }\n\n /**\n * Updates or inserts a feature usage record.\n * If the feature was already used before, its timestamp is updated.\n * Otherwise, a new usage entry is created.\n *\n * @param featureName - Name of the feature that was checked/used\n */\n private upsertFeaturesUsage(featureName: string) {\n const existing = this.featuresUsage.find(\n (u) => u.featureName === featureName,\n );\n\n if (existing) {\n existing.usedAt = new Date();\n return;\n }\n\n this.featuresUsage.push({\n featureName,\n usedAt: new Date(),\n });\n }\n\n /**\n * Creates a new feature on the server.\n * Requires a privateKey; otherwise the request is ignored.\n *\n * @param featureName Name of the feature\n * @param options Feature declaration options\n * @returns Created feature data or null if privateKey is missing\n */\n private async createFeature(\n featureName: string,\n options: IDeclareFeatureOptions,\n ) {\n if (!this.options.privateKey) {\n return null;\n }\n\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature\", baseUrl);\n\n fetch(url.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n featureName,\n privateKey: this.options.privateKey,\n ...options,\n }),\n }).catch((error) => {\n console.error(\"Create Feature:\", error);\n });\n }\n\n private getBaseUrl() {\n if (this.options.apiUrl) {\n return this.options.apiUrl.replace(/\\/+$/, \"\");\n }\n\n throw new Error(\n \"Base API URL is not configured. Please provide apiUrl in the SDK options.\",\n );\n }\n\n /**\n * Fetches all features associated with the publicKey.\n * Populates the local `featuresTimes` cache.\n *\n * Throws an error only during initial initialization;\n * later polling failures are ignored.\n */\n private async getFeaturesFlags() {\n if (!this.options.publicKey) {\n throw new Error(\n \"Public key is missing. Please provide a valid publicKey in the SDK configuration.\",\n );\n }\n\n try {\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature/flags\", baseUrl);\n url.searchParams.append(\"publicKey\", this.options.publicKey);\n\n const response = await fetch(url.toString(), {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok && !this.inited) {\n const errorText = await response.text();\n throw new Error(\n `Failed to get features: ${response.status} - ${errorText}`,\n );\n }\n\n this.featuresFlags = await response.json();\n } catch (error) {\n console.error(\"Get list features flag:\", error);\n }\n }\n\n /**\n * Fetches all features associated with the publicKey.\n * Populates the local `featuresTimes` cache.\n *\n * Throws an error only during initial initialization;\n * later polling failures are ignored.\n */\n private async syncFeaturesTimes() {\n if (!this.options.privateKey) {\n return null;\n }\n\n const list = Object.entries(this.featuresTimes);\n list.forEach(([featureName, options]: [string, IDeclareFeatureOptions]) => {\n this.createFeature(featureName, options);\n });\n }\n\n /**\n * Sends the collected feature usage data to the server.\n * Requires a valid `publicKey` provided in the SDK configuration.\n *\n * Throws an error if the `publicKey` is missing;\n * network or server errors during sync are not awaited or propagated.\n */\n private async syncFeaturesUsage() {\n if (!this.options.publicKey) {\n throw new Error(\n \"Public key is missing. Please provide a valid publicKey in the SDK configuration.\",\n );\n }\n\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature/usages\", baseUrl);\n\n fetch(url.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n publicKey: this.options.publicKey,\n usages: this.featuresUsage,\n }),\n }).catch((error) => {\n console.error(\"Feature Usage Sync:\", error);\n });\n }\n\n /**\n * Returns a locally cached feature flag by name.\n *\n * @param featureName Name of the feature\n * @returns The feature or undefined if it does not exist\n */\n private getLocalFeatureFlag(featureName: string) {\n return this.featuresFlags[featureName];\n }\n}\n"],"names":["FlipFlag","opts","_a","_b","_c","_d","configPath","path","raw","fs","e","parsed","yaml","doc","featureName","cfg","times","t","feature","existing","u","options","baseUrl","url","error","response","errorText"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"provider.js","sources":["../src/provider.ts"],"sourcesContent":["import {\n FlipFlagYaml,\n IDeclareFeatureOptions,\n IDeclareFeatureTime,\n IFeatureFlag,\n IFeatureFlagUsage,\n IManagerOptions,\n} from \"./types/provider\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport * as yaml from \"js-yaml\";\n\n/**\n * Manager for interacting with FlipFlag.\n * Handles feature declaration, loading remote states, and periodic syncing.\n */\nexport class FlipFlag {\n /** Indicates whether initialization has completed */\n private inited = false;\n /** Interval handler for periodic feature refreshing */\n private interval: NodeJS.Timeout | null = null;\n /** Options passed to the manager (merged with defaults) */\n private options: Partial<IManagerOptions>;\n /**\n * Local cache of declared features times.\n * Keyed by feature name.\n */\n private featuresTimes: Record<string, IDeclareFeatureOptions> = {};\n /**\n * Local cache of declared features flags and their metadata.\n * Keyed by feature name.\n */\n private featuresFlags: Record<string, IFeatureFlag> = {};\n /**\n * Local cache of feature-flag usage events.\n * Each entry represents an interaction with a specific feature flag\n * (read, update, check) along with related metadata.\n */\n private featuresUsage: IFeatureFlagUsage[] = [];\n\n /**\n * @param opts Manager configuration (publicKey, privateKey, apiUrl, etc.)\n */\n constructor(protected readonly opts: IManagerOptions) {\n this.options = { apiUrl: \"https://api.flipflag.dev\", ...opts };\n this.interval = null;\n }\n\n /**\n * Initializes the manager:\n * - Loads the initial feature flags from the server\n * - Starts a 10-second polling loop to:\n * • refresh feature flags\n * • synchronize feature activation times\n */\n public async init() {\n await this.loadConfigFromYaml();\n await this.getFeaturesFlags();\n await this.syncFeaturesTimes();\n\n this.interval = setInterval(() => {\n this.getFeaturesFlags();\n this.syncFeaturesTimes();\n this.syncFeaturesUsage();\n }, 10_000);\n\n this.inited = true;\n }\n\n private async loadConfigFromYaml() {\n const configPath =\n this.options.configPath ?? path.resolve(process.cwd(), \".flipflag.yml\");\n\n let raw: string;\n try {\n raw = await fs.readFile(configPath, \"utf8\");\n } catch (e: any) {\n if (e?.code === \"ENOENT\" && this.options.ignoreMissingConfig) return;\n throw new Error(\n `FlipFlag: cannot read config at ${configPath}: ${e?.message ?? e}`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = yaml.load(raw);\n } catch (e: any) {\n throw new Error(\n `FlipFlag: invalid YAML in ${configPath}: ${e?.message ?? e}`,\n );\n }\n\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n console.warn(\n \"FlipFlag: YAML root must be an object (mapping featureName -> config)\",\n );\n return;\n }\n\n const doc = parsed as FlipFlagYaml;\n\n for (const [featureName, cfg] of Object.entries(doc)) {\n const times = (cfg?.times ?? []).map((t) => ({\n email: doc[featureName].contributor,\n start: t.started,\n end: t.finished ?? null,\n })) as IDeclareFeatureTime[];\n\n for (const t of times) {\n if (Number.isNaN(Date.parse(t.start))) {\n throw new Error(\n `FlipFlag: invalid \"started\" date in ${featureName}: ${t.start}`,\n );\n }\n if (t.end !== null && Number.isNaN(Date.parse(String(t.end)))) {\n throw new Error(\n `FlipFlag: invalid \"finished\" date in ${featureName}: ${t.end}`,\n );\n }\n }\n\n this.featuresTimes[featureName] = { times };\n }\n }\n\n /**\n * Destroys the manager:\n * - Stops the periodic sync\n * - Clears all locally declared features\n */\n public destroy() {\n this.inited = false;\n if (this.interval) clearInterval(this.interval);\n this.featuresTimes = {};\n this.featuresFlags = {};\n this.featuresUsage = [];\n }\n\n /**\n * Checks whether a feature is enabled.\n * If the feature does not exist locally, it will be created with empty options.\n *\n * @param featureName Name of the feature\n * @returns `true` if enabled, otherwise `false`\n */\n isEnabled(featureName: string) {\n const feature = this.getLocalFeatureFlag(featureName);\n\n if (!feature) {\n this.createFeature(featureName, { times: [] });\n\n return false;\n }\n\n this.upsertFeaturesUsage(featureName);\n\n return feature.enabled;\n }\n\n /**\n * Updates or inserts a feature usage record.\n * If the feature was already used before, its timestamp is updated.\n * Otherwise, a new usage entry is created.\n *\n * @param featureName - Name of the feature that was checked/used\n */\n private upsertFeaturesUsage(featureName: string) {\n const existing = this.featuresUsage.find(\n (u) => u.featureName === featureName,\n );\n\n if (existing) {\n existing.usedAt = new Date();\n return;\n }\n\n this.featuresUsage.push({\n featureName,\n usedAt: new Date(),\n });\n }\n\n /**\n * Creates a new feature on the server.\n * Requires a privateKey; otherwise the request is ignored.\n *\n * @param featureName Name of the feature\n * @param options Feature declaration options\n * @returns Created feature data or null if privateKey is missing\n */\n private async createFeature(\n featureName: string,\n options: IDeclareFeatureOptions,\n ) {\n if (!this.options.privateKey) {\n return null;\n }\n\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature\", baseUrl);\n\n fetch(url.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n featureName,\n privateKey: this.options.privateKey,\n ...options,\n }),\n }).catch((error) => {\n console.error(\"Create Feature:\", error);\n });\n }\n\n private getBaseUrl() {\n if (this.options.apiUrl) {\n return this.options.apiUrl.replace(/\\/+$/, \"\");\n }\n\n throw new Error(\n \"Base API URL is not configured. Please provide apiUrl in the SDK options.\",\n );\n }\n\n /**\n * Fetches all features associated with the publicKey.\n * Populates the local `featuresTimes` cache.\n *\n * Throws an error only during initial initialization;\n * later polling failures are ignored.\n */\n private async getFeaturesFlags() {\n if (!this.options.publicKey) {\n throw new Error(\n \"Public key is missing. Please provide a valid publicKey in the SDK configuration.\",\n );\n }\n\n try {\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature/flags\", baseUrl);\n url.searchParams.append(\"publicKey\", this.options.publicKey);\n\n const response = await fetch(url.toString(), {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok && !this.inited) {\n const errorText = await response.text();\n throw new Error(\n `Failed to get features: ${response.status} - ${errorText}`,\n );\n }\n\n this.featuresFlags = await response.json();\n } catch (error) {\n console.error(\"Get list features flag:\", error);\n }\n }\n\n /**\n * Fetches all features associated with the publicKey.\n * Populates the local `featuresTimes` cache.\n *\n * Throws an error only during initial initialization;\n * later polling failures are ignored.\n */\n private async syncFeaturesTimes() {\n if (!this.options.privateKey) {\n return null;\n }\n\n const list = Object.entries(this.featuresTimes);\n list.forEach(([featureName, options]: [string, IDeclareFeatureOptions]) => {\n this.createFeature(featureName, options);\n });\n }\n\n /**\n * Sends the collected feature usage data to the server.\n * Requires a valid `publicKey` provided in the SDK configuration.\n *\n * Throws an error if the `publicKey` is missing;\n * network or server errors during sync are not awaited or propagated.\n */\n private async syncFeaturesUsage() {\n if (!this.options.publicKey) {\n throw new Error(\n \"Public key is missing. Please provide a valid publicKey in the SDK configuration.\",\n );\n }\n\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature/usages\", baseUrl);\n\n fetch(url.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n publicKey: this.options.publicKey,\n usages: this.featuresUsage,\n }),\n }).catch((error) => {\n console.error(\"Feature Usage Sync:\", error);\n });\n }\n\n /**\n * Returns a locally cached feature flag by name.\n *\n * @param featureName Name of the feature\n * @returns The feature or undefined if it does not exist\n */\n private getLocalFeatureFlag(featureName: string) {\n return this.featuresFlags[featureName];\n }\n}\n"],"names":["FlipFlag","opts","_a","_b","_c","_d","configPath","path","raw","fs","e","parsed","yaml","doc","featureName","cfg","times","t","feature","existing","u","options","baseUrl","url","error","response","errorText"],"mappings":"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"}
|