|
| 1 | +## @spotify-confidence/openfeature-server-provider-local |
| 2 | + |
| 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 | + |
| 5 | +### Features |
| 6 | +- Local flag evaluation via WASM (no per-eval network calls) |
| 7 | +- Automatic state refresh and batched flag log flushing |
| 8 | +- Pluggable `fetch` with retries, timeouts and routing |
| 9 | +- Optional logging using `debug` |
| 10 | + |
| 11 | +### Requirements |
| 12 | +- Node.js 18+ (built-in `fetch`) or provide a compatible `fetch` |
| 13 | +- WebAssembly support (Node 18+/modern browsers) |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Installation |
| 18 | + |
| 19 | +```bash |
| 20 | +yarn add @spotify-confidence/openfeature-server-provider-local |
| 21 | + |
| 22 | +# Optional: enable logs by installing the peer dependency |
| 23 | +yarn add debug |
| 24 | +``` |
| 25 | + |
| 26 | +Notes: |
| 27 | +- `debug` is an optional peer. Install it if you want logs. Without it, logging is a no-op. |
| 28 | +- Types and bundling are ESM-first; Node is supported, and a browser build is provided for modern bundlers. |
| 29 | + |
| 30 | +--- |
| 31 | + |
| 32 | +## Quick start (Node) |
| 33 | + |
| 34 | +```ts |
| 35 | +import { OpenFeature } from '@openfeature/server-sdk'; |
| 36 | +import { createConfidenceServerProvider } from '@spotify-confidence/openfeature-server-provider-local'; |
| 37 | + |
| 38 | +const provider = createConfidenceServerProvider({ |
| 39 | + flagClientSecret: process.env.CONFIDENCE_FLAG_CLIENT_SECRET!, |
| 40 | + apiClientId: process.env.CONFIDENCE_API_CLIENT_ID!, |
| 41 | + apiClientSecret: process.env.CONFIDENCE_API_CLIENT_SECRET!, |
| 42 | + // initializeTimeout?: number |
| 43 | + // flushInterval?: number |
| 44 | + // fetch?: typeof fetch (Node <18 or custom transport) |
| 45 | +}); |
| 46 | + |
| 47 | +// Wait for the provider to be ready (fetches initial resolver state) |
| 48 | +await OpenFeature.setProviderAndWait(provider); |
| 49 | + |
| 50 | +const client = OpenFeature.getClient(); |
| 51 | + |
| 52 | +// Evaluate a boolean flag |
| 53 | +const details = await client.getBooleanDetails('my-flag', false, { targetingKey: 'user-123' }); |
| 54 | +console.log(details.value, details.reason); |
| 55 | + |
| 56 | +// Evaluate a nested value from an object flag using dot-path |
| 57 | +// e.g. flag key "experiments" with payload { groupA: { ratio: 0.5 } } |
| 58 | +const ratio = await client.getNumberValue('experiments.groupA.ratio', 0, { targetingKey: 'user-123' }); |
| 59 | + |
| 60 | +// On shutdown, flush any pending logs |
| 61 | +await provider.onClose(); |
| 62 | +``` |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +## Options |
| 67 | + |
| 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. |
| 74 | + |
| 75 | +The provider periodically: |
| 76 | +- Refreshes resolver state (default every 30s) |
| 77 | +- Flushes flag evaluation logs to the backend |
| 78 | + |
| 79 | +--- |
| 80 | + |
| 81 | +## Logging (optional) |
| 82 | + |
| 83 | +Logging uses the `debug` library if present; otherwise, all log calls are no-ops. |
| 84 | + |
| 85 | +Namespaces: |
| 86 | +- Core: `cnfd:*` |
| 87 | +- Fetch/middleware: `cnfd:fetch:*` (e.g. retries, auth renewals, request summaries) |
| 88 | + |
| 89 | +Enable logs: |
| 90 | + |
| 91 | +- Node: |
| 92 | +```bash |
| 93 | +DEBUG=cnfd:* node app.js |
| 94 | +# or narrower |
| 95 | +DEBUG=cnfd:info,cnfd:error,cnfd:fetch:* node app.js |
| 96 | +``` |
| 97 | + |
| 98 | +- Browser (in DevTools console): |
| 99 | +```js |
| 100 | +localStorage.debug = 'cnfd:*'; |
| 101 | +``` |
| 102 | + |
| 103 | +Install `debug` if you haven’t: |
| 104 | + |
| 105 | +```bash |
| 106 | +yarn add debug |
| 107 | +``` |
| 108 | + |
| 109 | +--- |
| 110 | + |
| 111 | +## WebAssembly asset notes |
| 112 | + |
| 113 | +- Node: the WASM (`confidence_resolver.wasm`) is resolved from the installed package automatically; no extra config needed. |
| 114 | +- Browser: the ESM build resolves the WASM via `new URL('confidence_resolver.wasm', import.meta.url)` so modern bundlers (Vite/Rollup/Webpack 5 asset modules) will include it. If your bundler does not, configure it to treat the `.wasm` file as a static asset. |
| 115 | + |
| 116 | +--- |
| 117 | + |
| 118 | +## Using in browsers |
| 119 | + |
| 120 | +The package exports a browser ESM build that compiles the WASM via streaming and uses the global `fetch`. Integrate it with your OpenFeature SDK variant for the web similarly to Node, then register the provider before evaluation. Credentials must be available to the runtime (e.g. through your app’s configuration layer). |
| 121 | + |
| 122 | +--- |
| 123 | + |
| 124 | +## Testing |
| 125 | + |
| 126 | +- You can inject a custom `fetch` via the `fetch` option to stub network behavior in tests. |
| 127 | +- The provider batches logs; call `await provider.onClose()` in tests to flush them deterministically. |
| 128 | + |
| 129 | +--- |
| 130 | + |
| 131 | +## Troubleshooting |
| 132 | + |
| 133 | +- Provider stuck in NOT_READY/ERROR: |
| 134 | + - Verify `apiClientId`/`apiClientSecret` and `flagClientSecret` are correct. |
| 135 | + - Ensure outbound access to Confidence endpoints and GCS. |
| 136 | + - Enable `DEBUG=cnfd:*` for more detail. |
| 137 | + |
| 138 | +- No logs appear: |
| 139 | + - Install `debug` and enable the appropriate namespaces. |
| 140 | + - Check that your environment variable/`localStorage.debug` is set before your app initializes the provider. |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +## License |
| 145 | + |
| 146 | +See the root `LICENSE`. |
0 commit comments