@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/dist/server.d.cts CHANGED
@@ -1,14 +1,20 @@
1
- import { c as FlagshipEvaluationResponse, i as FLAGSHIP_DEFAULT_BASE_URL, l as FlagshipProviderOptions, n as ContextTransformer, o as FlagshipError, s as FlagshipErrorCode, t as FlagshipClient } from "./index-D8YLMfBG.cjs";
1
+ import { a as FlagshipBinding, 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, s as FlagshipBindingProviderOptions, t as FlagshipClient, u as FlagshipErrorCode } from "./index-BsvVosek.cjs";
2
2
  import { BeforeHookContext, ErrorCode, EvaluationContext, EvaluationDetails, FlagValue, Hook, HookContext, HookHints, JsonValue, Logger, OpenFeatureEventEmitter, Provider, ProviderMetadata, ProviderStatus, ResolutionDetails } from "@openfeature/server-sdk";
3
3
 
4
4
  //#region src/server-provider.d.ts
5
5
  /**
6
6
  * OpenFeature provider for Flagship (server-side / dynamic context).
7
7
  *
8
- * Use this provider with `@openfeature/server-sdk` for Node.js,
9
- * Cloudflare Workers, and other server-side JavaScript environments.
8
+ * Supports two modes of operation:
10
9
  *
11
- * @example
10
+ * **HTTP mode** — evaluates flags via HTTP requests to the Flagship API.
11
+ * Requires `appId`/`endpoint`, `accountId`, and optionally `authToken`.
12
+ *
13
+ * **Binding mode** — evaluates flags via a Cloudflare Workers wrangler binding.
14
+ * Only requires the `binding` field (the `Flagship` object from `env`). No HTTP
15
+ * overhead, no auth tokens. This is the recommended approach for Workers.
16
+ *
17
+ * @example HTTP mode
12
18
  * ```typescript
13
19
  * import { OpenFeature } from '@openfeature/server-sdk';
14
20
  * import { FlagshipServerProvider } from '@cloudflare/flagship/server';
@@ -20,22 +26,37 @@ import { BeforeHookContext, ErrorCode, EvaluationContext, EvaluationDetails, Fla
20
26
  * authToken: 'your-token',
21
27
  * })
22
28
  * );
29
+ * ```
23
30
  *
24
- * const client = OpenFeature.getClient();
25
- * const value = await client.getBooleanValue('my-flag', false, {
26
- * targetingKey: 'user-123',
27
- * email: 'user@example.com',
28
- * });
31
+ * @example Binding mode (Cloudflare Workers)
32
+ * ```typescript
33
+ * import { OpenFeature } from '@openfeature/server-sdk';
34
+ * import { FlagshipServerProvider } from '@cloudflare/flagship/server';
35
+ *
36
+ * export default {
37
+ * async fetch(request: Request, env: { FLAGS: FlagshipBinding }) {
38
+ * await OpenFeature.setProviderAndWait(
39
+ * new FlagshipServerProvider({ binding: env.FLAGS })
40
+ * );
41
+ * const client = OpenFeature.getClient();
42
+ * const value = await client.getBooleanValue('my-flag', false);
43
+ * return new Response(JSON.stringify({ value }));
44
+ * },
45
+ * };
29
46
  * ```
30
47
  */
