@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 CHANGED
@@ -1,5 +1,165 @@
1
1
  # @cloudflare/flagship
2
2
 
3
- OpenFeature provider SDK for Flagship, Cloudflare's feature flag platform.
3
+ [![npm version](https://img.shields.io/npm/v/@cloudflare/flagship.svg)](https://www.npmjs.com/package/@cloudflare/flagship)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@cloudflare/flagship.svg)](https://www.npmjs.com/package/@cloudflare/flagship)
5
+ [![license](https://img.shields.io/npm/l/@cloudflare/flagship.svg)](https://github.com/cloudflare/flagship/blob/main/LICENSE)
4
6
 
5
- This is a technical preview.
7
+ [OpenFeature](https://openfeature.dev)-compliant provider SDK for [Flagship](https://github.com/cloudflare/flagship), Cloudflare's globally distributed, low-latency feature flag platform.
8
+
9
+ Server-side (Node.js, Cloudflare Workers) and client-side (browser) support via isolated sub-path exports. Tree-shakeable — importing `@cloudflare/flagship/server` never loads `@openfeature/web-sdk` and vice versa.
10
+
11
+ ## Install
12
+
13
+ **Server-side** (Node.js, Cloudflare Workers):
14
+
15
+ ```bash
16
+ npm install @cloudflare/flagship @openfeature/server-sdk
17
+ ```
18
+
19
+ **Client-side** (browser):
20
+
21
+ ```bash
22
+ npm install @cloudflare/flagship @openfeature/web-sdk
23
+ ```
24
+
25
+ ## Quick start — server
26
+
27
+ ```typescript
28
+ import { OpenFeature } from '@openfeature/server-sdk';
29
+ import { FlagshipServerProvider } from '@cloudflare/flagship/server';
30
+
31
+ await OpenFeature.setProviderAndWait(
32
+ new FlagshipServerProvider({ appId: 'your-app-id', accountId: 'your-account-id', authToken: 'your-token' }),
33
+ );
34
+
35
+ const client = OpenFeature.getClient();
36
+ const enabled = await client.getBooleanValue('dark-mode', false, { userId: 'user-123' });
37
+ ```
38
+
39
+ ## Quick start — Cloudflare Workers
40
+
41
+ The recommended approach for Cloudflare Workers. Uses a wrangler binding — no HTTP overhead, no auth tokens needed.
42
+
43
+ Configure in `wrangler.json`:
44
+
45
+ ```jsonc
46
+ {
47
+ "flagship": [{ "binding": "FLAGS", "app_id": "<your-app-id>" }]
48
+ }
49
+ ```
50
+
51
+ ```typescript
52
+ import { OpenFeature } from '@openfeature/server-sdk';
53
+ import { FlagshipServerProvider } from '@cloudflare/flagship/server';
54
+ import type { FlagshipBinding } from '@cloudflare/flagship/server';
55
+
56
+ export default {
57
+ async fetch(request: Request, env: { FLAGS: FlagshipBinding }): Promise<Response> {
58
+ await OpenFeature.setProviderAndWait(new FlagshipServerProvider({ binding: env.FLAGS }));
59
+
60
+ const client = OpenFeature.getClient();
61
+ const darkMode = await client.getBooleanValue('dark-mode', false, {
62
+ targetingKey: new URL(request.url).searchParams.get('userId') ?? 'anonymous',
63
+ });
64
+
65
+ return Response.json({ darkMode });
66
+ },
67
+ };
68
+ ```
69
+
70
+ ## Quick start — HTTP
71
+
72
+ For Workers without a Flagship binding, or non-Workers server environments:
73
+
74
+ ```typescript
75
+ import { OpenFeature } from '@openfeature/server-sdk';
76
+ import { FlagshipServerProvider } from '@cloudflare/flagship/server';
77
+
78
+ let initialized = false;
79
+
80
+ export default {
81
+ async fetch(request: Request): Promise<Response> {
82
+ if (!initialized) {
83
+ await OpenFeature.setProviderAndWait(
84
+ new FlagshipServerProvider({ appId: 'your-app-id', accountId: 'your-account-id', authToken: 'your-token' }),
85
+ );
86
+ initialized = true;
87
+ }
88
+
89
+ const client = OpenFeature.getClient();
90
+ const darkMode = await client.getBooleanValue('dark-mode', false, {
91
+ userId: new URL(request.url).searchParams.get('userId') ?? 'anonymous',
92
+ });
93
+
94
+ return Response.json({ darkMode });
95
+ },
96
+ };
97
+ ```
98
+
99
+ ## Quick start — browser
100
+
101
+ ```typescript
102
+ import { OpenFeature } from '@openfeature/web-sdk';
103
+ import { FlagshipClientProvider } from '@cloudflare/flagship/web';
104
+
105
+ await OpenFeature.setProviderAndWait(
106
+ new FlagshipClientProvider({
107
+ appId: 'your-app-id',
108
+ accountId: 'your-account-id',
109
+ authToken: 'your-token',
110
+ prefetchFlags: ['dark-mode', 'welcome-message'],
111
+ }),
112
+ );
113
+
114
+ await OpenFeature.setContext({ userId: 'user-123', plan: 'premium' });
115
+
116
+ const client = OpenFeature.getClient();
117
+ const darkMode = client.getBooleanValue('dark-mode', false);
118
+ ```
119
+
120
+ ## Features
121
+
122
+ | Feature | Description |
123
+ | --------------------- | ---------------------------------------------------------------------------- |
124
+ | OpenFeature compliant | Implements the CNCF OpenFeature specification |
125
+ | Workers binding | Native wrangler binding support — zero HTTP overhead, no auth tokens |
126
+ | Server + client | Async per-request (server) and sync cache-based (browser) providers |
127
+ | Server providers | `FlagshipServerProvider` works via HTTP or wrangler binding |
128
+ | All flag types | Boolean, string, number, and object (JSON) |
129
+ | Authentication | `authToken` option adds `Authorization: Bearer` to every request (HTTP only) |
130
+ | Logging | `logging` option surfaces fetch errors and cache misses (off by default) |
131
+ | Retries + timeouts | Configurable retry logic with `AbortController`-based timeouts (HTTP only) |
132
+ | Hooks | Built-in `LoggingHook` and `TelemetryHook` |
133
+ | Tree-shakeable | Server and client bundles are fully isolated |
134
+ | TypeScript | Strict types throughout |
135
+
136
+ ## Packages
137
+
138
+ | Export | Description | Peer dependency |
139
+ | ----------------------------- | -------------------------------- | ------------------------- |
140
+ | `@cloudflare/flagship` | Core client, types, errors | None |
141
+ | `@cloudflare/flagship/server` | `FlagshipServerProvider` + hooks | `@openfeature/server-sdk` |
142
+ | `@cloudflare/flagship/web` | `FlagshipClientProvider` | `@openfeature/web-sdk` |
143
+
144
+ ## Documentation
145
+
146
+ - [API reference](../../docs/API.md)
147
+ - [OpenFeature specification](https://openfeature.dev/specification/)
148
+ - [Examples](./examples/)
149
+
150
+ ## Development
151
+
152
+ ```bash
153
+ pnpm install # install dependencies
154
+ pnpm run dev # watch mode
155
+ pnpm run test # run tests
156
+ pnpm run build # build for distribution
157
+ ```
158
+
159
+ ## Contributing
160
+
161
+ Contributions are welcome. Please open an issue first to discuss what you'd like to change. See the [repository](https://github.com/cloudflare/flagship) for more details.
162
+
163
+ ## License
164
+
165
+ [Apache-2.0](../../LICENSE)
@@ -33,8 +33,10 @@ interface FlagshipProviderOptions {
33
33
  */
34
34
  accountId?: string;
35
35
  /**
36
- * Full evaluation endpoint URL. Mutually exclusive with `appId`.
37
- * Use this for local development or custom deployments.
36
+ * Evaluation endpoint. Mutually exclusive with `appId`.
37
+ * Accepts an absolute URL or a root-relative path. Relative paths are
38
+ * only supported by `FlagshipClientProvider` and are resolved against
39
+ * `window.location.origin`.
38
40
  */
39
41
  endpoint?: string;
40
42
  /**
@@ -130,6 +132,64 @@ interface FlagshipEvaluationResponse {
130
132
  */
131
133
  reason: 'TARGETING_MATCH' | 'DEFAULT' | 'DISABLED' | 'SPLIT';
132
134
  }
135
+ /**
136
+ * Shape of the Flagship wrangler binding exposed on `env` in Cloudflare Workers.
137
+ *
138
+ * This is an alias for the `Flagship` class from `@cloudflare/workers-types`.
139
+ * The binding communicates directly with the Flagship service via workerd RPC —
140
+ * no HTTP overhead, no auth tokens required. Configure it in `wrangler.json`:
141
+ *
142
+ * ```jsonc
143
+ * {
144
+ * "flagship": [
145
+ * { "binding": "FLAGS", "app_id": "<your-app-id>" }
146
+ * ]
147
+ * }
148
+ * ```
149
+ */
150
+ type FlagshipBinding = Flagship;
151
+ /**
152
+ * Evaluation details returned by the Flagship binding's `*Details` methods.
153
+ * Contains the resolved value along with metadata about why that value was
154
+ * chosen.
155
+ *
156
+ * This is an alias for the `FlagshipEvaluationDetails` interface from
157
+ * `@cloudflare/workers-types`.
158
+ */
159
+ type FlagshipBindingEvaluationDetails<T> = FlagshipEvaluationDetails<T>;
160
+ /**
161
+ * Configuration for `FlagshipServerProvider` when using a wrangler binding.
162
+ *
163
+ * In this mode the provider delegates all evaluations to the Flagship binding
164
+ * on `env`, bypassing HTTP entirely. No `appId`, `accountId`, or `authToken`
165
+ * is required — the binding handles authentication and routing.
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * new FlagshipServerProvider({ binding: env.FLAGS })
170
+ * ```
171
+ */
172
+ interface FlagshipBindingProviderOptions {
173
+ /** The Flagship binding from the Worker's `env` object. */
174
+ binding: FlagshipBinding;
175
+ /**
176
+ * Enable SDK-level logging.
177
+ * @default false
178
+ */
179
+ logging?: boolean;
180
+ }
181
+ /**
182
+ * Options accepted by `FlagshipServerProvider`.
183
+ *
184
+ * Provide **either** HTTP configuration (`appId`/`endpoint` + credentials) **or**
185
+ * a wrangler `binding` — never both. The provider detects which mode to use
186
+ * based on the presence of the `binding` field.
187
+ */
188
+ type FlagshipServerProviderOptions = FlagshipProviderOptions | FlagshipBindingProviderOptions;
189
+ /**
190
+ * Type guard: returns `true` when the options use binding mode.
191
+ */
192
+ declare function isBindingOptions(options: FlagshipServerProviderOptions): options is FlagshipBindingProviderOptions;
133
193
  /**
134
194
  * Internal error codes produced by `FlagshipClient`.
135
195
  * These are mapped to OpenFeature `ErrorCode` values by the providers.
@@ -230,4 +290,4 @@ declare class FlagshipClient {
230
290
  private fetchWithTimeout;
231
291
  }
232
292
  //#endregion
233
- export { FlagshipClientProviderOptions as a, FlagshipEvaluationResponse as c, FLAGSHIP_DEFAULT_BASE_URL as i, FlagshipProviderOptions as l, ContextTransformer as n, FlagshipError as o, CachedFlag as r, FlagshipErrorCode as s, FlagshipClient as t };
293
+ export { FlagshipBinding as a, FlagshipClientProviderOptions as c, FlagshipEvaluationResponse as d, FlagshipProviderOptions as f, FLAGSHIP_DEFAULT_BASE_URL as i, FlagshipError as l, isBindingOptions as m, ContextTransformer as n, FlagshipBindingEvaluationDetails as o, FlagshipServerProviderOptions as p, CachedFlag as r, FlagshipBindingProviderOptions as s, FlagshipClient as t, FlagshipErrorCode as u };
@@ -33,8 +33,10 @@ interface FlagshipProviderOptions {
33
33
  */
34
34
  accountId?: string;
35
35
  /**
36
- * Full evaluation endpoint URL. Mutually exclusive with `appId`.
37
- * Use this for local development or custom deployments.
36
+ * Evaluation endpoint. Mutually exclusive with `appId`.
37
+ * Accepts an absolute URL or a root-relative path. Relative paths are
38
+ * only supported by `FlagshipClientProvider` and are resolved against
39
+ * `window.location.origin`.
38
40
  */
39
41
  endpoint?: string;
40
42
  /**
@@ -130,6 +132,64 @@ interface FlagshipEvaluationResponse {
130
132
  */
131
133
  reason: 'TARGETING_MATCH' | 'DEFAULT' | 'DISABLED' | 'SPLIT';
132
134
  }
135
+ /**
136
+ * Shape of the Flagship wrangler binding exposed on `env` in Cloudflare Workers.
137
+ *
138
+ * This is an alias for the `Flagship` class from `@cloudflare/workers-types`.
139
+ * The binding communicates directly with the Flagship service via workerd RPC —
140
+ * no HTTP overhead, no auth tokens required. Configure it in `wrangler.json`:
141
+ *
142
+ * ```jsonc
143
+ * {
144
+ * "flagship": [
145
+ * { "binding": "FLAGS", "app_id": "<your-app-id>" }
146
+ * ]
147
+ * }
148
+ * ```
149
+ */
150
+ type FlagshipBinding = Flagship;
151
+ /**
152
+ * Evaluation details returned by the Flagship binding's `*Details` methods.
153
+ * Contains the resolved value along with metadata about why that value was
154
+ * chosen.
155
+ *
156
+ * This is an alias for the `FlagshipEvaluationDetails` interface from
157
+ * `@cloudflare/workers-types`.
158
+ */
159
+ type FlagshipBindingEvaluationDetails<T> = FlagshipEvaluationDetails<T>;
160
+ /**
161
+ * Configuration for `FlagshipServerProvider` when using a wrangler binding.
162
+ *
163
+ * In this mode the provider delegates all evaluations to the Flagship binding
164
+ * on `env`, bypassing HTTP entirely. No `appId`, `accountId`, or `authToken`
165
+ * is required — the binding handles authentication and routing.
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * new FlagshipServerProvider({ binding: env.FLAGS })
170
+ * ```
171
+ */
172
+ interface FlagshipBindingProviderOptions {
173
+ /** The Flagship binding from the Worker's `env` object. */
174
+ binding: FlagshipBinding;
175
+ /**
176
+ * Enable SDK-level logging.
177
+ * @default false
178
+ */
179
+ logging?: boolean;
180
+ }
181
+ /**
182
+ * Options accepted by `FlagshipServerProvider`.
183
+ *
184
+ * Provide **either** HTTP configuration (`appId`/`endpoint` + credentials) **or**
185
+ * a wrangler `binding` — never both. The provider detects which mode to use
186
+ * based on the presence of the `binding` field.
187
+ */
188
+ type FlagshipServerProviderOptions = FlagshipProviderOptions | FlagshipBindingProviderOptions;
189
+ /**
190
+ * Type guard: returns `true` when the options use binding mode.
191
+ */
192
+ declare function isBindingOptions(options: FlagshipServerProviderOptions): options is FlagshipBindingProviderOptions;
133
193
  /**
134
194
  * Internal error codes produced by `FlagshipClient`.
135
195
  * These are mapped to OpenFeature `ErrorCode` values by the providers.
@@ -230,4 +290,4 @@ declare class FlagshipClient {
230
290
  private fetchWithTimeout;
231
291
  }
232
292
  //#endregion
233
- export { FlagshipClientProviderOptions as a, FlagshipEvaluationResponse as c, FLAGSHIP_DEFAULT_BASE_URL as i, FlagshipProviderOptions as l, ContextTransformer as n, FlagshipError as o, CachedFlag as r, FlagshipErrorCode as s, FlagshipClient as t };
293
+ export { FlagshipBinding as a, FlagshipClientProviderOptions as c, FlagshipEvaluationResponse as d, FlagshipProviderOptions as f, FLAGSHIP_DEFAULT_BASE_URL as i, FlagshipError as l, isBindingOptions as m, ContextTransformer as n, FlagshipBindingEvaluationDetails as o, FlagshipServerProviderOptions as p, CachedFlag as r, FlagshipBindingProviderOptions as s, FlagshipClient as t, FlagshipErrorCode as u };
package/dist/index.cjs CHANGED
@@ -1,7 +1 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_src = require("./src-De-abNIr.cjs");
3
- exports.ContextTransformer = require_src.ContextTransformer;
4
- exports.FLAGSHIP_DEFAULT_BASE_URL = require_src.FLAGSHIP_DEFAULT_BASE_URL;
5
- exports.FlagshipClient = require_src.FlagshipClient;
6
- exports.FlagshipError = require_src.FlagshipError;
7
- exports.FlagshipErrorCode = require_src.FlagshipErrorCode;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./src-QEIBSid3.cjs`);exports.ContextTransformer=e.n,exports.FLAGSHIP_DEFAULT_BASE_URL=e.r,exports.FlagshipClient=e.t,exports.FlagshipError=e.i,exports.FlagshipErrorCode=e.a,exports.isBindingOptions=e.o;
package/dist/index.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as FlagshipClientProviderOptions, c as FlagshipEvaluationResponse, i as FLAGSHIP_DEFAULT_BASE_URL, l as FlagshipProviderOptions, n as ContextTransformer, o as FlagshipError, r as CachedFlag, s as FlagshipErrorCode, t as FlagshipClient } from "./index-D8YLMfBG.cjs";
2
- export { CachedFlag, ContextTransformer, FLAGSHIP_DEFAULT_BASE_URL, FlagshipClient, FlagshipClientProviderOptions, FlagshipError, FlagshipErrorCode, FlagshipEvaluationResponse, FlagshipProviderOptions };
1
+ import { a as FlagshipBinding, c as FlagshipClientProviderOptions, d as FlagshipEvaluationResponse, f as FlagshipProviderOptions, i as FLAGSHIP_DEFAULT_BASE_URL, l as FlagshipError, m as isBindingOptions, n as ContextTransformer, o as FlagshipBindingEvaluationDetails, p as FlagshipServerProviderOptions, r as CachedFlag, s as FlagshipBindingProviderOptions, t as FlagshipClient, u as FlagshipErrorCode } from "./index-BsvVosek.cjs";
2
+ export { CachedFlag, ContextTransformer, FLAGSHIP_DEFAULT_BASE_URL, FlagshipBinding, FlagshipBindingEvaluationDetails, FlagshipBindingProviderOptions, FlagshipClient, FlagshipClientProviderOptions, FlagshipError, FlagshipErrorCode, FlagshipEvaluationResponse, FlagshipProviderOptions, FlagshipServerProviderOptions, isBindingOptions };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as FlagshipClientProviderOptions, c as FlagshipEvaluationResponse, i as FLAGSHIP_DEFAULT_BASE_URL, l as FlagshipProviderOptions, n as ContextTransformer, o as FlagshipError, r as CachedFlag, s as FlagshipErrorCode, t as FlagshipClient } from "./index-C_sW3e_7.mjs";
2
- export { CachedFlag, ContextTransformer, FLAGSHIP_DEFAULT_BASE_URL, FlagshipClient, FlagshipClientProviderOptions, FlagshipError, FlagshipErrorCode, FlagshipEvaluationResponse, FlagshipProviderOptions };
1
+ import { a as FlagshipBinding, c as FlagshipClientProviderOptions, d as FlagshipEvaluationResponse, f as FlagshipProviderOptions, i as FLAGSHIP_DEFAULT_BASE_URL, l as FlagshipError, m as isBindingOptions, n as ContextTransformer, o as FlagshipBindingEvaluationDetails, p as FlagshipServerProviderOptions, r as CachedFlag, s as FlagshipBindingProviderOptions, t as FlagshipClient, u as FlagshipErrorCode } from "./index-BMGtqsWU.mjs";
2
+ export { CachedFlag, ContextTransformer, FLAGSHIP_DEFAULT_BASE_URL, FlagshipBinding, FlagshipBindingEvaluationDetails, FlagshipBindingProviderOptions, FlagshipClient, FlagshipClientProviderOptions, FlagshipError, FlagshipErrorCode, FlagshipEvaluationResponse, FlagshipProviderOptions, FlagshipServerProviderOptions, isBindingOptions };
package/dist/index.mjs CHANGED
@@ -1,2 +1 @@
1
- import { a as FlagshipErrorCode, i as FlagshipError, n as ContextTransformer, r as FLAGSHIP_DEFAULT_BASE_URL, t as FlagshipClient } from "./src-CiVDWmng.mjs";
2
- export { ContextTransformer, FLAGSHIP_DEFAULT_BASE_URL, FlagshipClient, FlagshipError, FlagshipErrorCode };
1
+ import{a as e,i as t,n,o as r,r as i,t as a}from"./src-DW2QohY9.mjs";export{n as ContextTransformer,i as FLAGSHIP_DEFAULT_BASE_URL,a as FlagshipClient,t as FlagshipError,e as FlagshipErrorCode,r as isBindingOptions};
package/dist/server.cjs CHANGED
@@ -1,298 +1 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_src = require("./src-De-abNIr.cjs");
3
- let _openfeature_server_sdk = require("@openfeature/server-sdk");
4
- //#region src/server-provider.ts
5
- const _noop = () => {};
6
- /**
7
- * OpenFeature provider for Flagship (server-side / dynamic context).
8
- *
9
- * Use this provider with `@openfeature/server-sdk` for Node.js,
10
- * Cloudflare Workers, and other server-side JavaScript environments.
11
- *
12
- * @example
13
- * ```typescript
14
- * import { OpenFeature } from '@openfeature/server-sdk';
15
- * import { FlagshipServerProvider } from '@cloudflare/flagship/server';
16
- *
17
- * await OpenFeature.setProviderAndWait(
18
- * new FlagshipServerProvider({
19
- * appId: 'app-abc123',
20
- * accountId: 'your-account-id',
21
- * authToken: 'your-token',
22
- * })
23
- * );
24
- *
25
- * const client = OpenFeature.getClient();
26
- * const value = await client.getBooleanValue('my-flag', false, {
27
- * targetingKey: 'user-123',
28
- * email: 'user@example.com',
29
- * });
30
- * ```
31
- */
32
- var FlagshipServerProvider = class {
33
- constructor(options) {
34
- this.runsOn = "server";
35
- this.events = new _openfeature_server_sdk.OpenFeatureEventEmitter();
36
- this.currentStatus = _openfeature_server_sdk.ProviderStatus.NOT_READY;
37
- this.metadata = { name: "Flagship Server Provider" };
38
- this.client = new require_src.FlagshipClient(options);
39
- this.logging = options.logging ?? false;
40
- }
41
- /**
42
- * Returns the provided logger when logging is enabled, or a no-op logger
43
- * when `logging` is `false`. Using this in every resolve method ensures
44
- * the SDK produces no console output unless the caller opts in.
45
- */
46
- logger(logger) {
47
- if (this.logging) return logger;
48
- return {
49
- debug: _noop,
50
- info: _noop,
51
- warn: _noop,
52
- error: _noop
53
- };
54
- }
55
- /**
56
- * Probes the evaluation endpoint with a health-check request. A 404 response
57
- * is treated as success — it means the endpoint is reachable but the
58
- * health-check flag simply doesn't exist, which is expected. Any network
59
- * failure or timeout sets the status to ERROR.
60
- */
61
- async initialize(_context) {
62
- try {
63
- await this.client.evaluate("_flagship_health_check", {});
64
- this.currentStatus = _openfeature_server_sdk.ProviderStatus.READY;
65
- this.events.emit(_openfeature_server_sdk.ProviderEvents.Ready);
66
- } catch (error) {
67
- if (error instanceof require_src.FlagshipError && error.cause instanceof Response && error.cause.status === 404) {
68
- this.currentStatus = _openfeature_server_sdk.ProviderStatus.READY;
69
- this.events.emit(_openfeature_server_sdk.ProviderEvents.Ready);
70
- return;
71
- }
72
- this.currentStatus = _openfeature_server_sdk.ProviderStatus.ERROR;
73
- this.events.emit(_openfeature_server_sdk.ProviderEvents.Error, { message: error instanceof Error ? error.message : String(error) });
74
- }
75
- }
76
- async onClose() {
77
- this.currentStatus = _openfeature_server_sdk.ProviderStatus.NOT_READY;
78
- }
79
- get status() {
80
- return this.currentStatus;
81
- }
82
- async resolveBooleanEvaluation(flagKey, defaultValue, context, logger) {
83
- return this.resolve(flagKey, defaultValue, context, "boolean", logger);
84
- }
85
- async resolveStringEvaluation(flagKey, defaultValue, context, logger) {
86
- return this.resolve(flagKey, defaultValue, context, "string", logger);
87
- }
88
- async resolveNumberEvaluation(flagKey, defaultValue, context, logger) {
89
- return this.resolve(flagKey, defaultValue, context, "number", logger);
90
- }
91
- async resolveObjectEvaluation(flagKey, defaultValue, context, logger) {
92
- return this.resolve(flagKey, defaultValue, context, "object", logger);
93
- }
94
- async resolve(flagKey, defaultValue, context, expectedType, logger) {
95
- const log = this.logger(logger);
96
- try {
97
- log.debug(`[Flagship] Evaluating flag "${flagKey}" (expected: ${expectedType})`);
98
- const result = await this.client.evaluate(flagKey, context);
99
- const actualType = this.getValueType(result.value);
100
- if (actualType !== expectedType) {
101
- const msg = `Flag "${flagKey}" type mismatch: expected ${expectedType}, got ${actualType}`;
102
- log.warn(`[Flagship] ${msg}`);
103
- return {
104
- value: defaultValue,
105
- errorCode: _openfeature_server_sdk.ErrorCode.TYPE_MISMATCH,
106
- errorMessage: msg,
107
- reason: "ERROR"
108
- };
109
- }
110
- log.debug(`[Flagship] Flag "${flagKey}" resolved: value=${String(result.value)} reason=${result.reason} variant=${result.variant}`);
111
- return {
112
- value: result.value,
113
- variant: result.variant,
114
- reason: result.reason,
115
- flagMetadata: {}
116
- };
117
- } catch (error) {
118
- return this.handleError(flagKey, defaultValue, error, log);
119
- }
120
- }
121
- /**
122
- * Maps a runtime value to one of the four OpenFeature flag types.
123
- * `null` maps to `'object'` (typeof null === 'object'), treating it as a
124
- * JSON null which belongs to the object/structure category.
125
- */
126
- getValueType(value) {
127
- if (typeof value === "boolean") return "boolean";
128
- if (typeof value === "string") return "string";
129
- if (typeof value === "number") return "number";
130
- return "object";
131
- }
132
- handleError(flagKey, defaultValue, error, logger) {
133
- if (error instanceof require_src.FlagshipError) {
134
- let errorCode;
135
- switch (error.code) {
136
- case require_src.FlagshipErrorCode.NETWORK_ERROR:
137
- errorCode = error.cause instanceof Response && error.cause.status === 404 ? _openfeature_server_sdk.ErrorCode.FLAG_NOT_FOUND : _openfeature_server_sdk.ErrorCode.GENERAL;
138
- break;
139
- case require_src.FlagshipErrorCode.TIMEOUT_ERROR:
140
- errorCode = _openfeature_server_sdk.ErrorCode.GENERAL;
141
- break;
142
- case require_src.FlagshipErrorCode.PARSE_ERROR:
143
- errorCode = _openfeature_server_sdk.ErrorCode.PARSE_ERROR;
144
- break;
145
- case require_src.FlagshipErrorCode.INVALID_CONTEXT:
146
- errorCode = _openfeature_server_sdk.ErrorCode.INVALID_CONTEXT;
147
- break;
148
- default: errorCode = _openfeature_server_sdk.ErrorCode.GENERAL;
149
- }
150
- logger.error(`[Flagship] Flag "${flagKey}" evaluation failed (${errorCode}): ${error.message}`);
151
- return {
152
- value: defaultValue,
153
- errorCode,
154
- errorMessage: error.message,
155
- reason: "ERROR"
156
- };
157
- }
158
- const errorMessage = String(error);
159
- logger.error(`[Flagship] Flag "${flagKey}" evaluation failed (GENERAL): ${errorMessage}`);
160
- return {
161
- value: defaultValue,
162
- errorCode: _openfeature_server_sdk.ErrorCode.GENERAL,
163
- errorMessage,
164
- reason: "ERROR"
165
- };
166
- }
167
- };
168
- //#endregion
169
- //#region src/hooks/logging-hook.ts
170
- /**
171
- * Logging hook for debugging flag evaluations
172
- *
173
- * @example
174
- * ```typescript
175
- * import { OpenFeature } from '@openfeature/server-sdk';
176
- * import { FlagshipServerProvider, LoggingHook } from '@cloudflare/flagship/server';
177
- *
178
- * const provider = new FlagshipServerProvider({ appId: 'your-app-id', accountId: 'your-account-id' });
179
- * await OpenFeature.setProviderAndWait(provider);
180
- *
181
- * // Add logging hook
182
- * OpenFeature.addHooks(new LoggingHook());
183
- * ```
184
- */
185
- var LoggingHook = class {
186
- constructor(logger = console.log) {
187
- this.logger = logger;
188
- }
189
- before(hookContext, _hookHints) {
190
- this.logger(`[Flagship] Evaluating flag: ${hookContext.flagKey}`, {
191
- defaultValue: hookContext.defaultValue,
192
- context: hookContext.context
193
- });
194
- }
195
- after(hookContext, evaluationDetails, _hookHints) {
196
- this.logger(`[Flagship] Flag ${hookContext.flagKey} evaluated`, {
197
- value: evaluationDetails.value,
198
- reason: evaluationDetails.reason,
199
- variant: evaluationDetails.variant,
200
- errorCode: evaluationDetails.errorCode
201
- });
202
- }
203
- error(hookContext, error, _hookHints) {
204
- const message = error instanceof Error ? error.message : String(error);
205
- this.logger(`[Flagship] Error evaluating flag ${hookContext.flagKey}:`, message);
206
- }
207
- finally(_hookContext, _evaluationDetails, _hookHints) {}
208
- };
209
- //#endregion
210
- //#region src/hooks/telemetry-hook.ts
211
- /**
212
- * Telemetry hook for tracking flag evaluations
213
- *
214
- * @example
215
- * ```typescript
216
- * import { OpenFeature } from '@openfeature/server-sdk';
217
- * import { FlagshipServerProvider, TelemetryHook } from '@cloudflare/flagship/server';
218
- *
219
- * const telemetryHook = new TelemetryHook((event) => {
220
- * // Send to your analytics service
221
- * analytics.track('flag_evaluated', event);
222
- * });
223
- *
224
- * OpenFeature.addHooks(telemetryHook);
225
- * ```
226
- */
227
- var TelemetryHook = class {
228
- constructor(onEvent) {
229
- this.startTimes = /* @__PURE__ */ new Map();
230
- this.contextKeys = /* @__PURE__ */ new WeakMap();
231
- this.hints = /* @__PURE__ */ new WeakMap();
232
- this.onEvent = onEvent;
233
- }
234
- before(hookContext, hookHints) {
235
- const now = Date.now();
236
- const key = `${hookContext.flagKey}-${now}-${Math.random()}`;
237
- this.startTimes.set(key, now);
238
- this.contextKeys.set(hookContext, key);
239
- if (hookHints !== void 0) this.hints.set(hookContext, hookHints);
240
- }
241
- after(hookContext, evaluationDetails, _hookHints) {
242
- const telemetryKey = this.contextKeys.get(hookContext);
243
- const startTime = telemetryKey ? this.startTimes.get(telemetryKey) : void 0;
244
- const duration = startTime !== void 0 ? Date.now() - startTime : void 0;
245
- if (telemetryKey !== void 0) {
246
- this.startTimes.delete(telemetryKey);
247
- this.contextKeys.delete(hookContext);
248
- }
249
- this.onEvent({
250
- type: "evaluation",
251
- flagKey: hookContext.flagKey,
252
- timestamp: Date.now(),
253
- duration,
254
- value: evaluationDetails.value,
255
- reason: evaluationDetails.reason,
256
- variant: evaluationDetails.variant,
257
- errorCode: evaluationDetails.errorCode,
258
- context: hookContext.context,
259
- hints: this.hints.get(hookContext)
260
- });
261
- }
262
- error(hookContext, error, _hookHints) {
263
- const telemetryKey = this.contextKeys.get(hookContext);
264
- const startTime = telemetryKey ? this.startTimes.get(telemetryKey) : void 0;
265
- const duration = startTime !== void 0 ? Date.now() - startTime : void 0;
266
- if (telemetryKey !== void 0) {
267
- this.startTimes.delete(telemetryKey);
268
- this.contextKeys.delete(hookContext);
269
- }
270
- const errorMessage = error instanceof Error ? error.message : String(error);
271
- this.onEvent({
272
- type: "error",
273
- flagKey: hookContext.flagKey,
274
- timestamp: Date.now(),
275
- duration,
276
- errorMessage,
277
- context: hookContext.context,
278
- hints: this.hints.get(hookContext)
279
- });
280
- }
281
- finally(hookContext, _evaluationDetails, _hookHints) {
282
- const telemetryKey = this.contextKeys.get(hookContext);
283
- if (telemetryKey !== void 0) {
284
- this.startTimes.delete(telemetryKey);
285
- this.contextKeys.delete(hookContext);
286
- }
287
- this.hints.delete(hookContext);
288
- }
289
- };
290
- //#endregion
291
- exports.ContextTransformer = require_src.ContextTransformer;
292
- exports.FLAGSHIP_DEFAULT_BASE_URL = require_src.FLAGSHIP_DEFAULT_BASE_URL;
293
- exports.FlagshipClient = require_src.FlagshipClient;
294
- exports.FlagshipError = require_src.FlagshipError;
295
- exports.FlagshipErrorCode = require_src.FlagshipErrorCode;
296
- exports.FlagshipServerProvider = FlagshipServerProvider;
297
- exports.LoggingHook = LoggingHook;
298
- exports.TelemetryHook = TelemetryHook;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./src-QEIBSid3.cjs`);let t=require(`@openfeature/server-sdk`);const n=()=>{},r=[`appId`,`endpoint`,`accountId`,`authToken`,`baseUrl`,`fetchOptions`,`timeout`,`retries`,`retryDelay`];var i=class{constructor(n){if(this.runsOn=`server`,this.events=new t.OpenFeatureEventEmitter,this.currentStatus=t.ProviderStatus.NOT_READY,this.metadata={name:`Flagship Server Provider`},this.logging=n.logging??!1,e.o(n)){let e=r.filter(e=>e in n);if(e.length>0)throw Error(`Flagship: when using a binding, the following HTTP-specific options must not be provided: ${e.join(`, `)}. Provide either a binding or HTTP configuration, not both.`);this.binding=n.binding,this.client=void 0,this.resolve=this.resolveViaBinding.bind(this)}else this.client=new e.t(n),this.binding=void 0,this.resolve=this.resolveViaHttp.bind(this)}logger(e){return this.logging?e:{debug:n,info:n,warn:n,error:n}}async initialize(n){if(this.binding){this.currentStatus=t.ProviderStatus.READY,this.events.emit(t.ProviderEvents.Ready);return}try{await this.client.evaluate(`_flagship_health_check`,{}),this.currentStatus=t.ProviderStatus.READY,this.events.emit(t.ProviderEvents.Ready)}catch(n){if(n instanceof e.i&&n.cause instanceof Response&&n.cause.status===404){this.currentStatus=t.ProviderStatus.READY,this.events.emit(t.ProviderEvents.Ready);return}this.currentStatus=t.ProviderStatus.ERROR,this.events.emit(t.ProviderEvents.Error,{message:n instanceof Error?n.message:String(n)})}}async onClose(){this.currentStatus=t.ProviderStatus.NOT_READY}get status(){return this.currentStatus}async resolveBooleanEvaluation(e,t,n,r){return this.resolve(e,t,n,`boolean`,r)}async resolveStringEvaluation(e,t,n,r){return this.resolve(e,t,n,`string`,r)}async resolveNumberEvaluation(e,t,n,r){return this.resolve(e,t,n,`number`,r)}async resolveObjectEvaluation(e,t,n,r){return this.resolve(e,t,n,`object`,r)}async resolveViaHttp(e,n,r,i,o){let s=this.logger(o);try{s.debug(`[Flagship] Evaluating flag "${e}" (expected: ${i})`);let o=await this.client.evaluate(e,r),c=a(o.value);if(c!==i){let r=`Flag "${e}" type mismatch: expected ${i}, got ${c}`;return s.warn(`[Flagship] ${r}`),{value:n,errorCode:t.ErrorCode.TYPE_MISMATCH,errorMessage:r,reason:`ERROR`}}return s.debug(`[Flagship] Flag "${e}" resolved: value=${String(o.value)} reason=${o.reason} variant=${o.variant}`),{value:o.value,variant:o.variant,reason:o.reason,flagMetadata:{}}}catch(t){return this.handleHttpError(e,n,t,s)}}handleHttpError(n,r,i,a){if(i instanceof e.i){let o;switch(i.code){case e.a.NETWORK_ERROR:o=i.cause instanceof Response&&i.cause.status===404?t.ErrorCode.FLAG_NOT_FOUND:t.ErrorCode.GENERAL;break;case e.a.TIMEOUT_ERROR:o=t.ErrorCode.GENERAL;break;case e.a.PARSE_ERROR:o=t.ErrorCode.PARSE_ERROR;break;case e.a.INVALID_CONTEXT:o=t.ErrorCode.INVALID_CONTEXT;break;default:o=t.ErrorCode.GENERAL}return a.error(`[Flagship] Flag "${n}" evaluation failed (${o}): ${i.message}`),{value:r,errorCode:o,errorMessage:i.message,reason:`ERROR`}}let o=String(i);return a.error(`[Flagship] Flag "${n}" evaluation failed (GENERAL): ${o}`),{value:r,errorCode:t.ErrorCode.GENERAL,errorMessage:o,reason:`ERROR`}}async resolveViaBinding(e,n,r,i,c){let l=this.logger(c);try{l.debug(`[Flagship] Evaluating flag "${e}" via binding (expected: ${i})`);let c=o(r,l),u=await this.evaluateBinding(e,n,i,c);if(u.errorCode){let t=s(u.errorCode),r=u.errorMessage??`Binding error: ${u.errorCode}`;return l.error(`[Flagship] Flag "${e}" evaluation failed (${t}): ${r}`),{value:n,errorCode:t,errorMessage:r,reason:u.reason??`ERROR`}}let d=a(u.value);if(d!==i){let r=`Flag "${e}" type mismatch: expected ${i}, got ${d}`;return l.warn(`[Flagship] ${r}`),{value:n,errorCode:t.ErrorCode.TYPE_MISMATCH,errorMessage:r,reason:`ERROR`}}return l.debug(`[Flagship] Flag "${e}" resolved via binding: value=${String(u.value)} reason=${u.reason} variant=${u.variant}`),{value:u.value,variant:u.variant,reason:u.reason,flagMetadata:{}}}catch(r){let i=r instanceof Error?r.message:String(r);return l.error(`[Flagship] Flag "${e}" binding evaluation failed (GENERAL): ${i}`),{value:n,errorCode:t.ErrorCode.GENERAL,errorMessage:i,reason:`ERROR`}}}async evaluateBinding(e,t,n,r){let i=this.binding;switch(n){case`boolean`:return i.getBooleanDetails(e,t,r);case`string`:return i.getStringDetails(e,t,r);case`number`:return i.getNumberDetails(e,t,r);case`object`:return i.getObjectDetails(e,t,r)}}};function a(e){return typeof e==`boolean`?`boolean`:typeof e==`string`?`string`:typeof e==`number`?`number`:`object`}function o(e,t){let n={};for(let[r,i]of Object.entries(e))if(i!=null){if(i instanceof Date){n[r]=i.toISOString();continue}if(typeof i==`string`||typeof i==`number`||typeof i==`boolean`){n[r]=i;continue}if(typeof i==`object`){t.warn(`[Flagship] Context key "${r}" is a complex object/array and cannot be passed to the binding. This value will be ignored.`);continue}}return n}function s(e){switch(e){case`FLAG_NOT_FOUND`:return t.ErrorCode.FLAG_NOT_FOUND;case`PARSE_ERROR`:return t.ErrorCode.PARSE_ERROR;case`TYPE_MISMATCH`:return t.ErrorCode.TYPE_MISMATCH;case`INVALID_CONTEXT`:return t.ErrorCode.INVALID_CONTEXT;default:return t.ErrorCode.GENERAL}}var c=class{constructor(e=console.log){this.logger=e}before(e,t){this.logger(`[Flagship] Evaluating flag: ${e.flagKey}`,{defaultValue:e.defaultValue,context:e.context})}after(e,t,n){this.logger(`[Flagship] Flag ${e.flagKey} evaluated`,{value:t.value,reason:t.reason,variant:t.variant,errorCode:t.errorCode})}error(e,t,n){let r=t instanceof Error?t.message:String(t);this.logger(`[Flagship] Error evaluating flag ${e.flagKey}:`,r)}finally(e,t,n){}},l=class{constructor(e){this.startTimes=new Map,this.contextKeys=new WeakMap,this.hints=new WeakMap,this.onEvent=e}before(e,t){let n=Date.now(),r=`${e.flagKey}-${n}-${Math.random()}`;this.startTimes.set(r,n),this.contextKeys.set(e,r),t!==void 0&&this.hints.set(e,t)}after(e,t,n){let r=this.contextKeys.get(e),i=r?this.startTimes.get(r):void 0,a=i===void 0?void 0:Date.now()-i;r!==void 0&&(this.startTimes.delete(r),this.contextKeys.delete(e)),this.onEvent({type:`evaluation`,flagKey:e.flagKey,timestamp:Date.now(),duration:a,value:t.value,reason:t.reason,variant:t.variant,errorCode:t.errorCode,context:e.context,hints:this.hints.get(e)})}error(e,t,n){let r=this.contextKeys.get(e),i=r?this.startTimes.get(r):void 0,a=i===void 0?void 0:Date.now()-i;r!==void 0&&(this.startTimes.delete(r),this.contextKeys.delete(e));let o=t instanceof Error?t.message:String(t);this.onEvent({type:`error`,flagKey:e.flagKey,timestamp:Date.now(),duration:a,errorMessage:o,context:e.context,hints:this.hints.get(e)})}finally(e,t,n){let r=this.contextKeys.get(e);r!==void 0&&(this.startTimes.delete(r),this.contextKeys.delete(e)),this.hints.delete(e)}};exports.ContextTransformer=e.n,exports.FLAGSHIP_DEFAULT_BASE_URL=e.r,exports.FlagshipClient=e.t,exports.FlagshipError=e.i,exports.FlagshipErrorCode=e.a,exports.FlagshipServerProvider=i,exports.LoggingHook=c,exports.TelemetryHook=l,exports.isBindingOptions=e.o;