@spotify-confidence/openfeature-server-provider-local 0.1.1 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0](https://github.com/spotify/confidence-resolver/compare/openfeature-provider-js-v0.2.0...openfeature-provider-js-v0.3.0) (2025-12-02)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * migrate to cdn state fetch, publish logs using client secret ([#166](https://github.com/spotify/confidence-resolver/issues/166))
9
+
10
+ ### Features
11
+
12
+ * migrate to cdn state fetch, publish logs using client secret ([#166](https://github.com/spotify/confidence-resolver/issues/166)) ([6c8d959](https://github.com/spotify/confidence-resolver/commit/6c8d959f124faa419c1ace103d8832457248eb26))
13
+
14
+
15
+ ### Dependencies
16
+
17
+ * The following workspace dependencies were updated
18
+ * dependencies
19
+ * rust-guest bumped from 0.1.10 to 0.1.11
20
+
21
+ ## [0.2.0](https://github.com/spotify/confidence-resolver/compare/openfeature-provider-js-v0.1.1...openfeature-provider-js-v0.2.0) (2025-11-24)
22
+
23
+
24
+ ### Features
25
+
26
+ * send js sdk info in resolve request ([#161](https://github.com/spotify/confidence-resolver/issues/161)) ([5cbc7d9](https://github.com/spotify/confidence-resolver/commit/5cbc7d9e2ada26c52298d74faaba50ec6cb4c3e7))
27
+
28
+
29
+ ### Dependencies
30
+
31
+ * The following workspace dependencies were updated
32
+ * dependencies
33
+ * rust-guest bumped from 0.1.9 to 0.1.10
34
+
3
35
  ## [0.1.1](https://github.com/spotify/confidence-resolver-rust/compare/openfeature-provider-js-v0.1.0...openfeature-provider-js-v0.1.1) (2025-11-03)
4
36
 
5
37
 
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @spotify-confidence/openfeature-server-provider-local
1
+ # Confidence OpenFeature Local Provider for JavaScript
2
2
 
3
3
  OpenFeature provider for the Spotify Confidence resolver (local mode, powered by WebAssembly). It periodically fetches resolver state, evaluates flags locally, and flushes evaluation logs to the Confidence backend.
4
4
 
@@ -29,6 +29,18 @@ Notes:
29
29
 
30
30
  ---
31
31
 
32
+ ## Getting Your Credentials
33
+
34
+ You'll need a **client secret** from Confidence to use this provider.
35
+
36
+ **📖 See the [Integration Guide: Getting Your Credentials](../INTEGRATION_GUIDE.md#getting-your-credentials)** for step-by-step instructions on:
37
+ - How to navigate the Confidence dashboard
38
+ - Creating a Backend integration
39
+ - Creating a test flag for verification
40
+ - Best practices for credential storage
41
+
42
+ ---
43
+
32
44
  ## Quick start (Node)
33
45
 
34
46
  ```ts
@@ -37,8 +49,6 @@ import { createConfidenceServerProvider } from '@spotify-confidence/openfeature-
37
49
 
38
50
  const provider = createConfidenceServerProvider({
39
51
  flagClientSecret: process.env.CONFIDENCE_FLAG_CLIENT_SECRET!,
40
- apiClientId: process.env.CONFIDENCE_API_CLIENT_ID!,
41
- apiClientSecret: process.env.CONFIDENCE_API_CLIENT_SECRET!,
42
52
  // initializeTimeout?: number
43
53
  // flushInterval?: number
44
54
  // fetch?: typeof fetch (Node <18 or custom transport)
@@ -49,13 +59,20 @@ await OpenFeature.setProviderAndWait(provider);
49
59
 
50
60
  const client = OpenFeature.getClient();
51
61
 
62
+ // Create evaluation context with user attributes for targeting
63
+ const context = {
64
+ targetingKey: 'user-123',
65
+ country: 'US',
66
+ plan: 'premium',
67
+ };
68
+
52
69
  // Evaluate a boolean flag
53
- const details = await client.getBooleanDetails('my-flag', false, { targetingKey: 'user-123' });
54
- console.log(details.value, details.reason);
70
+ const enabled = await client.getBooleanValue('test-flag.enabled', false, context);
71
+ console.log('Flag value:', enabled);
55
72
 
56
73
  // Evaluate a nested value from an object flag using dot-path
57
74
  // e.g. flag key "experiments" with payload { groupA: { ratio: 0.5 } }
58
- const ratio = await client.getNumberValue('experiments.groupA.ratio', 0, { targetingKey: 'user-123' });
75
+ const ratio = await client.getNumberValue('experiments.groupA.ratio', 0, context);
59
76
 
60
77
  // On shutdown, flush any pending logs
61
78
  await provider.onClose();
@@ -63,56 +80,73 @@ await provider.onClose();
63
80
 
64
81
  ---
65
82
 
66
- ## Options
83
+ ## Evaluation Context
67
84
 
68
- - `flagClientSecret` (string, required): The flag client secret used during evaluation.
69
- - `apiClientId` (string, required): OAuth client ID for Confidence IAM.
70
- - `apiClientSecret` (string, required): OAuth client secret for Confidence IAM.
71
- - `initializeTimeout` (number, optional): Max ms to wait for initial state fetch. Defaults to 30_000.
72
- - `flushInterval` (number, optional): Interval in ms for sending evaluation logs. Defaults to 10_000.
73
- - `fetch` (optional): Custom `fetch` implementation. Required for Node < 18; for Node 18+ you can omit.
85
+ The evaluation context contains information about the user/session being evaluated for targeting and A/B testing.
74
86
 
75
- The provider periodically:
76
- - Refreshes resolver state (default every 30s)
77
- - Flushes flag evaluation logs to the backend
87
+ ### TypeScript/JavaScript Examples
88
+
89
+ ```typescript
90
+ // Simple attributes
91
+ const context = {
92
+ targetingKey: 'user-123',
93
+ country: 'US',
94
+ plan: 'premium',
95
+ age: 25,
96
+ };
97
+ ```
78
98
 
79
99
  ---
80
100
 
81
- ## Sticky Assignments
101
+ ## Error Handling
102
+
103
+ The provider uses a **default value fallback** pattern - when evaluation fails, it returns your specified default value instead of throwing an error.
104
+
105
+ **📖 See the [Integration Guide: Error Handling](../INTEGRATION_GUIDE.md#error-handling)** for:
106
+ - Common failure scenarios
107
+ - Error codes and meanings
108
+ - Production best practices
109
+ - Monitoring recommendations
110
+
111
+ ### TypeScript/JavaScript Examples
82
112
 
83
- Confidence supports "sticky" flag assignments to ensure users receive consistent variant assignments even when their context changes or flag configurations are updated. This SDK falls back to a cloud resolve in these cases.
113
+ ```typescript
114
+ // The provider returns the default value on errors
115
+ const enabled = await client.getBooleanValue('my-flag.enabled', false, context);
116
+ // enabled will be 'false' if evaluation failed
84
117
 
85
- > **ℹ️ Latency Considerations**
86
- >
87
- > When a sticky assignment is needed, the provider makes a **network call to Confidence's cloud resolvers**. This introduces additional latency (typically 50-200ms depending on your location) compared to local WASM evaluation.
88
- >
89
- > **Coming soon**: We're developing support for custom materialization storage (Redis, database, etc.) that will eliminate this network call and keep evaluation latency consistently low.
118
+ // For detailed error information, use getBooleanDetails()
119
+ const details = await client.getBooleanDetails('my-flag.enabled', false, context);
120
+ if (details.errorCode) {
121
+ console.error('Flag evaluation error:', details.errorMessage);
122
+ console.log('Reason:', details.reason);
123
+ }
124
+ ```
90
125
 
91
- ### How it works
126
+ ---
92
127
 
93
- When a flag is evaluated for a user, Confidence creates a "materialization" - a snapshot of which variant that user was assigned. On subsequent evaluations, the same variant is returned even if:
94
- - The user's context attributes change (e.g., different country, device type)
95
- - The flag's targeting rules are modified
96
- - New assignments are paused
128
+ ## Options
97
129
 
98
- ### Implementation
130
+ - `flagClientSecret` (string, required): The flag client secret used during evaluation and authentication.
131
+ - `initializeTimeout` (number, optional): Max ms to wait for initial state fetch. Defaults to 30_000.
132
+ - `flushInterval` (number, optional): Interval in ms for sending evaluation logs. Defaults to 10_000.
133
+ - `fetch` (optional): Custom `fetch` implementation. Required for Node < 18; for Node 18+ you can omit.
99
134
 
100
- The provider uses a **remote resolver fallback** for sticky assignments:
101
- - First, the local WASM resolver attempts to resolve the flag
102
- - If sticky assignment data is needed, the provider makes a network call to Confidence's cloud resolvers
103
- - Materializations are stored on Confidence servers with a **90-day TTL** (automatically renewed on access)
104
- - No local storage or database setup required
135
+ The provider periodically:
136
+ - Refreshes resolver state (default every 30s)
137
+ - Flushes flag evaluation logs to the backend
105
138
 
106
- ### Benefits
139
+ ---
107
140
 
108
- - **Zero configuration**: Works out of the box with no additional setup
109
- - **Managed storage**: Confidence handles all storage, TTL, and consistency
110
- - **Automatic renewal**: TTL is refreshed on each access
111
- - **Global availability**: Materializations are available across all your services
141
+ ## Sticky Assignments
112
142
 
113
- ### Coming Soon: Custom Materialization Storage
143
+ The provider supports **Sticky Assignments** for consistent variant assignments across flag evaluations.
114
144
 
115
- We're working on support for connecting your own materialization storage repository (Redis, database, file system, etc.) to eliminate network calls for sticky assignments and have full control over storage. This feature is currently in development.
145
+ **📖 See the [Integration Guide: Sticky Assignments](../INTEGRATION_GUIDE.md#sticky-assignments)** for:
146
+ - How sticky assignments work
147
+ - Server-managed storage (zero configuration)
148
+ - Latency considerations
149
+ - Custom storage options (currently Java-only, coming soon to JavaScript)
116
150
 
117
151
  ---
118
152
 
@@ -124,13 +158,19 @@ Namespaces:
124
158
  - Core: `cnfd:*`
125
159
  - Fetch/middleware: `cnfd:fetch:*` (e.g. retries, auth renewals, request summaries)
126
160
 
161
+ Log levels are hierarchical:
162
+ - `cnfd:debug` enables debug, info, warn, and error
163
+ - `cnfd:info` enables info, warn, and error
164
+ - `cnfd:warn` enables warn and error
165
+ - `cnfd:error` enables error only
166
+
127
167
  Enable logs:
128
168
 
129
169
  - Node:
130
170
  ```bash
131
171
  DEBUG=cnfd:* node app.js
132
172
  # or narrower
133
- DEBUG=cnfd:info,cnfd:error,cnfd:fetch:* node app.js
173
+ DEBUG=cnfd:info,cnfd:fetch:* node app.js
134
174
  ```
135
175
 
136
176
  - Browser (in DevTools console):
@@ -166,19 +206,14 @@ The package exports a browser ESM build that compiles the WASM via streaming and
166
206
 
167
207
  ---
168
208
 
169
- ## Troubleshooting
209
+ ## License
170
210
 
171
- - Provider stuck in NOT_READY/ERROR:
172
- - Verify `apiClientId`/`apiClientSecret` and `flagClientSecret` are correct.
173
- - Ensure outbound access to Confidence endpoints and GCS.
174
- - Enable `DEBUG=cnfd:*` for more detail.
211
+ See the root `LICENSE`.
175
212
 
176
- - No logs appear:
177
- - Install `debug` and enable the appropriate namespaces.
178
- - Check that your environment variable/`localStorage.debug` is set before your app initializes the provider.
213
+ ## Formatting
179
214
 
180
- ---
215
+ Code is formatted using prettier, you can format all files by running
181
216
 
182
- ## License
183
-
184
- See the root `LICENSE`.
217
+ ```sh
218
+ yarn format
219
+ ```
Binary file
@@ -1,7 +1,61 @@
1
1
  import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
2
2
  import { EvaluationContext, JsonValue, Provider, ProviderMetadata, ProviderStatus, ResolutionDetails } from "@openfeature/server-sdk";
3
3
 
4
- //#region src/proto/api.d.ts
4
+ //#region src/proto/confidence/flags/resolver/v1/types.d.ts
5
+
6
+ declare enum SdkId {
7
+ SDK_ID_UNSPECIFIED = 0,
8
+ SDK_ID_JAVA_PROVIDER = 1,
9
+ SDK_ID_KOTLIN_PROVIDER = 2,
10
+ SDK_ID_SWIFT_PROVIDER = 3,
11
+ SDK_ID_JS_WEB_PROVIDER = 4,
12
+ SDK_ID_JS_SERVER_PROVIDER = 5,
13
+ SDK_ID_PYTHON_PROVIDER = 6,
14
+ SDK_ID_GO_PROVIDER = 7,
15
+ SDK_ID_RUBY_PROVIDER = 8,
16
+ SDK_ID_RUST_PROVIDER = 9,
17
+ SDK_ID_JAVA_CONFIDENCE = 10,
18
+ SDK_ID_KOTLIN_CONFIDENCE = 11,
19
+ SDK_ID_SWIFT_CONFIDENCE = 12,
20
+ SDK_ID_JS_CONFIDENCE = 13,
21
+ SDK_ID_PYTHON_CONFIDENCE = 14,
22
+ SDK_ID_GO_CONFIDENCE = 15,
23
+ SDK_ID_RUST_CONFIDENCE = 16,
24
+ SDK_ID_FLUTTER_IOS_CONFIDENCE = 17,
25
+ SDK_ID_FLUTTER_ANDROID_CONFIDENCE = 18,
26
+ SDK_ID_DOTNET_CONFIDENCE = 19,
27
+ SDK_ID_GO_LOCAL_PROVIDER = 20,
28
+ SDK_ID_JAVA_LOCAL_PROVIDER = 21,
29
+ SDK_ID_JS_LOCAL_SERVER_PROVIDER = 22,
30
+ UNRECOGNIZED = -1,
31
+ }
32
+ /**
33
+ * (-- api-linter: core::0123::resource-annotation=disabled
34
+ * aip.dev/not-precedent: SDKs are not internal Confidence resources. --)
35
+ */
36
+ interface Sdk {
37
+ /** Name of a Confidence SDKs. */
38
+ id?: SdkId | undefined;
39
+ /** Custom name for non-Confidence SDKs. */
40
+ customId?: string | undefined;
41
+ /** Version of the SDK. */
42
+ version: string;
43
+ }
44
+ declare const Sdk: MessageFns$2<Sdk>;
45
+ type Builtin$2 = Date | Function | Uint8Array | string | number | boolean | undefined;
46
+ type DeepPartial$2<T> = T extends Builtin$2 ? T : T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial$2<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial$2<U>> : T extends {} ? { [K in keyof T]?: DeepPartial$2<T[K]> } : Partial<T>;
47
+ type KeysOfUnion$2<T> = T extends T ? keyof T : never;
48
+ type Exact$2<P, I extends P> = P extends Builtin$2 ? P : P & { [K in keyof P]: Exact$2<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion$2<P>>]: never };
49
+ interface MessageFns$2<T> {
50
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
51
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
52
+ fromJSON(object: any): T;
53
+ toJSON(message: T): unknown;
54
+ create<I extends Exact$2<DeepPartial$2<T>, I>>(base?: I): T;
55
+ fromPartial<I extends Exact$2<DeepPartial$2<T>, I>>(object: I): T;
56
+ }
57
+ //#endregion
58
+ //#region src/proto/types.d.ts
5
59
  declare enum ResolveReason {
6
60
  /** RESOLVE_REASON_UNSPECIFIED - Unspecified enum. */
7
61
  RESOLVE_REASON_UNSPECIFIED = 0,
@@ -24,6 +78,8 @@ declare enum ResolveReason {
24
78
  RESOLVE_REASON_ERROR = 6,
25
79
  UNRECOGNIZED = -1,
26
80
  }
81
+ //#endregion
82
+ //#region src/proto/resolver/api.d.ts
27
83
  interface ResolveFlagsRequest {
28
84
  /**
29
85
  * If non-empty, the specific flags are resolved, otherwise all flags
@@ -50,6 +106,8 @@ interface ResolveFlagsRequest {
50
106
  * `apply` should likely be set to false.
51
107
  */
52
108
  apply: boolean;
109
+ /** Information about the SDK used to initiate the request. */
110
+ sdk?: Sdk | undefined;
53
111
  }
54
112
  interface ResolveFlagsResponse {
55
113
  /**
@@ -80,10 +138,6 @@ interface ResolvedFlag {
80
138
  /** The reason to why the flag could be resolved or not. */
81
139
  reason: ResolveReason;
82
140
  }
83
- interface SetResolverStateRequest {
84
- state: Uint8Array;
85
- accountId: string;
86
- }
87
141
  /** Request for resolving flags with sticky (materialized) assignments */
88
142
  interface ResolveWithStickyRequest {
89
143
  /** The standard resolve request */
@@ -143,18 +197,36 @@ interface ResolveWithStickyResponse_MaterializationUpdate {
143
197
  rule: string;
144
198
  variant: string;
145
199
  }
146
- declare const ResolveFlagsRequest: MessageFns<ResolveFlagsRequest>;
147
- declare const ResolveFlagsResponse: MessageFns<ResolveFlagsResponse>;
148
- declare const ResolvedFlag: MessageFns<ResolvedFlag>;
200
+ declare const ResolveFlagsRequest: MessageFns$1<ResolveFlagsRequest>;
201
+ declare const ResolveFlagsResponse: MessageFns$1<ResolveFlagsResponse>;
202
+ declare const ResolvedFlag: MessageFns$1<ResolvedFlag>;
203
+ declare const ResolveWithStickyRequest: MessageFns$1<ResolveWithStickyRequest>;
204
+ declare const MaterializationMap: MessageFns$1<MaterializationMap>;
205
+ declare const MaterializationInfo: MessageFns$1<MaterializationInfo>;
206
+ declare const ResolveWithStickyResponse: MessageFns$1<ResolveWithStickyResponse>;
207
+ declare const ResolveWithStickyResponse_Success: MessageFns$1<ResolveWithStickyResponse_Success>;
208
+ declare const ResolveWithStickyResponse_MissingMaterializations: MessageFns$1<ResolveWithStickyResponse_MissingMaterializations>;
209
+ declare const ResolveWithStickyResponse_MissingMaterializationItem: MessageFns$1<ResolveWithStickyResponse_MissingMaterializationItem>;
210
+ declare const ResolveWithStickyResponse_MaterializationUpdate: MessageFns$1<ResolveWithStickyResponse_MaterializationUpdate>;
211
+ type Builtin$1 = Date | Function | Uint8Array | string | number | boolean | undefined;
212
+ type DeepPartial$1<T> = T extends Builtin$1 ? T : T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial$1<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial$1<U>> : T extends {} ? { [K in keyof T]?: DeepPartial$1<T[K]> } : Partial<T>;
213
+ type KeysOfUnion$1<T> = T extends T ? keyof T : never;
214
+ type Exact$1<P, I extends P> = P extends Builtin$1 ? P : P & { [K in keyof P]: Exact$1<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion$1<P>>]: never };
215
+ interface MessageFns$1<T> {
216
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
217
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
218
+ fromJSON(object: any): T;
219
+ toJSON(message: T): unknown;
220
+ create<I extends Exact$1<DeepPartial$1<T>, I>>(base?: I): T;
221
+ fromPartial<I extends Exact$1<DeepPartial$1<T>, I>>(object: I): T;
222
+ }
223
+ //#endregion
224
+ //#region src/proto/messages.d.ts
225
+ interface SetResolverStateRequest {
226
+ state: Uint8Array;
227
+ accountId: string;
228
+ }
149
229
  declare const SetResolverStateRequest: MessageFns<SetResolverStateRequest>;
150
- declare const ResolveWithStickyRequest: MessageFns<ResolveWithStickyRequest>;
151
- declare const MaterializationMap: MessageFns<MaterializationMap>;
152
- declare const MaterializationInfo: MessageFns<MaterializationInfo>;
153
- declare const ResolveWithStickyResponse: MessageFns<ResolveWithStickyResponse>;
154
- declare const ResolveWithStickyResponse_Success: MessageFns<ResolveWithStickyResponse_Success>;
155
- declare const ResolveWithStickyResponse_MissingMaterializations: MessageFns<ResolveWithStickyResponse_MissingMaterializations>;
156
- declare const ResolveWithStickyResponse_MissingMaterializationItem: MessageFns<ResolveWithStickyResponse_MissingMaterializationItem>;
157
- declare const ResolveWithStickyResponse_MaterializationUpdate: MessageFns<ResolveWithStickyResponse_MaterializationUpdate>;
158
230
  type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
159
231
  type DeepPartial<T> = T extends Builtin ? T : T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> } : Partial<T>;
160
232
  type KeysOfUnion<T> = T extends T ? keyof T : never;
@@ -178,8 +250,6 @@ interface LocalResolver {
178
250
  //#region src/ConfidenceServerProviderLocal.d.ts
179
251
  interface ProviderOptions {
180
252
  flagClientSecret: string;
181
- apiClientId: string;
182
- apiClientSecret: string;
183
253
  initializeTimeout?: number;
184
254
  flushInterval?: number;
185
255
  fetch?: typeof fetch;
@@ -216,8 +286,6 @@ declare class ConfidenceServerProviderLocal implements Provider {
216
286
  private extractValue;
217
287
  updateState(signal?: AbortSignal): Promise<void>;
218
288
  flush(signal?: AbortSignal): Promise<void>;
219
- private fetchResolveStateUri;
220
- private fetchToken;
221
289
  private static convertReason;
222
290
  private static convertEvaluationContext;
223
291
  /** Resolves with an evaluation of a Boolean flag */