@cloudflare/flagship 0.0.3 → 0.2.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.
- package/README.md +162 -2
- package/dist/{index-C_sW3e_7.d.mts → index-BMGtqsWU.d.mts} +63 -3
- package/dist/{index-D8YLMfBG.d.cts → index-BsvVosek.d.cts} +63 -3
- package/dist/index.cjs +1 -7
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -2
- package/dist/server.cjs +1 -298
- package/dist/server.d.cts +46 -21
- package/dist/server.d.mts +46 -21
- package/dist/server.mjs +1 -290
- package/dist/src-DW2QohY9.mjs +1 -0
- package/dist/src-QEIBSid3.cjs +1 -0
- package/dist/web.cjs +1 -148
- package/dist/web.d.cts +1 -1
- package/dist/web.d.mts +1 -1
- package/dist/web.mjs +1 -142
- package/package.json +4 -8
- package/dist/src-CiVDWmng.mjs +0 -202
- package/dist/src-De-abNIr.cjs +0 -231
package/dist/src-De-abNIr.cjs
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
//#region src/types.ts
|
|
2
|
-
/** Default base URL for the Flagship API. */
|
|
3
|
-
const FLAGSHIP_DEFAULT_BASE_URL = "https://api.cloudflare.com";
|
|
4
|
-
/**
|
|
5
|
-
* Internal error codes produced by `FlagshipClient`.
|
|
6
|
-
* These are mapped to OpenFeature `ErrorCode` values by the providers.
|
|
7
|
-
*/
|
|
8
|
-
let FlagshipErrorCode = /* @__PURE__ */ function(FlagshipErrorCode) {
|
|
9
|
-
/** HTTP or fetch-level failure (non-404/400 status, connection refused, etc.) */
|
|
10
|
-
FlagshipErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
11
|
-
/** The request was aborted because the configured timeout elapsed. */
|
|
12
|
-
FlagshipErrorCode["TIMEOUT_ERROR"] = "TIMEOUT_ERROR";
|
|
13
|
-
/** The response body was not a valid evaluation response. */
|
|
14
|
-
FlagshipErrorCode["PARSE_ERROR"] = "PARSE_ERROR";
|
|
15
|
-
/** The evaluation context contained complex values that cannot be serialized to query parameters. */
|
|
16
|
-
FlagshipErrorCode["INVALID_CONTEXT"] = "INVALID_CONTEXT";
|
|
17
|
-
return FlagshipErrorCode;
|
|
18
|
-
}({});
|
|
19
|
-
/**
|
|
20
|
-
* Error thrown by `FlagshipClient` for all abnormal conditions.
|
|
21
|
-
* Carries a `code` for programmatic handling and an optional `cause` which
|
|
22
|
-
* is the underlying `Response` object for HTTP errors, allowing callers to
|
|
23
|
-
* inspect the status code (e.g. to distinguish 404 → `FLAG_NOT_FOUND`).
|
|
24
|
-
*/
|
|
25
|
-
var FlagshipError = class extends Error {
|
|
26
|
-
constructor(message, code, cause) {
|
|
27
|
-
super(message);
|
|
28
|
-
this.code = code;
|
|
29
|
-
this.cause = cause;
|
|
30
|
-
this.name = "FlagshipError";
|
|
31
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
//#endregion
|
|
35
|
-
//#region src/context.ts
|
|
36
|
-
/**
|
|
37
|
-
* Utility for transforming OpenFeature evaluation context
|
|
38
|
-
*/
|
|
39
|
-
var ContextTransformer = class {
|
|
40
|
-
/**
|
|
41
|
-
* Transform OpenFeature evaluation context to query parameters
|
|
42
|
-
* for the Flagship API.
|
|
43
|
-
*
|
|
44
|
-
* Primitive values (`string`, `number`, `boolean`) and `Date` objects are
|
|
45
|
-
* serialized directly. Nested objects and arrays cannot be expressed as query
|
|
46
|
-
* parameters and are skipped.
|
|
47
|
-
*
|
|
48
|
-
* When a `droppedKeys` collector array is provided, skipped key names are
|
|
49
|
-
* pushed into it and **no** console warning is emitted — the caller is
|
|
50
|
-
* expected to handle the situation (e.g. throw `INVALID_CONTEXT`).
|
|
51
|
-
* When no collector is provided, a `console.warn` is emitted for each
|
|
52
|
-
* skipped key so the issue is still surfaced in development.
|
|
53
|
-
*
|
|
54
|
-
* @param context - OpenFeature evaluation context
|
|
55
|
-
* @param droppedKeys - Optional collector array; skipped key names are pushed here
|
|
56
|
-
*/
|
|
57
|
-
static toQueryParams(context, droppedKeys) {
|
|
58
|
-
const params = {};
|
|
59
|
-
for (const [key, value] of Object.entries(context)) {
|
|
60
|
-
if (value === void 0 || value === null) continue;
|
|
61
|
-
if (value instanceof Date) {
|
|
62
|
-
params[key] = value.toISOString();
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
66
|
-
params[key] = String(value);
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (typeof value === "object") {
|
|
70
|
-
if (droppedKeys) droppedKeys.push(key);
|
|
71
|
-
else console.warn(`[Flagship] Context key "${key}" is a complex object/array and cannot be serialized to a query parameter. This value will be ignored during flag evaluation.`);
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return params;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Build URL with query parameters from context.
|
|
79
|
-
*
|
|
80
|
-
* @param baseUrl - The base evaluation endpoint URL
|
|
81
|
-
* @param flagKey - The flag key to evaluate
|
|
82
|
-
* @param context - OpenFeature evaluation context
|
|
83
|
-
* @param droppedKeys - Optional collector array; skipped context key names are pushed here
|
|
84
|
-
*/
|
|
85
|
-
static buildUrl(baseUrl, flagKey, context, droppedKeys) {
|
|
86
|
-
const url = new URL(baseUrl);
|
|
87
|
-
url.searchParams.set("flagKey", flagKey);
|
|
88
|
-
const params = this.toQueryParams(context, droppedKeys);
|
|
89
|
-
for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
|
|
90
|
-
return url.toString();
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
//#endregion
|
|
94
|
-
//#region src/client.ts
|
|
95
|
-
var FlagshipClient = class {
|
|
96
|
-
constructor(options) {
|
|
97
|
-
this.options = {
|
|
98
|
-
endpoint: resolveEndpoint(options),
|
|
99
|
-
fetchOptions: buildFetchOptions(options),
|
|
100
|
-
timeout: options.timeout || 5e3,
|
|
101
|
-
retries: Math.min(options.retries !== void 0 ? options.retries : 1, 10),
|
|
102
|
-
retryDelay: Math.min(options.retryDelay !== void 0 ? options.retryDelay : 1e3, 3e4)
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Evaluate a flag with the given context.
|
|
107
|
-
*
|
|
108
|
-
* Throws a `FlagshipError` with `FlagshipErrorCode.INVALID_CONTEXT` if the
|
|
109
|
-
* evaluation context contains complex values (objects or arrays) that cannot
|
|
110
|
-
* be serialized to query parameters.
|
|
111
|
-
*/
|
|
112
|
-
async evaluate(flagKey, context) {
|
|
113
|
-
const droppedKeys = [];
|
|
114
|
-
const url = ContextTransformer.buildUrl(this.options.endpoint, flagKey, context, droppedKeys);
|
|
115
|
-
if (droppedKeys.length > 0) throw new FlagshipError(`Evaluation context contains complex values that cannot be serialized for flag "${flagKey}". Unsupported keys: ${droppedKeys.join(", ")}. Use primitive values (string, number, boolean) or Date objects.`, FlagshipErrorCode.INVALID_CONTEXT);
|
|
116
|
-
return this.fetchWithRetry(url, this.options.retries);
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Fetch with retry logic. Only retries on transient network/server errors —
|
|
120
|
-
* 404 and 400 responses are terminal and propagated immediately.
|
|
121
|
-
*/
|
|
122
|
-
async fetchWithRetry(url, retriesLeft) {
|
|
123
|
-
try {
|
|
124
|
-
return await this.fetchWithTimeout(url, this.options.timeout);
|
|
125
|
-
} catch (error) {
|
|
126
|
-
if (error instanceof FlagshipError && error.cause instanceof Response) {
|
|
127
|
-
const status = error.cause.status;
|
|
128
|
-
if (status === 404 || status === 400) throw error;
|
|
129
|
-
}
|
|
130
|
-
if (retriesLeft > 0) {
|
|
131
|
-
await new Promise((resolve) => setTimeout(resolve, this.options.retryDelay));
|
|
132
|
-
return this.fetchWithRetry(url, retriesLeft - 1);
|
|
133
|
-
}
|
|
134
|
-
throw error;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Fetch with timeout using AbortController
|
|
139
|
-
*/
|
|
140
|
-
async fetchWithTimeout(url, timeout) {
|
|
141
|
-
const controller = new AbortController();
|
|
142
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
143
|
-
try {
|
|
144
|
-
const response = await fetch(url, {
|
|
145
|
-
...this.options.fetchOptions,
|
|
146
|
-
signal: controller.signal
|
|
147
|
-
});
|
|
148
|
-
clearTimeout(timeoutId);
|
|
149
|
-
if (!response.ok) throw new FlagshipError(`HTTP ${response.status}: ${response.statusText}`, FlagshipErrorCode.NETWORK_ERROR, response);
|
|
150
|
-
const data = await response.json();
|
|
151
|
-
if (!data || typeof data !== "object" || !("flagKey" in data) || !("value" in data)) throw new FlagshipError("Invalid response format from Flagship API", FlagshipErrorCode.PARSE_ERROR);
|
|
152
|
-
return data;
|
|
153
|
-
} catch (error) {
|
|
154
|
-
clearTimeout(timeoutId);
|
|
155
|
-
if (error instanceof Error && error.name === "AbortError") throw new FlagshipError(`Request timeout after ${timeout}ms`, FlagshipErrorCode.TIMEOUT_ERROR, error);
|
|
156
|
-
if (error instanceof FlagshipError) throw error;
|
|
157
|
-
throw new FlagshipError(`Network error: ${error}`, FlagshipErrorCode.NETWORK_ERROR, error);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
/**
|
|
162
|
-
* Merge `authToken` and `fetchOptions` into a single `RequestInit`.
|
|
163
|
-
*
|
|
164
|
-
* Precedence for the `Authorization` header (highest → lowest):
|
|
165
|
-
* 1. An explicit `Authorization` value inside `fetchOptions.headers`
|
|
166
|
-
* 2. A value derived from `authToken`
|
|
167
|
-
*
|
|
168
|
-
* All other `fetchOptions` fields are spread as-is.
|
|
169
|
-
*/
|
|
170
|
-
function buildFetchOptions(options) {
|
|
171
|
-
const { authToken, fetchOptions = {} } = options;
|
|
172
|
-
if (!authToken) return fetchOptions;
|
|
173
|
-
const existingHeaders = new Headers(fetchOptions.headers);
|
|
174
|
-
if (!existingHeaders.has("Authorization")) existingHeaders.set("Authorization", `Bearer ${authToken}`);
|
|
175
|
-
return {
|
|
176
|
-
...fetchOptions,
|
|
177
|
-
headers: existingHeaders
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
function resolveEndpoint(options) {
|
|
181
|
-
const { appId, endpoint, baseUrl, accountId } = options;
|
|
182
|
-
if (appId && endpoint) throw new Error("Flagship: provide either \"appId\" or \"endpoint\", not both");
|
|
183
|
-
if (!appId && !endpoint) throw new Error("Flagship: either \"appId\" or \"endpoint\" is required");
|
|
184
|
-
if (endpoint) {
|
|
185
|
-
try {
|
|
186
|
-
new URL(endpoint);
|
|
187
|
-
} catch {
|
|
188
|
-
throw new Error(`Flagship: invalid endpoint URL: ${endpoint}`);
|
|
189
|
-
}
|
|
190
|
-
return endpoint;
|
|
191
|
-
}
|
|
192
|
-
if (!accountId) throw new Error("Flagship: \"accountId\" is required when using \"appId\"");
|
|
193
|
-
const resolved = `${(baseUrl || "https://api.cloudflare.com").replace(/\/+$/, "")}/client/v4/accounts/${encodeURIComponent(accountId)}/flagship/apps/${encodeURIComponent(appId)}/evaluate`;
|
|
194
|
-
try {
|
|
195
|
-
new URL(resolved);
|
|
196
|
-
} catch {
|
|
197
|
-
throw new Error(`Flagship: resolved endpoint is not a valid URL: ${resolved}`);
|
|
198
|
-
}
|
|
199
|
-
return resolved;
|
|
200
|
-
}
|
|
201
|
-
//#endregion
|
|
202
|
-
Object.defineProperty(exports, "ContextTransformer", {
|
|
203
|
-
enumerable: true,
|
|
204
|
-
get: function() {
|
|
205
|
-
return ContextTransformer;
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
Object.defineProperty(exports, "FLAGSHIP_DEFAULT_BASE_URL", {
|
|
209
|
-
enumerable: true,
|
|
210
|
-
get: function() {
|
|
211
|
-
return FLAGSHIP_DEFAULT_BASE_URL;
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
Object.defineProperty(exports, "FlagshipClient", {
|
|
215
|
-
enumerable: true,
|
|
216
|
-
get: function() {
|
|
217
|
-
return FlagshipClient;
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
Object.defineProperty(exports, "FlagshipError", {
|
|
221
|
-
enumerable: true,
|
|
222
|
-
get: function() {
|
|
223
|
-
return FlagshipError;
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
Object.defineProperty(exports, "FlagshipErrorCode", {
|
|
227
|
-
enumerable: true,
|
|
228
|
-
get: function() {
|
|
229
|
-
return FlagshipErrorCode;
|
|
230
|
-
}
|
|
231
|
-
});
|