@faststats/flags-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/README.md +4 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +76 -0
- package/package.json +41 -0
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Adapter } from "flags";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
/** Who a flag is evaluated for. Return this from the flag's `identify`. */
|
|
4
|
+
export interface FastStatsEntities {
|
|
5
|
+
/** Stable device, install or server ID. Required unless `userId` is set. */
|
|
6
|
+
identifier?: string;
|
|
7
|
+
/** Your own ID for a signed-in user. Rollouts follow it across devices. */
|
|
8
|
+
userId?: string;
|
|
9
|
+
/** Links evaluations to a session in analytics. */
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
/** Anything targeting rules should match on. */
|
|
12
|
+
attributes?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface FastStatsAdapterOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Public project token or a FastStats API key scoped to one project.
|
|
17
|
+
* Defaults to `FASTSTATS_FLAGS_TOKEN`, then `FASTSTATS_TOKEN`.
|
|
18
|
+
*/
|
|
19
|
+
token?: string;
|
|
20
|
+
/** Base URL of the feature flags service. Defaults to `FASTSTATS_FLAGS_ENDPOINT`. */
|
|
21
|
+
endpoint?: string;
|
|
22
|
+
/** Link to a flag in the dashboard, shown in the Flags Explorer. */
|
|
23
|
+
origin?: string | ((key: string) => string | undefined);
|
|
24
|
+
/** Aborts an evaluation that takes longer, so the flag serves its default. */
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
fetch?: typeof fetch;
|
|
27
|
+
}
|
|
28
|
+
export type FastStatsReason = "disabled" | "prerequisite_failed" | "target_match" | "rule_match" | "fallthrough";
|
|
29
|
+
export declare class FastStatsFlagsError extends Error {
|
|
30
|
+
name: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function createFastStatsAdapter(options?: FastStatsAdapterOptions): <ValueType, EntitiesType extends FastStatsEntities = FastStatsEntities>() => Adapter<ValueType, EntitiesType>;
|
|
33
|
+
/**
|
|
34
|
+
* Adapter configured from `FASTSTATS_FLAGS_TOKEN` (or `FASTSTATS_TOKEN`) and,
|
|
35
|
+
* optionally, `FASTSTATS_FLAGS_ENDPOINT`.
|
|
36
|
+
*/
|
|
37
|
+
export declare const fastStatsAdapter: <ValueType, EntitiesType extends FastStatsEntities = FastStatsEntities>() => Adapter<ValueType, EntitiesType>;
|
|
38
|
+
//#endregion
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//#region src/index.ts
|
|
2
|
+
const DEFAULT_ENDPOINT = "https://flags.faststats.dev";
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 3e3;
|
|
4
|
+
const COUNTRY_HEADERS = ["x-vercel-ip-country", "cf-ipcountry"];
|
|
5
|
+
var FastStatsFlagsError = class extends Error {
|
|
6
|
+
name = "FastStatsFlagsError";
|
|
7
|
+
};
|
|
8
|
+
function createFastStatsAdapter(options = {}) {
|
|
9
|
+
let connection;
|
|
10
|
+
function connect() {
|
|
11
|
+
if (!connection) {
|
|
12
|
+
const token = options.token ?? process.env.FASTSTATS_FLAGS_TOKEN ?? process.env.FASTSTATS_TOKEN;
|
|
13
|
+
if (!token) throw new FastStatsFlagsError("FastStats Adapter: Missing token. Pass `token` or set FASTSTATS_FLAGS_TOKEN");
|
|
14
|
+
connection = {
|
|
15
|
+
url: `${(options.endpoint ?? process.env.FASTSTATS_FLAGS_ENDPOINT ?? DEFAULT_ENDPOINT).replace(/\/+$/, "")}/v1/evaluate`,
|
|
16
|
+
token
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return connection;
|
|
20
|
+
}
|
|
21
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
22
|
+
const adapterId = Symbol("faststats");
|
|
23
|
+
async function evaluate(keys, entities, headers) {
|
|
24
|
+
if (!entities?.identifier && !entities?.userId) throw new FastStatsFlagsError("FastStats Adapter: identify() must return an identifier or userId");
|
|
25
|
+
const { url, token } = connect();
|
|
26
|
+
const attributes = { ...entities.attributes };
|
|
27
|
+
const country = COUNTRY_HEADERS.map((name) => headers.get(name)).find(Boolean);
|
|
28
|
+
if (country && attributes.country === void 0) attributes.country = country;
|
|
29
|
+
const requestHeaders = {
|
|
30
|
+
"content-type": "application/json",
|
|
31
|
+
authorization: `Bearer ${token}`
|
|
32
|
+
};
|
|
33
|
+
const userAgent = headers.get("user-agent");
|
|
34
|
+
if (userAgent) requestHeaders["user-agent"] = userAgent;
|
|
35
|
+
const response = await (options.fetch ?? fetch)(url, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: requestHeaders,
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
identifier: entities.identifier,
|
|
40
|
+
userId: entities.userId,
|
|
41
|
+
sessionId: entities.sessionId,
|
|
42
|
+
attributes,
|
|
43
|
+
flags: [...new Set(keys)]
|
|
44
|
+
}),
|
|
45
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
46
|
+
cache: "no-store"
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) throw new FastStatsFlagsError(`FastStats Adapter: Evaluation failed with status ${response.status}`);
|
|
49
|
+
const body = await response.json();
|
|
50
|
+
if (typeof body?.flags !== "object" || body.flags === null) throw new FastStatsFlagsError("FastStats Adapter: Unexpected response from the flags service");
|
|
51
|
+
return body.flags;
|
|
52
|
+
}
|
|
53
|
+
return function fastStatsAdapter() {
|
|
54
|
+
return {
|
|
55
|
+
adapterId,
|
|
56
|
+
origin: options.origin,
|
|
57
|
+
async decide({ key, entities, headers, defaultValue }) {
|
|
58
|
+
const result = (await evaluate([key], entities, headers))[key];
|
|
59
|
+
if (result) return result.value;
|
|
60
|
+
if (defaultValue !== void 0) return defaultValue;
|
|
61
|
+
throw new FastStatsFlagsError(`FastStats Adapter: Flag "${key}" does not exist and has no defaultValue`);
|
|
62
|
+
},
|
|
63
|
+
async bulkDecide({ flags, entities, headers }) {
|
|
64
|
+
const results = await evaluate(flags.map((flag) => flag.key), entities, headers);
|
|
65
|
+
return Object.fromEntries(Object.entries(results).map(([key, result]) => [key, result.value]));
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Adapter configured from `FASTSTATS_FLAGS_TOKEN` (or `FASTSTATS_TOKEN`) and,
|
|
72
|
+
* optionally, `FASTSTATS_FLAGS_ENDPOINT`.
|
|
73
|
+
*/
|
|
74
|
+
const fastStatsAdapter = createFastStatsAdapter();
|
|
75
|
+
//#endregion
|
|
76
|
+
export { FastStatsFlagsError, createFastStatsAdapter, fastStatsAdapter };
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@faststats/flags-sdk",
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "https://github.com/faststats-dev/faststats-javascript.git"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.0.1",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsdown",
|
|
28
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
29
|
+
"test": "bun test"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"flags": ">=4"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/bun": "^1.4.2",
|
|
36
|
+
"@types/node": "^26.6.2",
|
|
37
|
+
"flags": "^4.3.1",
|
|
38
|
+
"tsdown": "^0.23.0",
|
|
39
|
+
"typescript": "^7.0.2"
|
|
40
|
+
}
|
|
41
|
+
}
|