@flaggly/sdk 0.0.1
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/dist/index.d.mts +104 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +285 -0
- package/dist/index.mjs +258 -0
- package/package.json +27 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { MapStore } from 'nanostores';
|
|
2
|
+
|
|
3
|
+
type AllKeys<T> = T extends any ? keyof T : never;
|
|
4
|
+
type BooleanFlag = {
|
|
5
|
+
type: "boolean";
|
|
6
|
+
};
|
|
7
|
+
type VariantFlag<T = string> = {
|
|
8
|
+
type: "variant";
|
|
9
|
+
result: T;
|
|
10
|
+
};
|
|
11
|
+
type PayloadFlag<T = unknown> = {
|
|
12
|
+
type: "payload";
|
|
13
|
+
result: T;
|
|
14
|
+
};
|
|
15
|
+
type FlagConfig = BooleanFlag | VariantFlag | PayloadFlag;
|
|
16
|
+
type FlagSchema = Record<string, FlagConfig>;
|
|
17
|
+
type FlagInput = {
|
|
18
|
+
user?: unknown;
|
|
19
|
+
id?: string;
|
|
20
|
+
page?: {
|
|
21
|
+
url: string | null;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
type FlagValue<FR extends FlagConfig = FlagConfig> = FR extends {
|
|
25
|
+
type: "variant" | "payload";
|
|
26
|
+
} ? FR["result"] : boolean;
|
|
27
|
+
type FlagValues<FD extends FlagSchema = FlagSchema> = {
|
|
28
|
+
[K in keyof FD]: FlagValue<FD[K]>;
|
|
29
|
+
};
|
|
30
|
+
type EvaluatedFlags<FD extends FlagSchema = FlagSchema> = {
|
|
31
|
+
[K in keyof FD]: {
|
|
32
|
+
type: FD[K]["type"];
|
|
33
|
+
result: FlagValue<FD[K]>;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
type FlagglyOptions<TFlags extends FlagSchema = FlagSchema> = {
|
|
37
|
+
/**
|
|
38
|
+
* The base URL of your Flaggly worker.
|
|
39
|
+
*/
|
|
40
|
+
url: string;
|
|
41
|
+
/**
|
|
42
|
+
* The public `API_KEY` you set while installing the worker.
|
|
43
|
+
*/
|
|
44
|
+
apiKey: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional app for this instance.
|
|
47
|
+
* @default "default"
|
|
48
|
+
*/
|
|
49
|
+
app?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Optional enviornment for this instance
|
|
52
|
+
* @default "production"
|
|
53
|
+
*/
|
|
54
|
+
env?: string;
|
|
55
|
+
/**
|
|
56
|
+
* By default, flags are evaluated when you create the FlagglyClient instance.
|
|
57
|
+
* Pass this as true to manually initiate the flag evaluations.
|
|
58
|
+
* Useful if you just care about feature flags for authenticated users only,
|
|
59
|
+
* and want to evaluate flags when the users log in.
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
lazy?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Partial default values for the feature flags.
|
|
65
|
+
*/
|
|
66
|
+
bootstrap?: Partial<FlagValues<TFlags>>;
|
|
67
|
+
/**
|
|
68
|
+
* Optional method to generate a backup identifer for the flags,
|
|
69
|
+
* for anonymous users when you don't have a stable ID.
|
|
70
|
+
* By default, it generate and store a value in local host per app/env.
|
|
71
|
+
* Use this method to pass in your own ID for anonymous users.
|
|
72
|
+
*/
|
|
73
|
+
getBackupId?: () => string;
|
|
74
|
+
};
|
|
75
|
+
declare class Flaggly<TFlags extends FlagSchema = FlagSchema> {
|
|
76
|
+
#private;
|
|
77
|
+
private url;
|
|
78
|
+
private apiKey;
|
|
79
|
+
private app;
|
|
80
|
+
private env;
|
|
81
|
+
user?: unknown;
|
|
82
|
+
id?: string;
|
|
83
|
+
getBackupId?: () => string;
|
|
84
|
+
constructor({ url, apiKey, app, env, lazy, bootstrap, getBackupId, }: FlagglyOptions<TFlags>);
|
|
85
|
+
identify(id: string, user: unknown): Promise<EvaluatedFlags<TFlags>>;
|
|
86
|
+
getPageUrl(): string | null;
|
|
87
|
+
fetchFlags(input?: FlagInput): Promise<EvaluatedFlags<TFlags>>;
|
|
88
|
+
fetchFlag<K extends AllKeys<EvaluatedFlags<TFlags>>>(key: K, input?: FlagInput): Promise<EvaluatedFlags<TFlags>[K]>;
|
|
89
|
+
getFlags(): EvaluatedFlags<TFlags>;
|
|
90
|
+
getFlag<K extends keyof TFlags>(key: K): FlagValue<TFlags[K]>;
|
|
91
|
+
getBooleanFlag<K extends keyof TFlags>(key: K & (TFlags[K] extends {
|
|
92
|
+
type: "boolean";
|
|
93
|
+
} ? K : never)): FlagValue<TFlags[K]> | false;
|
|
94
|
+
getVariantFlag<K extends keyof TFlags>(key: K & (TFlags[K] extends {
|
|
95
|
+
type: "variant";
|
|
96
|
+
} ? K : never)): FlagValue<TFlags[K]> | null;
|
|
97
|
+
getPayloadFlag<K extends keyof TFlags>(key: K & (TFlags[K] extends {
|
|
98
|
+
type: "payload";
|
|
99
|
+
} ? K : never)): FlagValue<TFlags[K]> | null;
|
|
100
|
+
onChange(cb: (flags: EvaluatedFlags<TFlags>) => void): () => void;
|
|
101
|
+
get store(): MapStore<EvaluatedFlags<TFlags>>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { type EvaluatedFlags, type FlagConfig, type FlagInput, type FlagSchema, type FlagValue, type FlagValues, Flaggly, type FlagglyOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { MapStore } from 'nanostores';
|
|
2
|
+
|
|
3
|
+
type AllKeys<T> = T extends any ? keyof T : never;
|
|
4
|
+
type BooleanFlag = {
|
|
5
|
+
type: "boolean";
|
|
6
|
+
};
|
|
7
|
+
type VariantFlag<T = string> = {
|
|
8
|
+
type: "variant";
|
|
9
|
+
result: T;
|
|
10
|
+
};
|
|
11
|
+
type PayloadFlag<T = unknown> = {
|
|
12
|
+
type: "payload";
|
|
13
|
+
result: T;
|
|
14
|
+
};
|
|
15
|
+
type FlagConfig = BooleanFlag | VariantFlag | PayloadFlag;
|
|
16
|
+
type FlagSchema = Record<string, FlagConfig>;
|
|
17
|
+
type FlagInput = {
|
|
18
|
+
user?: unknown;
|
|
19
|
+
id?: string;
|
|
20
|
+
page?: {
|
|
21
|
+
url: string | null;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
type FlagValue<FR extends FlagConfig = FlagConfig> = FR extends {
|
|
25
|
+
type: "variant" | "payload";
|
|
26
|
+
} ? FR["result"] : boolean;
|
|
27
|
+
type FlagValues<FD extends FlagSchema = FlagSchema> = {
|
|
28
|
+
[K in keyof FD]: FlagValue<FD[K]>;
|
|
29
|
+
};
|
|
30
|
+
type EvaluatedFlags<FD extends FlagSchema = FlagSchema> = {
|
|
31
|
+
[K in keyof FD]: {
|
|
32
|
+
type: FD[K]["type"];
|
|
33
|
+
result: FlagValue<FD[K]>;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
type FlagglyOptions<TFlags extends FlagSchema = FlagSchema> = {
|
|
37
|
+
/**
|
|
38
|
+
* The base URL of your Flaggly worker.
|
|
39
|
+
*/
|
|
40
|
+
url: string;
|
|
41
|
+
/**
|
|
42
|
+
* The public `API_KEY` you set while installing the worker.
|
|
43
|
+
*/
|
|
44
|
+
apiKey: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional app for this instance.
|
|
47
|
+
* @default "default"
|
|
48
|
+
*/
|
|
49
|
+
app?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Optional enviornment for this instance
|
|
52
|
+
* @default "production"
|
|
53
|
+
*/
|
|
54
|
+
env?: string;
|
|
55
|
+
/**
|
|
56
|
+
* By default, flags are evaluated when you create the FlagglyClient instance.
|
|
57
|
+
* Pass this as true to manually initiate the flag evaluations.
|
|
58
|
+
* Useful if you just care about feature flags for authenticated users only,
|
|
59
|
+
* and want to evaluate flags when the users log in.
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
lazy?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Partial default values for the feature flags.
|
|
65
|
+
*/
|
|
66
|
+
bootstrap?: Partial<FlagValues<TFlags>>;
|
|
67
|
+
/**
|
|
68
|
+
* Optional method to generate a backup identifer for the flags,
|
|
69
|
+
* for anonymous users when you don't have a stable ID.
|
|
70
|
+
* By default, it generate and store a value in local host per app/env.
|
|
71
|
+
* Use this method to pass in your own ID for anonymous users.
|
|
72
|
+
*/
|
|
73
|
+
getBackupId?: () => string;
|
|
74
|
+
};
|
|
75
|
+
declare class Flaggly<TFlags extends FlagSchema = FlagSchema> {
|
|
76
|
+
#private;
|
|
77
|
+
private url;
|
|
78
|
+
private apiKey;
|
|
79
|
+
private app;
|
|
80
|
+
private env;
|
|
81
|
+
user?: unknown;
|
|
82
|
+
id?: string;
|
|
83
|
+
getBackupId?: () => string;
|
|
84
|
+
constructor({ url, apiKey, app, env, lazy, bootstrap, getBackupId, }: FlagglyOptions<TFlags>);
|
|
85
|
+
identify(id: string, user: unknown): Promise<EvaluatedFlags<TFlags>>;
|
|
86
|
+
getPageUrl(): string | null;
|
|
87
|
+
fetchFlags(input?: FlagInput): Promise<EvaluatedFlags<TFlags>>;
|
|
88
|
+
fetchFlag<K extends AllKeys<EvaluatedFlags<TFlags>>>(key: K, input?: FlagInput): Promise<EvaluatedFlags<TFlags>[K]>;
|
|
89
|
+
getFlags(): EvaluatedFlags<TFlags>;
|
|
90
|
+
getFlag<K extends keyof TFlags>(key: K): FlagValue<TFlags[K]>;
|
|
91
|
+
getBooleanFlag<K extends keyof TFlags>(key: K & (TFlags[K] extends {
|
|
92
|
+
type: "boolean";
|
|
93
|
+
} ? K : never)): FlagValue<TFlags[K]> | false;
|
|
94
|
+
getVariantFlag<K extends keyof TFlags>(key: K & (TFlags[K] extends {
|
|
95
|
+
type: "variant";
|
|
96
|
+
} ? K : never)): FlagValue<TFlags[K]> | null;
|
|
97
|
+
getPayloadFlag<K extends keyof TFlags>(key: K & (TFlags[K] extends {
|
|
98
|
+
type: "payload";
|
|
99
|
+
} ? K : never)): FlagValue<TFlags[K]> | null;
|
|
100
|
+
onChange(cb: (flags: EvaluatedFlags<TFlags>) => void): () => void;
|
|
101
|
+
get store(): MapStore<EvaluatedFlags<TFlags>>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { type EvaluatedFlags, type FlagConfig, type FlagInput, type FlagSchema, type FlagValue, type FlagValues, Flaggly, type FlagglyOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// sdk/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Flaggly: () => Flaggly
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// node_modules/.pnpm/nanostores@1.0.1/node_modules/nanostores/clean-stores/index.js
|
|
28
|
+
var clean = Symbol("clean");
|
|
29
|
+
|
|
30
|
+
// node_modules/.pnpm/nanostores@1.0.1/node_modules/nanostores/atom/index.js
|
|
31
|
+
var listenerQueue = [];
|
|
32
|
+
var lqIndex = 0;
|
|
33
|
+
var QUEUE_ITEMS_PER_LISTENER = 4;
|
|
34
|
+
var epoch = 0;
|
|
35
|
+
var atom = (initialValue) => {
|
|
36
|
+
let listeners = [];
|
|
37
|
+
let $atom = {
|
|
38
|
+
get() {
|
|
39
|
+
if (!$atom.lc) {
|
|
40
|
+
$atom.listen(() => {
|
|
41
|
+
})();
|
|
42
|
+
}
|
|
43
|
+
return $atom.value;
|
|
44
|
+
},
|
|
45
|
+
lc: 0,
|
|
46
|
+
listen(listener) {
|
|
47
|
+
$atom.lc = listeners.push(listener);
|
|
48
|
+
return () => {
|
|
49
|
+
for (let i = lqIndex + QUEUE_ITEMS_PER_LISTENER; i < listenerQueue.length; ) {
|
|
50
|
+
if (listenerQueue[i] === listener) {
|
|
51
|
+
listenerQueue.splice(i, QUEUE_ITEMS_PER_LISTENER);
|
|
52
|
+
} else {
|
|
53
|
+
i += QUEUE_ITEMS_PER_LISTENER;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
let index = listeners.indexOf(listener);
|
|
57
|
+
if (~index) {
|
|
58
|
+
listeners.splice(index, 1);
|
|
59
|
+
if (!--$atom.lc) $atom.off();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
notify(oldValue, changedKey) {
|
|
64
|
+
epoch++;
|
|
65
|
+
let runListenerQueue = !listenerQueue.length;
|
|
66
|
+
for (let listener of listeners) {
|
|
67
|
+
listenerQueue.push(listener, $atom.value, oldValue, changedKey);
|
|
68
|
+
}
|
|
69
|
+
if (runListenerQueue) {
|
|
70
|
+
for (lqIndex = 0; lqIndex < listenerQueue.length; lqIndex += QUEUE_ITEMS_PER_LISTENER) {
|
|
71
|
+
listenerQueue[lqIndex](
|
|
72
|
+
listenerQueue[lqIndex + 1],
|
|
73
|
+
listenerQueue[lqIndex + 2],
|
|
74
|
+
listenerQueue[lqIndex + 3]
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
listenerQueue.length = 0;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
/* It will be called on last listener unsubscribing.
|
|
81
|
+
We will redefine it in onMount and onStop. */
|
|
82
|
+
off() {
|
|
83
|
+
},
|
|
84
|
+
set(newValue) {
|
|
85
|
+
let oldValue = $atom.value;
|
|
86
|
+
if (oldValue !== newValue) {
|
|
87
|
+
$atom.value = newValue;
|
|
88
|
+
$atom.notify(oldValue);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
subscribe(listener) {
|
|
92
|
+
let unbind = $atom.listen(listener);
|
|
93
|
+
listener($atom.value);
|
|
94
|
+
return unbind;
|
|
95
|
+
},
|
|
96
|
+
value: initialValue
|
|
97
|
+
};
|
|
98
|
+
if (process.env.NODE_ENV !== "production") {
|
|
99
|
+
$atom[clean] = () => {
|
|
100
|
+
listeners = [];
|
|
101
|
+
$atom.lc = 0;
|
|
102
|
+
$atom.off();
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return $atom;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// node_modules/.pnpm/nanostores@1.0.1/node_modules/nanostores/map/index.js
|
|
109
|
+
var map = (initial = {}) => {
|
|
110
|
+
let $map = atom(initial);
|
|
111
|
+
$map.setKey = function(key, value) {
|
|
112
|
+
let oldMap = $map.value;
|
|
113
|
+
if (typeof value === "undefined" && key in $map.value) {
|
|
114
|
+
$map.value = { ...$map.value };
|
|
115
|
+
delete $map.value[key];
|
|
116
|
+
$map.notify(oldMap, key);
|
|
117
|
+
} else if ($map.value[key] !== value) {
|
|
118
|
+
$map.value = {
|
|
119
|
+
...$map.value,
|
|
120
|
+
[key]: value
|
|
121
|
+
};
|
|
122
|
+
$map.notify(oldMap, key);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
return $map;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// sdk/index.ts
|
|
129
|
+
var Flaggly = class {
|
|
130
|
+
url;
|
|
131
|
+
apiKey;
|
|
132
|
+
app;
|
|
133
|
+
env;
|
|
134
|
+
user;
|
|
135
|
+
id;
|
|
136
|
+
getBackupId;
|
|
137
|
+
#flags = map();
|
|
138
|
+
constructor({
|
|
139
|
+
url,
|
|
140
|
+
apiKey,
|
|
141
|
+
app = "default",
|
|
142
|
+
env = "production",
|
|
143
|
+
lazy = false,
|
|
144
|
+
bootstrap,
|
|
145
|
+
getBackupId
|
|
146
|
+
}) {
|
|
147
|
+
this.url = url;
|
|
148
|
+
this.apiKey = apiKey;
|
|
149
|
+
this.app = app;
|
|
150
|
+
this.env = env;
|
|
151
|
+
this.getBackupId = getBackupId;
|
|
152
|
+
const defValues = bootstrap ? Object.entries(bootstrap).reduce(
|
|
153
|
+
(acc, [flagKey, flagValue]) => {
|
|
154
|
+
const type = typeof flagValue === "boolean" ? "boolean" : typeof flagValue === "string" ? "variant" : "payload";
|
|
155
|
+
acc[flagKey] = {
|
|
156
|
+
type,
|
|
157
|
+
result: flagValue
|
|
158
|
+
};
|
|
159
|
+
return acc;
|
|
160
|
+
},
|
|
161
|
+
{}
|
|
162
|
+
) : void 0;
|
|
163
|
+
if (defValues) {
|
|
164
|
+
this.#flags.set(defValues);
|
|
165
|
+
}
|
|
166
|
+
if (!lazy) {
|
|
167
|
+
this.fetchFlags();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async identify(id, user) {
|
|
171
|
+
this.id = id;
|
|
172
|
+
this.user = user;
|
|
173
|
+
return await this.fetchFlags({ id, user });
|
|
174
|
+
}
|
|
175
|
+
getPageUrl() {
|
|
176
|
+
if ("window" in globalThis) {
|
|
177
|
+
return globalThis.window.location.href;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
#getBackupId() {
|
|
182
|
+
if (this.getBackupId) {
|
|
183
|
+
return this.getBackupId();
|
|
184
|
+
}
|
|
185
|
+
if ("window" in globalThis) {
|
|
186
|
+
const key = `__flaggly_id.${this.app}.${this.env}`;
|
|
187
|
+
const storage = globalThis.window.localStorage.getItem(key);
|
|
188
|
+
if (!storage) {
|
|
189
|
+
const id = globalThis.crypto.randomUUID();
|
|
190
|
+
globalThis.window.localStorage.setItem(key, id);
|
|
191
|
+
return id;
|
|
192
|
+
}
|
|
193
|
+
return storage;
|
|
194
|
+
}
|
|
195
|
+
return globalThis.crypto.randomUUID();
|
|
196
|
+
}
|
|
197
|
+
async #request(path, options = {}) {
|
|
198
|
+
const { method = "POST", body } = options;
|
|
199
|
+
const url = new URL(path, this.url);
|
|
200
|
+
const response = await fetch(url, {
|
|
201
|
+
method,
|
|
202
|
+
headers: {
|
|
203
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
204
|
+
"x-app-id": this.app,
|
|
205
|
+
"x-env-id": this.env,
|
|
206
|
+
...body ? { "Content-Type": "application/json" } : {}
|
|
207
|
+
},
|
|
208
|
+
body: body ? JSON.stringify(body) : void 0
|
|
209
|
+
});
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
let cause;
|
|
212
|
+
try {
|
|
213
|
+
cause = await response.json();
|
|
214
|
+
} catch {
|
|
215
|
+
cause = await response.text();
|
|
216
|
+
}
|
|
217
|
+
throw new Error(`Request failed: ${response.statusText}`, { cause });
|
|
218
|
+
}
|
|
219
|
+
return response.json();
|
|
220
|
+
}
|
|
221
|
+
async fetchFlags(input) {
|
|
222
|
+
const result = await this.#request("/api/eval", {
|
|
223
|
+
method: "POST",
|
|
224
|
+
body: {
|
|
225
|
+
id: input?.id ?? this.id ?? this.#getBackupId(),
|
|
226
|
+
user: input?.user ?? this.user,
|
|
227
|
+
page: {
|
|
228
|
+
url: this.getPageUrl()
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
if (result) {
|
|
233
|
+
this.#flags.set(result);
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
async fetchFlag(key, input) {
|
|
238
|
+
const result = await this.#request(
|
|
239
|
+
`/api/eval/${String(key)}`,
|
|
240
|
+
{
|
|
241
|
+
method: "POST",
|
|
242
|
+
body: {
|
|
243
|
+
id: input?.id ?? this.id ?? this.#getBackupId(),
|
|
244
|
+
user: input?.user ?? this.user,
|
|
245
|
+
page: {
|
|
246
|
+
url: this.getPageUrl()
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
if (result) {
|
|
252
|
+
this.#flags.setKey(key, result);
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
getFlags() {
|
|
257
|
+
return this.#flags.get();
|
|
258
|
+
}
|
|
259
|
+
getFlag(key) {
|
|
260
|
+
const flags = this.#flags.get();
|
|
261
|
+
return flags?.[key]?.result;
|
|
262
|
+
}
|
|
263
|
+
getBooleanFlag(key) {
|
|
264
|
+
const flags = this.#flags.get();
|
|
265
|
+
return flags[key]?.result ?? false;
|
|
266
|
+
}
|
|
267
|
+
getVariantFlag(key) {
|
|
268
|
+
const flags = this.#flags.get();
|
|
269
|
+
return flags[key]?.result ?? null;
|
|
270
|
+
}
|
|
271
|
+
getPayloadFlag(key) {
|
|
272
|
+
const flags = this.#flags.get();
|
|
273
|
+
return flags[key]?.result ?? null;
|
|
274
|
+
}
|
|
275
|
+
onChange(cb) {
|
|
276
|
+
return this.#flags.subscribe(cb);
|
|
277
|
+
}
|
|
278
|
+
get store() {
|
|
279
|
+
return this.#flags;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
283
|
+
0 && (module.exports = {
|
|
284
|
+
Flaggly
|
|
285
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// node_modules/.pnpm/nanostores@1.0.1/node_modules/nanostores/clean-stores/index.js
|
|
2
|
+
var clean = Symbol("clean");
|
|
3
|
+
|
|
4
|
+
// node_modules/.pnpm/nanostores@1.0.1/node_modules/nanostores/atom/index.js
|
|
5
|
+
var listenerQueue = [];
|
|
6
|
+
var lqIndex = 0;
|
|
7
|
+
var QUEUE_ITEMS_PER_LISTENER = 4;
|
|
8
|
+
var epoch = 0;
|
|
9
|
+
var atom = (initialValue) => {
|
|
10
|
+
let listeners = [];
|
|
11
|
+
let $atom = {
|
|
12
|
+
get() {
|
|
13
|
+
if (!$atom.lc) {
|
|
14
|
+
$atom.listen(() => {
|
|
15
|
+
})();
|
|
16
|
+
}
|
|
17
|
+
return $atom.value;
|
|
18
|
+
},
|
|
19
|
+
lc: 0,
|
|
20
|
+
listen(listener) {
|
|
21
|
+
$atom.lc = listeners.push(listener);
|
|
22
|
+
return () => {
|
|
23
|
+
for (let i = lqIndex + QUEUE_ITEMS_PER_LISTENER; i < listenerQueue.length; ) {
|
|
24
|
+
if (listenerQueue[i] === listener) {
|
|
25
|
+
listenerQueue.splice(i, QUEUE_ITEMS_PER_LISTENER);
|
|
26
|
+
} else {
|
|
27
|
+
i += QUEUE_ITEMS_PER_LISTENER;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
let index = listeners.indexOf(listener);
|
|
31
|
+
if (~index) {
|
|
32
|
+
listeners.splice(index, 1);
|
|
33
|
+
if (!--$atom.lc) $atom.off();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
notify(oldValue, changedKey) {
|
|
38
|
+
epoch++;
|
|
39
|
+
let runListenerQueue = !listenerQueue.length;
|
|
40
|
+
for (let listener of listeners) {
|
|
41
|
+
listenerQueue.push(listener, $atom.value, oldValue, changedKey);
|
|
42
|
+
}
|
|
43
|
+
if (runListenerQueue) {
|
|
44
|
+
for (lqIndex = 0; lqIndex < listenerQueue.length; lqIndex += QUEUE_ITEMS_PER_LISTENER) {
|
|
45
|
+
listenerQueue[lqIndex](
|
|
46
|
+
listenerQueue[lqIndex + 1],
|
|
47
|
+
listenerQueue[lqIndex + 2],
|
|
48
|
+
listenerQueue[lqIndex + 3]
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
listenerQueue.length = 0;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
/* It will be called on last listener unsubscribing.
|
|
55
|
+
We will redefine it in onMount and onStop. */
|
|
56
|
+
off() {
|
|
57
|
+
},
|
|
58
|
+
set(newValue) {
|
|
59
|
+
let oldValue = $atom.value;
|
|
60
|
+
if (oldValue !== newValue) {
|
|
61
|
+
$atom.value = newValue;
|
|
62
|
+
$atom.notify(oldValue);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
subscribe(listener) {
|
|
66
|
+
let unbind = $atom.listen(listener);
|
|
67
|
+
listener($atom.value);
|
|
68
|
+
return unbind;
|
|
69
|
+
},
|
|
70
|
+
value: initialValue
|
|
71
|
+
};
|
|
72
|
+
if (process.env.NODE_ENV !== "production") {
|
|
73
|
+
$atom[clean] = () => {
|
|
74
|
+
listeners = [];
|
|
75
|
+
$atom.lc = 0;
|
|
76
|
+
$atom.off();
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return $atom;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// node_modules/.pnpm/nanostores@1.0.1/node_modules/nanostores/map/index.js
|
|
83
|
+
var map = (initial = {}) => {
|
|
84
|
+
let $map = atom(initial);
|
|
85
|
+
$map.setKey = function(key, value) {
|
|
86
|
+
let oldMap = $map.value;
|
|
87
|
+
if (typeof value === "undefined" && key in $map.value) {
|
|
88
|
+
$map.value = { ...$map.value };
|
|
89
|
+
delete $map.value[key];
|
|
90
|
+
$map.notify(oldMap, key);
|
|
91
|
+
} else if ($map.value[key] !== value) {
|
|
92
|
+
$map.value = {
|
|
93
|
+
...$map.value,
|
|
94
|
+
[key]: value
|
|
95
|
+
};
|
|
96
|
+
$map.notify(oldMap, key);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
return $map;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// sdk/index.ts
|
|
103
|
+
var Flaggly = class {
|
|
104
|
+
url;
|
|
105
|
+
apiKey;
|
|
106
|
+
app;
|
|
107
|
+
env;
|
|
108
|
+
user;
|
|
109
|
+
id;
|
|
110
|
+
getBackupId;
|
|
111
|
+
#flags = map();
|
|
112
|
+
constructor({
|
|
113
|
+
url,
|
|
114
|
+
apiKey,
|
|
115
|
+
app = "default",
|
|
116
|
+
env = "production",
|
|
117
|
+
lazy = false,
|
|
118
|
+
bootstrap,
|
|
119
|
+
getBackupId
|
|
120
|
+
}) {
|
|
121
|
+
this.url = url;
|
|
122
|
+
this.apiKey = apiKey;
|
|
123
|
+
this.app = app;
|
|
124
|
+
this.env = env;
|
|
125
|
+
this.getBackupId = getBackupId;
|
|
126
|
+
const defValues = bootstrap ? Object.entries(bootstrap).reduce(
|
|
127
|
+
(acc, [flagKey, flagValue]) => {
|
|
128
|
+
const type = typeof flagValue === "boolean" ? "boolean" : typeof flagValue === "string" ? "variant" : "payload";
|
|
129
|
+
acc[flagKey] = {
|
|
130
|
+
type,
|
|
131
|
+
result: flagValue
|
|
132
|
+
};
|
|
133
|
+
return acc;
|
|
134
|
+
},
|
|
135
|
+
{}
|
|
136
|
+
) : void 0;
|
|
137
|
+
if (defValues) {
|
|
138
|
+
this.#flags.set(defValues);
|
|
139
|
+
}
|
|
140
|
+
if (!lazy) {
|
|
141
|
+
this.fetchFlags();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async identify(id, user) {
|
|
145
|
+
this.id = id;
|
|
146
|
+
this.user = user;
|
|
147
|
+
return await this.fetchFlags({ id, user });
|
|
148
|
+
}
|
|
149
|
+
getPageUrl() {
|
|
150
|
+
if ("window" in globalThis) {
|
|
151
|
+
return globalThis.window.location.href;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
#getBackupId() {
|
|
156
|
+
if (this.getBackupId) {
|
|
157
|
+
return this.getBackupId();
|
|
158
|
+
}
|
|
159
|
+
if ("window" in globalThis) {
|
|
160
|
+
const key = `__flaggly_id.${this.app}.${this.env}`;
|
|
161
|
+
const storage = globalThis.window.localStorage.getItem(key);
|
|
162
|
+
if (!storage) {
|
|
163
|
+
const id = globalThis.crypto.randomUUID();
|
|
164
|
+
globalThis.window.localStorage.setItem(key, id);
|
|
165
|
+
return id;
|
|
166
|
+
}
|
|
167
|
+
return storage;
|
|
168
|
+
}
|
|
169
|
+
return globalThis.crypto.randomUUID();
|
|
170
|
+
}
|
|
171
|
+
async #request(path, options = {}) {
|
|
172
|
+
const { method = "POST", body } = options;
|
|
173
|
+
const url = new URL(path, this.url);
|
|
174
|
+
const response = await fetch(url, {
|
|
175
|
+
method,
|
|
176
|
+
headers: {
|
|
177
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
178
|
+
"x-app-id": this.app,
|
|
179
|
+
"x-env-id": this.env,
|
|
180
|
+
...body ? { "Content-Type": "application/json" } : {}
|
|
181
|
+
},
|
|
182
|
+
body: body ? JSON.stringify(body) : void 0
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
let cause;
|
|
186
|
+
try {
|
|
187
|
+
cause = await response.json();
|
|
188
|
+
} catch {
|
|
189
|
+
cause = await response.text();
|
|
190
|
+
}
|
|
191
|
+
throw new Error(`Request failed: ${response.statusText}`, { cause });
|
|
192
|
+
}
|
|
193
|
+
return response.json();
|
|
194
|
+
}
|
|
195
|
+
async fetchFlags(input) {
|
|
196
|
+
const result = await this.#request("/api/eval", {
|
|
197
|
+
method: "POST",
|
|
198
|
+
body: {
|
|
199
|
+
id: input?.id ?? this.id ?? this.#getBackupId(),
|
|
200
|
+
user: input?.user ?? this.user,
|
|
201
|
+
page: {
|
|
202
|
+
url: this.getPageUrl()
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
if (result) {
|
|
207
|
+
this.#flags.set(result);
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
async fetchFlag(key, input) {
|
|
212
|
+
const result = await this.#request(
|
|
213
|
+
`/api/eval/${String(key)}`,
|
|
214
|
+
{
|
|
215
|
+
method: "POST",
|
|
216
|
+
body: {
|
|
217
|
+
id: input?.id ?? this.id ?? this.#getBackupId(),
|
|
218
|
+
user: input?.user ?? this.user,
|
|
219
|
+
page: {
|
|
220
|
+
url: this.getPageUrl()
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
if (result) {
|
|
226
|
+
this.#flags.setKey(key, result);
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
getFlags() {
|
|
231
|
+
return this.#flags.get();
|
|
232
|
+
}
|
|
233
|
+
getFlag(key) {
|
|
234
|
+
const flags = this.#flags.get();
|
|
235
|
+
return flags?.[key]?.result;
|
|
236
|
+
}
|
|
237
|
+
getBooleanFlag(key) {
|
|
238
|
+
const flags = this.#flags.get();
|
|
239
|
+
return flags[key]?.result ?? false;
|
|
240
|
+
}
|
|
241
|
+
getVariantFlag(key) {
|
|
242
|
+
const flags = this.#flags.get();
|
|
243
|
+
return flags[key]?.result ?? null;
|
|
244
|
+
}
|
|
245
|
+
getPayloadFlag(key) {
|
|
246
|
+
const flags = this.#flags.get();
|
|
247
|
+
return flags[key]?.result ?? null;
|
|
248
|
+
}
|
|
249
|
+
onChange(cb) {
|
|
250
|
+
return this.#flags.subscribe(cb);
|
|
251
|
+
}
|
|
252
|
+
get store() {
|
|
253
|
+
return this.#flags;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
export {
|
|
257
|
+
Flaggly
|
|
258
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flaggly/sdk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Client SDK for Flaggly",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"feature-flags",
|
|
13
|
+
"sdk",
|
|
14
|
+
"js",
|
|
15
|
+
"typescript",
|
|
16
|
+
"cloudflare"
|
|
17
|
+
],
|
|
18
|
+
"author": "butttons",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"nanostores": "^1.0.1"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/butttons/flaggly"
|
|
26
|
+
}
|
|
27
|
+
}
|