@dr-sentry/react 1.0.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.
Files changed (59) hide show
  1. package/README.md +258 -0
  2. package/dist/cjs/client.d.ts +121 -0
  3. package/dist/cjs/client.d.ts.map +1 -0
  4. package/dist/cjs/client.js +483 -0
  5. package/dist/cjs/client.js.map +1 -0
  6. package/dist/cjs/context.d.ts +10 -0
  7. package/dist/cjs/context.d.ts.map +1 -0
  8. package/dist/cjs/context.js +14 -0
  9. package/dist/cjs/context.js.map +1 -0
  10. package/dist/cjs/hooks.d.ts +86 -0
  11. package/dist/cjs/hooks.d.ts.map +1 -0
  12. package/dist/cjs/hooks.js +186 -0
  13. package/dist/cjs/hooks.js.map +1 -0
  14. package/dist/cjs/index.d.ts +7 -0
  15. package/dist/cjs/index.d.ts.map +1 -0
  16. package/dist/cjs/index.js +23 -0
  17. package/dist/cjs/index.js.map +1 -0
  18. package/dist/cjs/local-evaluator.d.ts +12 -0
  19. package/dist/cjs/local-evaluator.d.ts.map +1 -0
  20. package/dist/cjs/local-evaluator.js +44 -0
  21. package/dist/cjs/local-evaluator.js.map +1 -0
  22. package/dist/cjs/package.json +1 -0
  23. package/dist/cjs/provider.d.ts +24 -0
  24. package/dist/cjs/provider.d.ts.map +1 -0
  25. package/dist/cjs/provider.js +97 -0
  26. package/dist/cjs/provider.js.map +1 -0
  27. package/dist/cjs/types.d.ts +163 -0
  28. package/dist/cjs/types.d.ts.map +1 -0
  29. package/dist/cjs/types.js +9 -0
  30. package/dist/cjs/types.js.map +1 -0
  31. package/dist/esm/client.d.ts +121 -0
  32. package/dist/esm/client.d.ts.map +1 -0
  33. package/dist/esm/client.js +479 -0
  34. package/dist/esm/client.js.map +1 -0
  35. package/dist/esm/context.d.ts +10 -0
  36. package/dist/esm/context.d.ts.map +1 -0
  37. package/dist/esm/context.js +11 -0
  38. package/dist/esm/context.js.map +1 -0
  39. package/dist/esm/hooks.d.ts +86 -0
  40. package/dist/esm/hooks.d.ts.map +1 -0
  41. package/dist/esm/hooks.js +178 -0
  42. package/dist/esm/hooks.js.map +1 -0
  43. package/dist/esm/index.d.ts +7 -0
  44. package/dist/esm/index.d.ts.map +1 -0
  45. package/dist/esm/index.js +10 -0
  46. package/dist/esm/index.js.map +1 -0
  47. package/dist/esm/local-evaluator.d.ts +12 -0
  48. package/dist/esm/local-evaluator.d.ts.map +1 -0
  49. package/dist/esm/local-evaluator.js +41 -0
  50. package/dist/esm/local-evaluator.js.map +1 -0
  51. package/dist/esm/provider.d.ts +24 -0
  52. package/dist/esm/provider.d.ts.map +1 -0
  53. package/dist/esm/provider.js +94 -0
  54. package/dist/esm/provider.js.map +1 -0
  55. package/dist/esm/types.d.ts +163 -0
  56. package/dist/esm/types.d.ts.map +1 -0
  57. package/dist/esm/types.js +8 -0
  58. package/dist/esm/types.js.map +1 -0
  59. package/package.json +58 -0