31
48
  declare class FlagshipServerProvider implements Provider {
32
49
  readonly metadata: ProviderMetadata;
33
50
  readonly runsOn: "server";
34
51
  readonly events: OpenFeatureEventEmitter;
52
+ /** Set when operating in HTTP mode; `undefined` in binding mode. */
35
53
  private readonly client;
54
+ /** Set when operating in binding mode; `undefined` in HTTP mode. */
55
+ private readonly binding;
36
56
  private readonly logging;
37
57
  private currentStatus;
38
- constructor(options: FlagshipProviderOptions);
58
+ private readonly resolve;
59
+ constructor(options: FlagshipServerProviderOptions);
39
60
  /**
40
61
  * Returns the provided logger when logging is enabled, or a no-op logger
41
62
  * when `logging` is `false`. Using this in every resolve method ensures
@@ -43,10 +64,14 @@ declare class FlagshipServerProvider implements Provider {
43
64
  */
44
65
  private logger;
45
66
  /**
46
- * Probes the evaluation endpoint with a health-check request. A 404 response
47
- * is treated as success — it means the endpoint is reachable but the
48
- * health-check flag simply doesn't exist, which is expected. Any network
49
- * failure or timeout sets the status to ERROR.
67
+ * Initializes the provider.
68
+ *
69
+ * **HTTP mode**: probes the evaluation endpoint with a health-check request.
70
+ * A 404 response is treated as success — it means the endpoint is reachable
71
+ * but the health-check flag simply doesn't exist, which is expected.
72
+ *
73
+ * **Binding mode**: sets READY immediately — the binding is guaranteed to
74
+ * be available by the Workers runtime.
50
75
  */
51
76
  initialize(_context?: EvaluationContext): Promise<void>;
52
77
  onClose(): Promise<void>;
@@ -55,14 +80,14 @@ declare class FlagshipServerProvider implements Provider {
55
80
  resolveStringEvaluation(flagKey: string, defaultValue: string, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<string>>;
56
81
  resolveNumberEvaluation(flagKey: string, defaultValue: number, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<number>>;
57
82
  resolveObjectEvaluation<T extends JsonValue>(flagKey: string, defaultValue: T, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<T>>;
58
- private resolve;
83
+ private resolveViaHttp;
84
+ private handleHttpError;
85
+ private resolveViaBinding;
59
86
  /**
60
- * Maps a runtime value to one of the four OpenFeature flag types.
61
- * `null` maps to `'object'` (typeof null === 'object'), treating it as a
62
- * JSON null which belongs to the object/structure category.
87
+ * Calls the appropriate `*Details` method on the binding based on the
88
+ * expected type. Falls back to `get` + synthetic details for unknown types.
63
89
  */
64
- private getValueType;
65
- private handleError;
90
+ private evaluateBinding;
66
91
  }
67
92
  //#endregion
68
93
  //#region src/hooks/logging-hook.d.ts
@@ -141,4 +166,4 @@ declare class TelemetryHook implements Hook {
141
166
  finally(hookContext: Readonly<HookContext>, _evaluationDetails: EvaluationDetails<FlagValue>, _hookHints?: HookHints): void;
142
167
  }
143
168
  //#endregion
144
- export { ContextTransformer, FLAGSHIP_DEFAULT_BASE_URL, FlagshipClient, FlagshipError, FlagshipErrorCode, type FlagshipEvaluationResponse, type FlagshipProviderOptions, FlagshipServerProvider, LoggingHook, type TelemetryEvent, TelemetryHook };
169
+ export { ContextTransformer, FLAGSHIP_DEFAULT_BASE_URL, type FlagshipBinding, type FlagshipBindingEvaluationDetails, type FlagshipBindingProviderOptions, FlagshipClient, FlagshipError, FlagshipErrorCode, type FlagshipEvaluationResponse, type FlagshipProviderOptions, FlagshipServerProvider, type FlagshipServerProviderOptions, LoggingHook, type TelemetryEvent, TelemetryHook, isBindingOptions };
package/dist/server.d.mts CHANGED
@@ -1,14 +1,20 @@
1
- import { c as FlagshipEvaluationResponse, i as FLAGSHIP_DEFAULT_BASE_URL, l as FlagshipProviderOptions, n as ContextTransformer, o as FlagshipError, s as FlagshipErrorCode, t as FlagshipClient } from "./index-C_sW3e_7.mjs";
1
+ import { a as FlagshipBinding, 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, s as FlagshipBindingProviderOptions, t as FlagshipClient, u as FlagshipErrorCode } from "./index-BMGtqsWU.mjs";
2
2
  import { BeforeHookContext, ErrorCode, EvaluationContext, EvaluationDetails, FlagValue, Hook, HookContext, HookHints, JsonValue, Logger, OpenFeatureEventEmitter, Provider, ProviderMetadata, ProviderStatus, ResolutionDetails } from "@openfeature/server-sdk";
3
3
 
4
4
  //#region src/server-provider.d.ts
5
5
  /**
6
6
  * OpenFeature provider for Flagship (server-side / dynamic context).
7
7
  *
8
- * Use this provider with `@openfeature/server-sdk` for Node.js,
9
- * Cloudflare Workers, and other server-side JavaScript environments.
8
+ * Supports two modes of operation:
10
9
  *
11
- * @example
10
+ * **HTTP mode** — evaluates flags via HTTP requests to the Flagship API.
11
+ * Requires `appId`/`endpoint`, `accountId`, and optionally `authToken`.
12
+ *
13
+ * **Binding mode** — evaluates flags via a Cloudflare Workers wrangler binding.
14
+ * Only requires the `binding` field (the `Flagship` object from `env`). No HTTP
15
+ * overhead, no auth tokens. This is the recommended approach for Workers.
16
+ *
17
+ * @example HTTP mode
12
18
  * ```typescript
13
19
  * import { OpenFeature } from '@openfeature/server-sdk';
14
20
  * import { FlagshipServerProvider } from '@cloudflare/flagship/server';
@@ -20,22 +26,37 @@ import { BeforeHookContext, ErrorCode, EvaluationContext, EvaluationDetails, Fla
20
26
  * authToken: 'your-token',
21
27
  * })
22
28
  * );
29
+ * ```
23
30
  *
24
- * const client = OpenFeature.getClient();
25
- * const value = await client.getBooleanValue('my-flag', false, {
26
- * targetingKey: 'user-123',
27
- * email: 'user@example.com',
28
- * });
31
+ * @example Binding mode (Cloudflare Workers)
32
+ * ```typescript
33
+ * import { OpenFeature } from '@openfeature/server-sdk';
34
+ * import { FlagshipServerProvider } from '@cloudflare/flagship/server';
35
+ *
36
+ * export default {
37
+ * async fetch(request: Request, env: { FLAGS: FlagshipBinding }) {
38
+ * await OpenFeature.setProviderAndWait(
39
+ * new FlagshipServerProvider({ binding: env.FLAGS })
40
+ * );
41
+ * const client = OpenFeature.getClient();
42
+ * const value = await client.getBooleanValue('my-flag', false);
43
+ * return new Response(JSON.stringify({ value }));
44
+ * },
45
+ * };
29
46
  * ```
30
47
  */
31
48
  declare class FlagshipServerProvider implements Provider {
32
49
  readonly metadata: ProviderMetadata;
33
50
  readonly runsOn: "server";
34
51
  readonly events: OpenFeatureEventEmitter;
52
+ /** Set when operating in HTTP mode; `undefined` in binding mode. */
35
53
  private readonly client;
54
+ /** Set when operating in binding mode; `undefined` in HTTP mode. */
55
+ private readonly binding;
36
56
  private readonly logging;
37
57
  private currentStatus;
38
- constructor(options: FlagshipProviderOptions);
58
+ private readonly resolve;
59
+ constructor(options: FlagshipServerProviderOptions);
39
60
  /**
40
61
  * Returns the provided logger when logging is enabled, or a no-op logger
41
62
  * when `logging` is `false`. Using this in every resolve method ensures
@@ -43,10 +64,14 @@ declare class FlagshipServerProvider implements Provider {
43
64
  */
44
65
  private logger;
45
66
  /**
46
- * Probes the evaluation endpoint with a health-check request. A 404 response
47
- * is treated as success — it means the endpoint is reachable but the
48
- * health-check flag simply doesn't exist, which is expected. Any network
49
- * failure or timeout sets the status to ERROR.
67
+ * Initializes the provider.
68
+ *
69
+ * **HTTP mode**: probes the evaluation endpoint with a health-check request.
70
+ * A 404 response is treated as success — it means the endpoint is reachable
71
+ * but the health-check flag simply doesn't exist, which is expected.
72
+ *
73
+ * **Binding mode**: sets READY immediately — the binding is guaranteed to
74
+ * be available by the Workers runtime.
50
75
  */
51
76
  initialize(_context?: EvaluationContext): Promise<void>;
52
77
  onClose(): Promise<void>;
@@ -55,14 +80,14 @@ declare class FlagshipServerProvider implements Provider {
55
80
  resolveStringEvaluation(flagKey: string, defaultValue: string, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<string>>;
56
81
  resolveNumberEvaluation(flagKey: string, defaultValue: number, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<number>>;
57
82
  resolveObjectEvaluation<T extends JsonValue>(flagKey: string, defaultValue: T, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<T>>;
58
- private resolve;
83
+ private resolveViaHttp;
84
+ private handleHttpError;
85
+ private resolveViaBinding;
59
86
  /**
60
- * Maps a runtime value to one of the four OpenFeature flag types.
61
- * `null` maps to `'object'` (typeof null === 'object'), treating it as a
62
- * JSON null which belongs to the object/structure category.
87
+ * Calls the appropriate `*Details` method on the binding based on the
88
+ * expected type. Falls back to `get` + synthetic details for unknown types.
63
89
  */
64
- private getValueType;
65
- private handleError;
90
+ private evaluateBinding;
66
91
  }
67
92
  //#endregion
68
93
  //#region src/hooks/logging-hook.d.ts
@@ -141,4 +166,4 @@ declare class TelemetryHook implements Hook {
141
166
  finally(hookContext: Readonly<HookContext>, _evaluationDetails: EvaluationDetails<FlagValue>, _hookHints?: HookHints): void;
142
167
  }
143
168
  //#endregion
144
- export { ContextTransformer, FLAGSHIP_DEFAULT_BASE_URL, FlagshipClient, FlagshipError, FlagshipErrorCode, type FlagshipEvaluationResponse, type FlagshipProviderOptions, FlagshipServerProvider, LoggingHook, type TelemetryEvent, TelemetryHook };
169
+ export { ContextTransformer, FLAGSHIP_DEFAULT_BASE_URL, type FlagshipBinding, type FlagshipBindingEvaluationDetails, type FlagshipBindingProviderOptions, FlagshipClient, FlagshipError, FlagshipErrorCode, type FlagshipEvaluationResponse, type FlagshipProviderOptions, FlagshipServerProvider, type FlagshipServerProviderOptions, LoggingHook, type TelemetryEvent, TelemetryHook, isBindingOptions };
package/dist/server.mjs CHANGED
@@ -1,290 +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
- import { ErrorCode, OpenFeatureEventEmitter, ProviderEvents, ProviderStatus } from "@openfeature/server-sdk";
3
- //#region src/server-provider.ts
4
- const _noop = () => {};
5
- /**
6
- * OpenFeature provider for Flagship (server-side / dynamic context).
7
- *
8
- * Use this provider with `@openfeature/server-sdk` for Node.js,
9
- * Cloudflare Workers, and other server-side JavaScript environments.
10
- *
11
- * @example
12
- * ```typescript
13
- * import { OpenFeature } from '@openfeature/server-sdk';
14
- * import { FlagshipServerProvider } from '@cloudflare/flagship/server';
15
- *
16
- * await OpenFeature.setProviderAndWait(
17
- * new FlagshipServerProvider({
18
- * appId: 'app-abc123',
19
- * accountId: 'your-account-id',
20
- * authToken: 'your-token',
21
- * })
22
- * );
23
- *
24
- * const client = OpenFeature.getClient();
25
- * const value = await client.getBooleanValue('my-flag', false, {
26
- * targetingKey: 'user-123',
27
- * email: 'user@example.com',
28
- * });
29
- * ```
30
- */
31
- var FlagshipServerProvider = class {
32
- constructor(options) {
33
- this.runsOn = "server";
34
- this.events = new OpenFeatureEventEmitter();
35
- this.currentStatus = ProviderStatus.NOT_READY;
36
- this.metadata = { name: "Flagship Server Provider" };
37
- this.client = new FlagshipClient(options);
38
- this.logging = options.logging ?? false;
39
- }
40
- /**
41
- * Returns the provided logger when logging is enabled, or a no-op logger
42
- * when `logging` is `false`. Using this in every resolve method ensures
43
- * the SDK produces no console output unless the caller opts in.
44
- */
45
- logger(logger) {
46
- if (this.logging) return logger;
47
- return {
48
- debug: _noop,
49
- info: _noop,
50
- warn: _noop,
51
- error: _noop
52
- };
53
- }
54
- /**
55
- * Probes the evaluation endpoint with a health-check request. A 404 response
56
- * is treated as success — it means the endpoint is reachable but the
57
- * health-check flag simply doesn't exist, which is expected. Any network
58
- * failure or timeout sets the status to ERROR.
59
- */
60
- async initialize(_context) {
61
- try {
62
- await this.client.evaluate("_flagship_health_check", {});
63
- this.currentStatus = ProviderStatus.READY;
64
- this.events.emit(ProviderEvents.Ready);
65
- } catch (error) {
66
- if (error instanceof FlagshipError && error.cause instanceof Response && error.cause.status === 404) {
67
- this.currentStatus = ProviderStatus.READY;
68
- this.events.emit(ProviderEvents.Ready);
69
- return;
70
- }
71
- this.currentStatus = ProviderStatus.ERROR;
72
- this.events.emit(ProviderEvents.Error, { message: error instanceof Error ? error.message : String(error) });
73
- }
74
- }
75
- async onClose() {
76
- this.currentStatus = ProviderStatus.NOT_READY;
77
- }
78
- get status() {
79
- return this.currentStatus;
80
- }
81
- async resolveBooleanEvaluation(flagKey, defaultValue, context, logger) {
82
- return this.resolve(flagKey, defaultValue, context, "boolean", logger);
83
- }
84
- async resolveStringEvaluation(flagKey, defaultValue, context, logger) {
85
- return this.resolve(flagKey, defaultValue, context, "string", logger);
86
- }
87
- async resolveNumberEvaluation(flagKey, defaultValue, context, logger) {
88
- return this.resolve(flagKey, defaultValue, context, "number", logger);
89
- }
90
- async resolveObjectEvaluation(flagKey, defaultValue, context, logger) {
91
- return this.resolve(flagKey, defaultValue, context, "object", logger);
92
- }
93
- async resolve(flagKey, defaultValue, context, expectedType, logger) {
94
- const log = this.logger(logger);
95
- try {
96
- log.debug(`[Flagship] Evaluating flag "${flagKey}" (expected: ${expectedType})`);
97
- const result = await this.client.evaluate(flagKey, context);
98
- const actualType = this.getValueType(result.value);
99
- if (actualType !== expectedType) {
100
- const msg = `Flag "${flagKey}" type mismatch: expected ${expectedType}, got ${actualType}`;
101
- log.warn(`[Flagship] ${msg}`);
102
- return {
103
- value: defaultValue,
104
- errorCode: ErrorCode.TYPE_MISMATCH,
105
- errorMessage: msg,
106
- reason: "ERROR"
107
- };
108
- }
109
- log.debug(`[Flagship] Flag "${flagKey}" resolved: value=${String(result.value)} reason=${result.reason} variant=${result.variant}`);
110
- return {
111
- value: result.value,
112
- variant: result.variant,
113
- reason: result.reason,
114
- flagMetadata: {}
115
- };
116
- } catch (error) {
117
- return this.handleError(flagKey, defaultValue, error, log);
118
- }
119
- }
120
- /**
121
- * Maps a runtime value to one of the four OpenFeature flag types.
122
- * `null` maps to `'object'` (typeof null === 'object'), treating it as a
123
- * JSON null which belongs to the object/structure category.
124
- */
125
- getValueType(value) {
126
- if (typeof value === "boolean") return "boolean";
127
- if (typeof value === "string") return "string";
128
- if (typeof value === "number") return "number";
129
- return "object";
130
- }
131
- handleError(flagKey, defaultValue, error, logger) {
132
- if (error instanceof FlagshipError) {
133
- let errorCode;
134
- switch (error.code) {
135
- case FlagshipErrorCode.NETWORK_ERROR:
136
- errorCode = error.cause instanceof Response && error.cause.status === 404 ? ErrorCode.FLAG_NOT_FOUND : ErrorCode.GENERAL;
137
- break;
138
- case FlagshipErrorCode.TIMEOUT_ERROR:
139
- errorCode = ErrorCode.GENERAL;
140
- break;
141
- case FlagshipErrorCode.PARSE_ERROR:
142
- errorCode = ErrorCode.PARSE_ERROR;
143
- break;
144
- case FlagshipErrorCode.INVALID_CONTEXT:
145
- errorCode = ErrorCode.INVALID_CONTEXT;
146
- break;
147
- default: errorCode = ErrorCode.GENERAL;
148
- }
149
- logger.error(`[Flagship] Flag "${flagKey}" evaluation failed (${errorCode}): ${error.message}`);
150
- return {
151
- value: defaultValue,
152
- errorCode,
153
- errorMessage: error.message,
154
- reason: "ERROR"
155
- };
156
- }
157
- const errorMessage = String(error);
158
- logger.error(`[Flagship] Flag "${flagKey}" evaluation failed (GENERAL): ${errorMessage}`);
159
- return {
160
- value: defaultValue,
161
- errorCode: ErrorCode.GENERAL,
162
- errorMessage,
163
- reason: "ERROR"
164
- };
165
- }
166
- };
167
- //#endregion
168
- //#region src/hooks/logging-hook.ts
169
- /**
170
- * Logging hook for debugging flag evaluations
171
- *
172
- * @example
173
- * ```typescript
174
- * import { OpenFeature } from '@openfeature/server-sdk';
175
- * import { FlagshipServerProvider, LoggingHook } from '@cloudflare/flagship/server';
176
- *
177
- * const provider = new FlagshipServerProvider({ appId: 'your-app-id', accountId: 'your-account-id' });
178
- * await OpenFeature.setProviderAndWait(provider);
179
- *
180
- * // Add logging hook
181
- * OpenFeature.addHooks(new LoggingHook());
182
- * ```
183
- */
184
- var LoggingHook = class {
185
- constructor(logger = console.log) {
186
- this.logger = logger;
187
- }
188
- before(hookContext, _hookHints) {
189
- this.logger(`[Flagship] Evaluating flag: ${hookContext.flagKey}`, {
190
- defaultValue: hookContext.defaultValue,
191
- context: hookContext.context
192
- });
193
- }
194
- after(hookContext, evaluationDetails, _hookHints) {
195
- this.logger(`[Flagship] Flag ${hookContext.flagKey} evaluated`, {
196
- value: evaluationDetails.value,
197
- reason: evaluationDetails.reason,
198
- variant: evaluationDetails.variant,
199
- errorCode: evaluationDetails.errorCode
200
- });
201
- }
202
- error(hookContext, error, _hookHints) {
203
- const message = error instanceof Error ? error.message : String(error);
204
- this.logger(`[Flagship] Error evaluating flag ${hookContext.flagKey}:`, message);
205
- }
206
- finally(_hookContext, _evaluationDetails, _hookHints) {}
207
- };
208
- //#endregion
209
- //#region src/hooks/telemetry-hook.ts
210
- /**
211
- * Telemetry hook for tracking flag evaluations
212
- *
213
- * @example
214
- * ```typescript
215
- * import { OpenFeature } from '@openfeature/server-sdk';
216
- * import { FlagshipServerProvider, TelemetryHook } from '@cloudflare/flagship/server';
217
- *
218
- * const telemetryHook = new TelemetryHook((event) => {
219
- * // Send to your analytics service
220
- * analytics.track('flag_evaluated', event);
221
- * });
222
- *
223
- * OpenFeature.addHooks(telemetryHook);
224
- * ```
225
- */
226
- var TelemetryHook = class {
227
- constructor(onEvent) {
228
- this.startTimes = /* @__PURE__ */ new Map();
229
- this.contextKeys = /* @__PURE__ */ new WeakMap();
230
- this.hints = /* @__PURE__ */ new WeakMap();
231
- this.onEvent = onEvent;
232
- }
233
- before(hookContext, hookHints) {
234
- const now = Date.now();
235
- const key = `${hookContext.flagKey}-${now}-${Math.random()}`;
236
- this.startTimes.set(key, now);
237
- this.contextKeys.set(hookContext, key);
238
- if (hookHints !== void 0) this.hints.set(hookContext, hookHints);
239
- }
240
- after(hookContext, evaluationDetails, _hookHints) {
241
- const telemetryKey = this.contextKeys.get(hookContext);
242
- const startTime = telemetryKey ? this.startTimes.get(telemetryKey) : void 0;
243
- const duration = startTime !== void 0 ? Date.now() - startTime : void 0;
244
- if (telemetryKey !== void 0) {
245
- this.startTimes.delete(telemetryKey);
246
- this.contextKeys.delete(hookContext);
247
- }
248
- this.onEvent({
249
- type: "evaluation",
250
- flagKey: hookContext.flagKey,
251
- timestamp: Date.now(),
252
- duration,
253
- value: evaluationDetails.value,
254
- reason: evaluationDetails.reason,
255
- variant: evaluationDetails.variant,
256
- errorCode: evaluationDetails.errorCode,
257
- context: hookContext.context,
258
- hints: this.hints.get(hookContext)
259
- });
260
- }
261
- error(hookContext, error, _hookHints) {
262
- const telemetryKey = this.contextKeys.get(hookContext);
263
- const startTime = telemetryKey ? this.startTimes.get(telemetryKey) : void 0;
264
- const duration = startTime !== void 0 ? Date.now() - startTime : void 0;
265
- if (telemetryKey !== void 0) {
266
- this.startTimes.delete(telemetryKey);
267
- this.contextKeys.delete(hookContext);
268
- }
269
- const errorMessage = error instanceof Error ? error.message : String(error);
270
- this.onEvent({
271
- type: "error",
272
- flagKey: hookContext.flagKey,
273
- timestamp: Date.now(),
274
- duration,
275
- errorMessage,
276
- context: hookContext.context,
277
- hints: this.hints.get(hookContext)
278
- });
279
- }
280
- finally(hookContext, _evaluationDetails, _hookHints) {
281
- const telemetryKey = this.contextKeys.get(hookContext);
282
- if (telemetryKey !== void 0) {
283
- this.startTimes.delete(telemetryKey);
284
- this.contextKeys.delete(hookContext);
285
- }
286
- this.hints.delete(hookContext);
287
- }
288
- };
289
- //#endregion
290
- export { ContextTransformer, FLAGSHIP_DEFAULT_BASE_URL, FlagshipClient, FlagshipError, FlagshipErrorCode, FlagshipServerProvider, LoggingHook, TelemetryHook };
1
+ import{a as e,i as t,n,o as r,r as i,t as a}from"./src-DW2QohY9.mjs";import{ErrorCode as o,OpenFeatureEventEmitter as s,ProviderEvents as c,ProviderStatus as l}from"@openfeature/server-sdk";const u=()=>{},d=[`appId`,`endpoint`,`accountId`,`authToken`,`baseUrl`,`fetchOptions`,`timeout`,`retries`,`retryDelay`];var f=class{constructor(e){if(this.runsOn=`server`,this.events=new s,this.currentStatus=l.NOT_READY,this.metadata={name:`Flagship Server Provider`},this.logging=e.logging??!1,r(e)){let t=d.filter(t=>t in e);if(t.length>0)throw Error(`Flagship: when using a binding, the following HTTP-specific options must not be provided: ${t.join(`, `)}. Provide either a binding or HTTP configuration, not both.`);this.binding=e.binding,this.client=void 0,this.resolve=this.resolveViaBinding.bind(this)}else this.client=new a(e),this.binding=void 0,this.resolve=this.resolveViaHttp.bind(this)}logger(e){return this.logging?e:{debug:u,info:u,warn:u,error:u}}async initialize(e){if(this.binding){this.currentStatus=l.READY,this.events.emit(c.Ready);return}try{await this.client.evaluate(`_flagship_health_check`,{}),this.currentStatus=l.READY,this.events.emit(c.Ready)}catch(e){if(e instanceof t&&e.cause instanceof Response&&e.cause.status===404){this.currentStatus=l.READY,this.events.emit(c.Ready);return}this.currentStatus=l.ERROR,this.events.emit(c.Error,{message:e instanceof Error?e.message:String(e)})}}async onClose(){this.currentStatus=l.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,t,n,r,i){let a=this.logger(i);try{a.debug(`[Flagship] Evaluating flag "${e}" (expected: ${r})`);let i=await this.client.evaluate(e,n),s=p(i.value);if(s!==r){let n=`Flag "${e}" type mismatch: expected ${r}, got ${s}`;return a.warn(`[Flagship] ${n}`),{value:t,errorCode:o.TYPE_MISMATCH,errorMessage:n,reason:`ERROR`}}return a.debug(`[Flagship] Flag "${e}" resolved: value=${String(i.value)} reason=${i.reason} variant=${i.variant}`),{value:i.value,variant:i.variant,reason:i.reason,flagMetadata:{}}}catch(n){return this.handleHttpError(e,t,n,a)}}handleHttpError(n,r,i,a){if(i instanceof t){let t;switch(i.code){case e.NETWORK_ERROR:t=i.cause instanceof Response&&i.cause.status===404?o.FLAG_NOT_FOUND:o.GENERAL;break;case e.TIMEOUT_ERROR:t=o.GENERAL;break;case e.PARSE_ERROR:t=o.PARSE_ERROR;break;case e.INVALID_CONTEXT:t=o.INVALID_CONTEXT;break;default:t=o.GENERAL}return a.error(`[Flagship] Flag "${n}" evaluation failed (${t}): ${i.message}`),{value:r,errorCode:t,errorMessage:i.message,reason:`ERROR`}}let s=String(i);return a.error(`[Flagship] Flag "${n}" evaluation failed (GENERAL): ${s}`),{value:r,errorCode:o.GENERAL,errorMessage:s,reason:`ERROR`}}async resolveViaBinding(e,t,n,r,i){let a=this.logger(i);try{a.debug(`[Flagship] Evaluating flag "${e}" via binding (expected: ${r})`);let i=m(n,a),s=await this.evaluateBinding(e,t,r,i);if(s.errorCode){let n=h(s.errorCode),r=s.errorMessage??`Binding error: ${s.errorCode}`;return a.error(`[Flagship] Flag "${e}" evaluation failed (${n}): ${r}`),{value:t,errorCode:n,errorMessage:r,reason:s.reason??`ERROR`}}let c=p(s.value);if(c!==r){let n=`Flag "${e}" type mismatch: expected ${r}, got ${c}`;return a.warn(`[Flagship] ${n}`),{value:t,errorCode:o.TYPE_MISMATCH,errorMessage:n,reason:`ERROR`}}return a.debug(`[Flagship] Flag "${e}" resolved via binding: value=${String(s.value)} reason=${s.reason} variant=${s.variant}`),{value:s.value,variant:s.variant,reason:s.reason,flagMetadata:{}}}catch(n){let r=n instanceof Error?n.message:String(n);return a.error(`[Flagship] Flag "${e}" binding evaluation failed (GENERAL): ${r}`),{value:t,errorCode:o.GENERAL,errorMessage:r,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 p(e){return typeof e==`boolean`?`boolean`:typeof e==`string`?`string`:typeof e==`number`?`number`:`object`}function m(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 h(e){switch(e){case`FLAG_NOT_FOUND`:return o.FLAG_NOT_FOUND;case`PARSE_ERROR`:return o.PARSE_ERROR;case`TYPE_MISMATCH`:return o.TYPE_MISMATCH;case`INVALID_CONTEXT`:return o.INVALID_CONTEXT;default:return o.GENERAL}}var g=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){}},_=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)}};export{n as ContextTransformer,i as FLAGSHIP_DEFAULT_BASE_URL,a as FlagshipClient,t as FlagshipError,e as FlagshipErrorCode,f as FlagshipServerProvider,g as LoggingHook,_ as TelemetryHook,r as isBindingOptions};
@@ -0,0 +1 @@
1
+ const e=`https://api.cloudflare.com`;function t(e){return`binding`in e&&e.binding!==null&&e.binding!==void 0}let n=function(e){return e.NETWORK_ERROR=`NETWORK_ERROR`,e.TIMEOUT_ERROR=`TIMEOUT_ERROR`,e.PARSE_ERROR=`PARSE_ERROR`,e.INVALID_CONTEXT=`INVALID_CONTEXT`,e}({});var r=class extends Error{constructor(e,t,n){super(e),this.code=t,this.cause=n,this.name=`FlagshipError`,Object.setPrototypeOf(this,new.target.prototype)}},i=class{static toQueryParams(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]=String(i);continue}if(typeof i==`object`){t?t.push(r):console.warn(`[Flagship] Context key "${r}" is a complex object/array and cannot be serialized to a query parameter. This value will be ignored during flag evaluation.`);continue}}return n}static buildUrl(e,t,n,r){let i=new URL(e);i.searchParams.set(`flagKey`,t);let a=this.toQueryParams(n,r);for(let[e,t]of Object.entries(a))i.searchParams.set(e,t);return i.toString()}},a=class{constructor(e){this.options={endpoint:s(e),fetchOptions:o(e),timeout:e.timeout||5e3,retries:Math.min(e.retries===void 0?1:e.retries,10),retryDelay:Math.min(e.retryDelay===void 0?1e3:e.retryDelay,3e4)}}async evaluate(e,t){let a=[],o=i.buildUrl(this.options.endpoint,e,t,a);if(a.length>0)throw new r(`Evaluation context contains complex values that cannot be serialized for flag "${e}". Unsupported keys: ${a.join(`, `)}. Use primitive values (string, number, boolean) or Date objects.`,n.INVALID_CONTEXT);return this.fetchWithRetry(o,this.options.retries)}async fetchWithRetry(e,t){try{return await this.fetchWithTimeout(e,this.options.timeout)}catch(n){if(n instanceof r&&n.cause instanceof Response){let e=n.cause.status;if(e===404||e===400)throw n}if(t>0)return await new Promise(e=>setTimeout(e,this.options.retryDelay)),this.fetchWithRetry(e,t-1);throw n}}async fetchWithTimeout(e,t){let i=new AbortController,a=setTimeout(()=>i.abort(),t);try{let t=await fetch(e,{...this.options.fetchOptions,signal:i.signal});if(clearTimeout(a),!t.ok)throw new r(`HTTP ${t.status}: ${t.statusText}`,n.NETWORK_ERROR,t);let o=await t.json();if(!o||typeof o!=`object`||!(`flagKey`in o)||!(`value`in o))throw new r(`Invalid response format from Flagship API`,n.PARSE_ERROR);return o}catch(e){throw clearTimeout(a),e instanceof Error&&e.name===`AbortError`?new r(`Request timeout after ${t}ms`,n.TIMEOUT_ERROR,e):e instanceof r?e:new r(`Network error: ${e}`,n.NETWORK_ERROR,e)}}};function o(e){let{authToken:t,fetchOptions:n={}}=e;if(!t)return n;let r=new Headers(n.headers);return r.has(`Authorization`)||r.set(`Authorization`,`Bearer ${t}`),{...n,headers:r}}function s(e){let{appId:t,endpoint:n,baseUrl:r,accountId:i}=e;if(t&&n)throw Error(`Flagship: provide either "appId" or "endpoint", not both`);if(!t&&!n)throw Error(`Flagship: either "appId" or "endpoint" is required`);if(n){try{new URL(n)}catch{throw Error(`Flagship: invalid endpoint URL: ${n}`)}return n}if(!i)throw Error(`Flagship: "accountId" is required when using "appId"`);let a=`${(r||`https://api.cloudflare.com`).replace(/\/+$/,``)}/client/v4/accounts/${encodeURIComponent(i)}/flagship/apps/${encodeURIComponent(t)}/evaluate`;try{new URL(a)}catch{throw Error(`Flagship: resolved endpoint is not a valid URL: ${a}`)}return a}export{n as a,r as i,i as n,t as o,e as r,a as t};
@@ -0,0 +1 @@
1
+ function e(e){return`binding`in e&&e.binding!==null&&e.binding!==void 0}let t=function(e){return e.NETWORK_ERROR=`NETWORK_ERROR`,e.TIMEOUT_ERROR=`TIMEOUT_ERROR`,e.PARSE_ERROR=`PARSE_ERROR`,e.INVALID_CONTEXT=`INVALID_CONTEXT`,e}({});var n=class extends Error{constructor(e,t,n){super(e),this.code=t,this.cause=n,this.name=`FlagshipError`,Object.setPrototypeOf(this,new.target.prototype)}},r=class{static toQueryParams(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]=String(i);continue}if(typeof i==`object`){t?t.push(r):console.warn(`[Flagship] Context key "${r}" is a complex object/array and cannot be serialized to a query parameter. This value will be ignored during flag evaluation.`);continue}}return n}static buildUrl(e,t,n,r){let i=new URL(e);i.searchParams.set(`flagKey`,t);let a=this.toQueryParams(n,r);for(let[e,t]of Object.entries(a))i.searchParams.set(e,t);return i.toString()}},i=class{constructor(e){this.options={endpoint:o(e),fetchOptions:a(e),timeout:e.timeout||5e3,retries:Math.min(e.retries===void 0?1:e.retries,10),retryDelay:Math.min(e.retryDelay===void 0?1e3:e.retryDelay,3e4)}}async evaluate(e,i){let a=[],o=r.buildUrl(this.options.endpoint,e,i,a);if(a.length>0)throw new n(`Evaluation context contains complex values that cannot be serialized for flag "${e}". Unsupported keys: ${a.join(`, `)}. Use primitive values (string, number, boolean) or Date objects.`,t.INVALID_CONTEXT);return this.fetchWithRetry(o,this.options.retries)}async fetchWithRetry(e,t){try{return await this.fetchWithTimeout(e,this.options.timeout)}catch(r){if(r instanceof n&&r.cause instanceof Response){let e=r.cause.status;if(e===404||e===400)throw r}if(t>0)return await new Promise(e=>setTimeout(e,this.options.retryDelay)),this.fetchWithRetry(e,t-1);throw r}}async fetchWithTimeout(e,r){let i=new AbortController,a=setTimeout(()=>i.abort(),r);try{let r=await fetch(e,{...this.options.fetchOptions,signal:i.signal});if(clearTimeout(a),!r.ok)throw new n(`HTTP ${r.status}: ${r.statusText}`,t.NETWORK_ERROR,r);let o=await r.json();if(!o||typeof o!=`object`||!(`flagKey`in o)||!(`value`in o))throw new n(`Invalid response format from Flagship API`,t.PARSE_ERROR);return o}catch(e){throw clearTimeout(a),e instanceof Error&&e.name===`AbortError`?new n(`Request timeout after ${r}ms`,t.TIMEOUT_ERROR,e):e instanceof n?e:new n(`Network error: ${e}`,t.NETWORK_ERROR,e)}}};function a(e){let{authToken:t,fetchOptions:n={}}=e;if(!t)return n;let r=new Headers(n.headers);return r.has(`Authorization`)||r.set(`Authorization`,`Bearer ${t}`),{...n,headers:r}}function o(e){let{appId:t,endpoint:n,baseUrl:r,accountId:i}=e;if(t&&n)throw Error(`Flagship: provide either "appId" or "endpoint", not both`);if(!t&&!n)throw Error(`Flagship: either "appId" or "endpoint" is required`);if(n){try{new URL(n)}catch{throw Error(`Flagship: invalid endpoint URL: ${n}`)}return n}if(!i)throw Error(`Flagship: "accountId" is required when using "appId"`);let a=`${(r||`https://api.cloudflare.com`).replace(/\/+$/,``)}/client/v4/accounts/${encodeURIComponent(i)}/flagship/apps/${encodeURIComponent(t)}/evaluate`;try{new URL(a)}catch{throw Error(`Flagship: resolved endpoint is not a valid URL: ${a}`)}return a}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return e}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return`https://api.cloudflare.com`}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return i}});