package/README.md ADDED
@@ -0,0 +1,258 @@
1
+ # @deploysentry/react
2
+
3
+ Official React SDK for the DeploySentry feature flag platform. Provides React hooks and a context provider for evaluating feature flags with real-time SSE updates.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @deploysentry/react
9
+ ```
10
+
11
+ React 18 or later is required as a peer dependency.
12
+
13
+ ## Quick Start
14
+
15
+ Wrap your application with `DeploySentryProvider` and use hooks anywhere in the tree:
16
+
17
+ ```tsx
18
+ import { DeploySentryProvider, useFlag } from '@deploysentry/react';
19
+
20
+ function App() {
21
+ return (
22
+ <DeploySentryProvider
23
+ apiKey="ds_live_abc123"
24
+ baseURL="https://api.dr-sentry.com"
25
+ environment="production"
26
+ project="my-app"
27
+ application="my-web-app"
28
+ user={{ id: 'user-42' }}
29
+ >
30
+ <MyComponent />
31
+ </DeploySentryProvider>
32
+ );
33
+ }
34
+
35
+ function MyComponent() {
36
+ const showBanner = useFlag('show-banner', false);
37
+
38
+ if (!showBanner) return null;
39
+ return <div>New feature banner!</div>;
40
+ }
41
+ ```
42
+
43
+ ## Provider Props
44
+
45
+ | Prop | Type | Required | Description |
46
+ |---------------|--------------|----------|--------------------------------------------------|
47
+ | `apiKey` | `string` | Yes | API key for authenticating with DeploySentry. |
48
+ | `baseURL` | `string` | Yes | Base URL of the DeploySentry API. |
49
+ | `environment` | `string` | Yes | Environment identifier (e.g. `"production"`). |
50
+ | `project` | `string` | Yes | Project identifier. |
51
+ | `application` | `string` | Yes | Application identifier |
52
+ | `user` | `UserContext` | No | User context for targeting rules. |
53
+ | `children` | `ReactNode` | Yes | React children. |
54
+ | `mode` | `string` | No | SDK mode: `server`, `file`, or `server-with-fallback`. |
55
+ | `flagData` | `object` | No | Pre-loaded flag config for file/fallback mode. |
56
+
57
+ ## Offline / File Mode
58
+
59
+ For offline use or testing, pass pre-loaded flag config via the `flagData` prop:
60
+
61
+ ```tsx
62
+ import flagConfig from './.deploysentry/flags.json';
63
+
64
+ <DeploySentryProvider
65
+ apiKey="not-used"
66
+ baseURL=""
67
+ environment="staging"
68
+ project="my-project"
69
+ application="my-web-app"
70
+ mode="file"
71
+ flagData={flagConfig}
72
+ >
73
+ <App />
74
+ </DeploySentryProvider>
75
+ ```
76
+
77
+ ### Modes
78
+
79
+ | Mode | Behavior |
80
+ | --- | --- |
81
+ | `server` (default) | API calls + SSE streaming |
82
+ | `file` | Use `flagData` prop, evaluate locally. No server contact. |
83
+ | `server-with-fallback` | Try server first. If unavailable, use `flagData` as fallback. |
84
+
85
+ Export the config from the dashboard (App Settings → Export flags.yaml), then convert to JSON or import with a YAML plugin.
86
+
87
+ ## Hooks
88
+
89
+ ### `useFlag(key, defaultValue)`
90
+
91
+ Returns the resolved flag value. Falls back to `defaultValue` while loading or if the flag does not exist.
92
+
93
+ ```tsx
94
+ const isEnabled = useFlag('dark-mode', false);
95
+ const variant = useFlag('checkout-flow', 'control');
96
+ ```
97
+
98
+ ### `useFlagDetail(key)`
99
+
100
+ Returns detailed evaluation information including metadata.
101
+
102
+ ```tsx
103
+ const { value, enabled, metadata, loading } = useFlagDetail('new-checkout');
104
+
105
+ // metadata contains: category, purpose, owners, isPermanent, expiresAt, tags
106
+ ```
107
+
108
+ ### `useFlagsByCategory(category)`
109
+
110
+ Returns all flags matching the given category.
111
+
112
+ ```tsx
113
+ const experiments = useFlagsByCategory('experiment');
114
+ const releases = useFlagsByCategory('release');
115
+ ```
116
+
117
+ Categories: `'release'` | `'feature'` | `'experiment'` | `'ops'` | `'permission'`
118
+
119
+ ### `useExpiredFlags()`
120
+
121
+ Returns all non-permanent flags whose `expiresAt` date is in the past. Useful for admin dashboards.
122
+
123
+ ```tsx
124
+ const expired = useExpiredFlags();
125
+ ```
126
+
127
+ ### `useDispatch(operation)`
128
+
129
+ Returns the resolved handler for a registered operation based on current flag state. See [Register / Dispatch Pattern](#register--dispatch-pattern) below.
130
+
131
+ ```tsx
132
+ const checkout = useDispatch<() => Promise<void>>('checkout');
133
+ await checkout();
134
+ ```
135
+
136
+ ### `useDeploySentry()`
137
+
138
+ Returns the raw `DeploySentryClient` instance for advanced use-cases.
139
+
140
+ ```tsx
141
+ const client = useDeploySentry();
142
+ ```
143
+
144
+ ## Register / Dispatch Pattern
145
+
146
+ The register/dispatch pattern centralizes all flag-gated behavior in one place instead of scattering `if/else` checks across components.
147
+
148
+ ### Setup (single-point registration)
149
+
150
+ Create one registration file that runs at app initialization:
151
+
152
+ ```typescript
153
+ // src/flags/registrations.ts
154
+ import type { DeploySentryClient } from '@deploysentry/react';
155
+ import { legacySearch, vectorSearch } from '../search';
156
+ import { classicCheckout, newCheckout } from '../checkout';
157
+
158
+ export function registerFlags(client: DeploySentryClient) {
159
+ // Search — default handler, then flag-gated override
160
+ client.register('search', legacySearch);
161
+ client.register('search', vectorSearch, 'vector-search-v2');
162
+
163
+ // Checkout
164
+ client.register('checkout', classicCheckout);
165
+ client.register('checkout', newCheckout, 'new-checkout-flow');
166
+ }
167
+ ```
168
+
169
+ Call `registerFlags(client)` after the client initializes (e.g. in the provider setup or an app-level effect).
170
+
171
+ ### Usage in components
172
+
173
+ ```tsx
174
+ import { useDispatch } from '@deploysentry/react';
175
+
176
+ function SearchBar() {
177
+ const search = useDispatch<(query: string) => Promise<Results>>('search');
178
+
179
+ return (
180
+ <input onChange={(e) => search(e.target.value).then(setResults)} />
181
+ );
182
+ }
183
+ ```
184
+
185
+ ### How it works
186
+
187
+ 1. **`client.register(operation, handler, flagKey?)`** — Register a handler for a named operation. With `flagKey`, it's only selected when that flag is enabled. Without `flagKey`, it's the default fallback.
188
+ 2. **`useDispatch(operation)`** — Returns the first registered handler whose flag is enabled, or the default handler. The component never knows about flags.
189
+
190
+ ### Why single-point registration
191
+
192
+ - One file shows every flag-gated behavior in the app
193
+ - Easy auditing — search for a flag key and find every behavior it controls
194
+ - Simple cleanup when a flag is retired (remove the registration, keep the handler)
195
+ - LLMs and code review tools can read one file to understand all flag-gated behavior
196
+
197
+ ## Real-Time Updates
198
+
199
+ The provider automatically opens an SSE connection to receive flag updates in real time. When a flag changes on the server, all components consuming that flag re-render immediately. The connection reconnects automatically with exponential backoff if it drops.
200
+
201
+ ## SSR Compatibility
202
+
203
+ Hooks return default/empty values during server-side rendering. Flags are fetched client-side after the provider mounts.
204
+
205
+ ## Types
206
+
207
+ All types are exported from the package:
208
+
209
+ ```tsx
210
+ import type {
211
+ Flag,
212
+ FlagCategory,
213
+ FlagDetail,
214
+ FlagMetadata,
215
+ ProviderProps,
216
+ UserContext,
217
+ } from '@deploysentry/react';
218
+ ```
219
+
220
+ ## Authentication
221
+
222
+ All requests use an API key passed in the `Authorization` header:
223
+
224
+ ```
225
+ Authorization: ApiKey <your-api-key>
226
+ ```
227
+
228
+ Pass the key via the provider's `apiKey` prop. The SDK sets the header automatically.
229
+
230
+ ### Session Consistency
231
+
232
+ Bind evaluations to a session so the server caches results for a consistent user experience:
233
+
234
+ ```tsx
235
+ <DeploySentryProvider
236
+ apiKey="ds_key_xxxxxxxxxxxx"
237
+ baseURL="https://api.dr-sentry.com"
238
+ environment="production"
239
+ project="my-app"
240
+ application="my-web-app"
241
+ sessionId={`user:${userId}`}
242
+ >
243
+ <App />
244
+ </DeploySentryProvider>
245
+ ```
246
+
247
+ ```tsx
248
+ const client = useDeploySentry();
249
+ await client.refreshSession();
250
+ ```
251
+
252
+ - The session ID is sent as an `X-DeploySentry-Session` header on every request.
253
+ - The server caches evaluation results per session for 30 minutes (sliding TTL).
254
+ - Omit the session ID to always get fresh evaluations on each request.
255
+
256
+ ## License
257
+
258
+ Apache-2.0
@@ -0,0 +1,121 @@
1
+ import type { EvaluationContext, Flag, FlagConfig, FlagDetail, FlagMetadata, UserContext } from './types';
2
+ /** Listener callback invoked whenever the flag store is updated. */
3
+ export type FlagChangeListener = () => void;
4
+ /**
5
+ * Lightweight HTTP + SSE client that manages the local flag store.
6
+ *
7
+ * This client is designed for browser environments. It uses the native
8
+ * `fetch` and `EventSource` APIs, so no polyfills are required in modern
9
+ * browsers.
10
+ */
11
+ export declare class DeploySentryClient {
12
+ private readonly apiKey;
13
+ private readonly baseURL;
14
+ private readonly environment;
15
+ private readonly project;
16
+ private readonly application;
17
+ private readonly sessionId;
18
+ private readonly mode;
19
+ private flagConfig;
20
+ private user;
21
+ /** In-memory flag store keyed by flag key. */
22
+ private readonly flags;
23
+ /** Registry of operation handlers for the register/dispatch pattern. */
24
+ private registry;
25
+ /** Subscribed listeners notified on any flag change. */
26
+ private readonly listeners;
27
+ /** Active SSE connection, if any. */
28
+ private eventSource;
29
+ /** Whether the initial fetch has completed at least once. */
30
+ private initialised;
31
+ /** Whether the client has been destroyed. */
32
+ private destroyed;
33
+ /** Retry state for SSE reconnection. */
34
+ private sseRetryTimer;
35
+ private sseRetryCount;
36
+ private static readonly SSE_MAX_RETRY_DELAY_MS;
37
+ constructor(options: {
38
+ apiKey: string;
39
+ baseURL: string;
40
+ environment: string;
41
+ project: string;
42
+ application: string;
43
+ user?: UserContext;
44
+ sessionId?: string;
45
+ mode?: 'server' | 'file' | 'server-with-fallback';
46
+ flagConfig?: FlagConfig;
47
+ });
48
+ /** Fetch all flags from the API and start listening for SSE updates. */
49
+ init(): Promise<void>;
50
+ /** Stop the SSE connection and release resources. */
51
+ destroy(): void;
52
+ /** Clear the local flag store and re-fetch all flags from the API. */
53
+ refreshSession(): Promise<void>;
54
+ /** Update the user context and re-fetch flags. */
55
+ identify(user: UserContext | undefined): Promise<void>;
56
+ /** Subscribe to flag changes. Returns an unsubscribe function. */
57
+ subscribe(listener: FlagChangeListener): () => void;
58
+ /** True once the first successful fetch has completed. */
59
+ get isInitialised(): boolean;
60
+ /** Return the stored {@link Flag} for the given key, or `undefined`. */
61
+ getFlag(key: string): Flag | undefined;
62
+ /** Return all stored flags. */
63
+ getAllFlags(): Flag[];
64
+ /** Build a {@link FlagMetadata} object for a stored flag. */
65
+ getFlagMetadata(key: string): FlagMetadata | undefined;
66
+ /**
67
+ * Register a handler for the given operation name.
68
+ *
69
+ * When `flagKey` is provided the handler is used only when that flag is
70
+ * enabled. Omit `flagKey` to register the default (fallback) handler.
71
+ */
72
+ register<T extends (...args: any[]) => any>(operation: string, handler: T, flagKey?: string): void;
73
+ /**
74
+ * Dispatch the appropriate handler for the given operation.
75
+ *
76
+ * Returns the first registered handler whose flag is enabled. Falls back
77
+ * to the default (no-flagKey) handler if no flagged handler matches.
78
+ *
79
+ * @throws If no handlers are registered for the operation.
80
+ * @throws If no flagged handler matches and no default is registered.
81
+ */
82
+ dispatch<T extends (...args: any[]) => any>(operation: string, _context?: EvaluationContext): T;
83
+ /**
84
+ * Evaluate a boolean flag from the in-memory store.
85
+ *
86
+ * No API call is made -- the value comes from the flags already fetched
87
+ * via {@link init} and kept up-to-date by SSE.
88
+ */
89
+ boolValue(key: string, defaultValue: boolean): boolean;
90
+ /**
91
+ * Evaluate a string flag from the in-memory store.
92
+ */
93
+ stringValue(key: string, defaultValue: string): string;
94
+ /**
95
+ * Evaluate a number flag from the in-memory store.
96
+ *
97
+ * TypeScript has no separate `int` type -- this returns `number` and is
98
+ * the idiomatic equivalent of `intValue` / `floatValue` in other SDKs.
99
+ */
100
+ numberValue(key: string, defaultValue: number): number;
101
+ /**
102
+ * Evaluate a JSON (object) flag from the in-memory store.
103
+ */
104
+ jsonValue<T extends object = object>(key: string, defaultValue: T): T;
105
+ /**
106
+ * Return the full evaluation detail for a flag from the in-memory store.
107
+ *
108
+ * Returns `{ value, enabled, metadata, loading }` matching the
109
+ * {@link FlagDetail} interface used by the `useFlagDetail` hook.
110
+ */
111
+ detail(key: string): FlagDetail;
112
+ private get headers();
113
+ private buildQueryParams;
114
+ private fetchFlags;
115
+ private connectSSE;
116
+ private handleSSEMessage;
117
+ private disconnectSSE;
118
+ private scheduleReconnect;
119
+ private emit;
120
+ }
121
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}