@dr-sentry/sdk 1.0.4 → 1.1.1
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 +101 -4
- package/dist/cjs/cache.d.ts +16 -0
- package/dist/cjs/cache.d.ts.map +1 -1
- package/dist/cjs/cache.js +21 -1
- package/dist/cjs/cache.js.map +1 -1
- package/dist/cjs/client.d.ts +46 -1
- package/dist/cjs/client.d.ts.map +1 -1
- package/dist/cjs/client.js +236 -9
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/defaults.d.ts +58 -0
- package/dist/cjs/defaults.d.ts.map +1 -0
- package/dist/cjs/defaults.js +175 -0
- package/dist/cjs/defaults.js.map +1 -0
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/status-reporter.d.ts +42 -0
- package/dist/cjs/status-reporter.d.ts.map +1 -0
- package/dist/cjs/status-reporter.js +153 -0
- package/dist/cjs/status-reporter.js.map +1 -0
- package/dist/cjs/streaming.d.ts +8 -2
- package/dist/cjs/streaming.d.ts.map +1 -1
- package/dist/cjs/streaming.js +17 -4
- package/dist/cjs/streaming.js.map +1 -1
- package/dist/cjs/types.d.ts +65 -0
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/esm/cache.d.ts +16 -0
- package/dist/esm/cache.d.ts.map +1 -1
- package/dist/esm/cache.js +21 -1
- package/dist/esm/cache.js.map +1 -1
- package/dist/esm/client.d.ts +46 -1
- package/dist/esm/client.d.ts.map +1 -1
- package/dist/esm/client.js +236 -9
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/defaults.d.ts +58 -0
- package/dist/esm/defaults.d.ts.map +1 -0
- package/dist/esm/defaults.js +136 -0
- package/dist/esm/defaults.js.map +1 -0
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/status-reporter.d.ts +42 -0
- package/dist/esm/status-reporter.d.ts.map +1 -0
- package/dist/esm/status-reporter.js +148 -0
- package/dist/esm/status-reporter.js.map +1 -0
- package/dist/esm/streaming.d.ts +8 -2
- package/dist/esm/streaming.d.ts.map +1 -1
- package/dist/esm/streaming.js +17 -4
- package/dist/esm/streaming.js.map +1 -1
- package/dist/esm/types.d.ts +65 -0
- package/dist/esm/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,42 @@ const config = await client.jsonValue('rate-limits', { rpm: 100 });
|
|
|
40
40
|
client.close();
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
## Status reporting (optional)
|
|
44
|
+
|
|
45
|
+
Enable `reportStatus` to have the SDK push version + health to DeploySentry automatically. No separate startup code needed — the SDK posts `POST /applications/:id/status` on init and on an interval.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const client = new DeploySentryClient({
|
|
49
|
+
apiKey: process.env.DS_API_KEY!,
|
|
50
|
+
environment: 'production',
|
|
51
|
+
project: 'my-app',
|
|
52
|
+
application: 'my-web-app',
|
|
53
|
+
|
|
54
|
+
// Agentless status reporting
|
|
55
|
+
applicationId: process.env.DS_APPLICATION_ID!, // UUID of the app
|
|
56
|
+
reportStatus: true,
|
|
57
|
+
reportStatusIntervalMs: 30_000, // default; 0 = startup-only
|
|
58
|
+
reportStatusVersion: process.env.APP_VERSION, // optional override
|
|
59
|
+
reportStatusCommitSha: process.env.GIT_SHA,
|
|
60
|
+
reportStatusDeploySlot: process.env.DS_DEPLOY_SLOT, // "stable" | "canary"
|
|
61
|
+
reportStatusTags: { region: process.env.REGION ?? 'unknown' },
|
|
62
|
+
reportStatusHealthProvider: async () => ({
|
|
63
|
+
state: (await isDbUp()) ? 'healthy' : 'degraded',
|
|
64
|
+
score: 0.99,
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await client.initialize();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The API key must carry the `status:write` scope and be scoped to a single application + environment. When `reportStatus` is `true` but `applicationId` is omitted, the reporter logs a warning and is disabled — flag evaluation continues to work.
|
|
72
|
+
|
|
73
|
+
Without a `reportStatusHealthProvider`, the SDK sends `health: "healthy"` on every tick ("process alive" floor). Supply a provider if your app can detect degradation (stale DB, downstream outages, etc.).
|
|
74
|
+
|
|
75
|
+
Version auto-detection (when `reportStatusVersion` is omitted) checks these env vars in order: `APP_VERSION`, `GIT_SHA`, `GIT_COMMIT`, `SOURCE_COMMIT`, `RAILWAY_GIT_COMMIT_SHA`, `RENDER_GIT_COMMIT`, `VERCEL_GIT_COMMIT_SHA`, `HEROKU_SLUG_COMMIT`, `npm_package_version` — falling back to the literal string `"unknown"`.
|
|
76
|
+
|
|
77
|
+
The first `/status` report with a version DeploySentry has not seen auto-creates a `mode=record` deployment with `source="app-push"`, so deploy history populates even without a Railway/Render/etc. webhook configured.
|
|
78
|
+
|
|
43
79
|
## Evaluation Context
|
|
44
80
|
|
|
45
81
|
Pass targeting context with every evaluation:
|
|
@@ -113,6 +149,36 @@ List all cached flags:
|
|
|
113
149
|
const flags = client.allFlags();
|
|
114
150
|
```
|
|
115
151
|
|
|
152
|
+
## Inspecting Flag Sources (admin / support view)
|
|
153
|
+
|
|
154
|
+
`inspect()` returns a provenance-tagged snapshot of every flag the SDK currently knows about — drawn from all sources at once (live server cache, offline file config, and offline defaults). Each entry reports the resolved value, its type, **where the value came from**, an evaluation reason, and when it was last fetched. Wire it into an admin or support panel to see exactly what an application is serving and why.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const view = client.inspect();
|
|
158
|
+
// [
|
|
159
|
+
// {
|
|
160
|
+
// key: 'new-checkout',
|
|
161
|
+
// value: true,
|
|
162
|
+
// type: 'boolean',
|
|
163
|
+
// enabled: true,
|
|
164
|
+
// source: 'server', // 'server' | 'cache' | 'file' | 'default'
|
|
165
|
+
// reason: 'LIVE', // LIVE | CACHE | OFFLINE_FILE | OFFLINE_DEFAULT | DEFAULT
|
|
166
|
+
// fetchedAt: '2026-06-23T12:00:00.000Z',
|
|
167
|
+
// stale: false, // true when a cached value is past its TTL
|
|
168
|
+
// },
|
|
169
|
+
// ...
|
|
170
|
+
// ]
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
| `source` | Meaning |
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| `server` | Fetched live from the API and still within its cache TTL. |
|
|
176
|
+
| `cache` | A previously-fetched server value past its TTL, still served because the server is currently unreachable (`stale: true`). |
|
|
177
|
+
| `file` | An offline file — a `mode: 'file'` config, or the offline defaults loaded via `loadDefaults*()` (the `reason` distinguishes `OFFLINE_FILE` vs `OFFLINE_DEFAULT`). |
|
|
178
|
+
| `default` | No value from any source; callers receive their coded `defaultValue`. |
|
|
179
|
+
|
|
180
|
+
`inspect()` is a read-only snapshot — it does not contact the server or evaluate targeting rules for a specific user.
|
|
181
|
+
|
|
116
182
|
## Register / Dispatch Pattern
|
|
117
183
|
|
|
118
184
|
The register/dispatch pattern lets you centralize all flag-gated behavior in one place instead of scattering `if/else` checks throughout your codebase. Register named operations with their handlers and optional flag keys, then dispatch by operation name at the call site.
|
|
@@ -191,6 +257,37 @@ const value = await client.boolValue('any-flag', true);
|
|
|
191
257
|
// Returns the default value: true
|
|
192
258
|
```
|
|
193
259
|
|
|
260
|
+
## Offline Defaults
|
|
261
|
+
|
|
262
|
+
For deployments where the SDK may briefly be unable to reach DeploySentry (CI runs without network, cold-start before SSE connects, air-gapped builds), commit a flag snapshot to your repo and load it as fallback. The SDK still prefers the live API; defaults only fire when both the API and the in-process cache are empty.
|
|
263
|
+
|
|
264
|
+
Generate the snapshot with the CLI:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
deploysentry flags export --application web --env production -f flags.json
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Then load it after constructing the client:
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
const client = new DeploySentryClient({
|
|
274
|
+
apiKey: process.env.DEPLOYSENTRY_API_KEY!,
|
|
275
|
+
environment: 'production',
|
|
276
|
+
project: 'my-app',
|
|
277
|
+
application: 'web',
|
|
278
|
+
});
|
|
279
|
+
await client.loadDefaultsFromFile('flags.json');
|
|
280
|
+
// or sync from an already-parsed object:
|
|
281
|
+
// client.loadDefaults(JSON.parse(fs.readFileSync('flags.json', 'utf-8')));
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Evaluation order:
|
|
285
|
+
|
|
286
|
+
1. **Live API** when reachable.
|
|
287
|
+
2. **In-process cache** (entries from prior live calls).
|
|
288
|
+
3. **Offline defaults** loaded above — picks the per-environment value matching your `environment` option (by UUID first, then by name, case-insensitive). Falls back to the flag's global `default_value` when no env matches.
|
|
289
|
+
4. **Caller's `defaultValue`** when the flag isn't in the export.
|
|
290
|
+
|
|
194
291
|
## Configuration
|
|
195
292
|
|
|
196
293
|
| Option | Type | Required | Default | Description |
|
|
@@ -207,15 +304,15 @@ const value = await client.boolValue('any-flag', true);
|
|
|
207
304
|
|
|
208
305
|
## Offline / File Mode
|
|
209
306
|
|
|
210
|
-
The SDK can load flag configurations from a local YAML file
|
|
307
|
+
The SDK can load flag configurations from a local YAML/JSON file. **The server is always the source of truth** — the exported file is a *backup/fallback only*. In production use `server-with-fallback`: the SDK reaches the server first on every evaluation and consults the local file only when the server (and the in-process cache) are unreachable. Pure `file` mode (no server contact) is intended for local development, CI, and testing — not for normal production operation.
|
|
211
308
|
|
|
212
309
|
### Modes
|
|
213
310
|
|
|
214
311
|
| Mode | Behavior |
|
|
215
312
|
| --- | --- |
|
|
216
|
-
| `server` (default) | API calls + SSE streaming |
|
|
217
|
-
| `file` | Load from
|
|
218
|
-
| `server-with-fallback` |
|
|
313
|
+
| `server` (default) | API calls + SSE streaming. Server only. |
|
|
314
|
+
| `file` | Load from local file, evaluate locally. **No server contact — dev/CI/testing only.** |
|
|
315
|
+
| `server-with-fallback` | **Recommended for resilience.** Always try the server first; fall back to the local file only when the server is unreachable, then return to live as soon as it recovers. |
|
|
219
316
|
|
|
220
317
|
### Usage
|
|
221
318
|
|
package/dist/cjs/cache.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { Flag } from './types';
|
|
2
|
+
/** A cache entry exposed for inspection, including staleness. */
|
|
3
|
+
export interface CacheInspectionEntry {
|
|
4
|
+
flag: Flag;
|
|
5
|
+
/** Epoch-ms when the entry was last written. */
|
|
6
|
+
storedAt: number;
|
|
7
|
+
/** Epoch-ms when the entry's TTL elapses. */
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
/** True when the TTL has elapsed but the entry is still held. */
|
|
10
|
+
stale: boolean;
|
|
11
|
+
}
|
|
2
12
|
/**
|
|
3
13
|
* In-memory flag cache with per-entry TTL support.
|
|
4
14
|
*
|
|
@@ -21,6 +31,12 @@ export declare class FlagCache {
|
|
|
21
31
|
get(key: string): Flag | undefined;
|
|
22
32
|
/** Return all non-expired flags currently in the cache. */
|
|
23
33
|
getAll(): Flag[];
|
|
34
|
+
/**
|
|
35
|
+
* Return every entry held in the cache — including stale (TTL-elapsed)
|
|
36
|
+
* entries — without evicting anything. For inspection / admin views only;
|
|
37
|
+
* read paths should use {@link get} / {@link getAll} which evict.
|
|
38
|
+
*/
|
|
39
|
+
entries(): CacheInspectionEntry[];
|
|
24
40
|
/** Remove a single key from the cache. */
|
|
25
41
|
delete(key: string): void;
|
|
26
42
|
/** Remove all expired entries. Returns the number of entries purged. */
|
package/dist/cjs/cache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAQ/B,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,IAAI,CAAC;IACX,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;GAKG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B;;;OAGG;gBACS,KAAK,GAAE,MAAe;IAIlC,kDAAkD;IAClD,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IASrB,sDAAsD;IACtD,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI;IAM5B,yEAAyE;IACzE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAYlC,2DAA2D;IAC3D,MAAM,IAAI,IAAI,EAAE;IAehB;;;;OAIG;IACH,OAAO,IAAI,oBAAoB,EAAE;IAcjC,0CAA0C;IAC1C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,wEAAwE;IACxE,YAAY,IAAI,MAAM;IActB,uCAAuC;IACvC,KAAK,IAAI,IAAI;IAIb,8DAA8D;IAC9D,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
package/dist/cjs/cache.js
CHANGED
|
@@ -19,9 +19,11 @@ class FlagCache {
|
|
|
19
19
|
}
|
|
20
20
|
/** Store or update a single flag in the cache. */
|
|
21
21
|
set(flag) {
|
|
22
|
+
const now = Date.now();
|
|
22
23
|
this.store.set(flag.key, {
|
|
23
24
|
flag,
|
|
24
|
-
|
|
25
|
+
storedAt: now,
|
|
26
|
+
expiresAt: now + this.ttlMs,
|
|
25
27
|
});
|
|
26
28
|
}
|
|
27
29
|
/** Bulk-insert flags, replacing any stale entries. */
|
|
@@ -55,6 +57,24 @@ class FlagCache {
|
|
|
55
57
|
}
|
|
56
58
|
return result;
|
|
57
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Return every entry held in the cache — including stale (TTL-elapsed)
|
|
62
|
+
* entries — without evicting anything. For inspection / admin views only;
|
|
63
|
+
* read paths should use {@link get} / {@link getAll} which evict.
|
|
64
|
+
*/
|
|
65
|
+
entries() {
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
const result = [];
|
|
68
|
+
for (const entry of this.store.values()) {
|
|
69
|
+
result.push({
|
|
70
|
+
flag: entry.flag,
|
|
71
|
+
storedAt: entry.storedAt,
|
|
72
|
+
expiresAt: entry.expiresAt,
|
|
73
|
+
stale: now > entry.expiresAt,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
58
78
|
/** Remove a single key from the cache. */
|
|
59
79
|
delete(key) {
|
|
60
80
|
this.store.delete(key);
|
package/dist/cjs/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClientOptions, EvaluationContext, EvaluationResult, Flag, FlagCategory } from './types';
|
|
1
|
+
import { ClientOptions, EvaluationContext, EvaluationResult, Flag, FlagCategory, FlagInspection } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* DeploySentry feature-flag client.
|
|
4
4
|
*
|
|
@@ -26,11 +26,18 @@ export declare class DeploySentryClient {
|
|
|
26
26
|
private readonly sessionId;
|
|
27
27
|
private readonly mode;
|
|
28
28
|
private readonly flagFilePath?;
|
|
29
|
+
private readonly onFlagChange?;
|
|
29
30
|
private readonly cache;
|
|
30
31
|
private streamClient;
|
|
31
32
|
private _initialized;
|
|
32
33
|
private flagConfig;
|
|
34
|
+
private offlineDefaults;
|
|
35
|
+
private offlineDefaultsEnvUUID;
|
|
36
|
+
private flagConfigLoadedAt?;
|
|
37
|
+
private offlineDefaultsLoadedAt?;
|
|
33
38
|
private registry;
|
|
39
|
+
private statusReporter;
|
|
40
|
+
private readonly statusReporterOptions;
|
|
34
41
|
constructor(options: ClientOptions);
|
|
35
42
|
/**
|
|
36
43
|
* Fetch the initial flag set and open an SSE connection for real-time
|
|
@@ -39,6 +46,7 @@ export declare class DeploySentryClient {
|
|
|
39
46
|
initialize(): Promise<void>;
|
|
40
47
|
/** Tear down the SSE connection and release resources. */
|
|
41
48
|
close(): void;
|
|
49
|
+
private startStatusReporter;
|
|
42
50
|
/**
|
|
43
51
|
* Clear the local cache and re-fetch all flags from the server.
|
|
44
52
|
* Useful when session state may have changed and the client needs
|
|
@@ -68,10 +76,47 @@ export declare class DeploySentryClient {
|
|
|
68
76
|
flagOwners(key: string): string[];
|
|
69
77
|
/** Return every flag currently held in the local cache. */
|
|
70
78
|
allFlags(): Flag[];
|
|
79
|
+
/**
|
|
80
|
+
* Return a provenance-tagged view of every flag the SDK currently knows
|
|
81
|
+
* about, drawn from all sources (live server cache, offline file config,
|
|
82
|
+
* and offline defaults). Each entry reports the resolved value, its type,
|
|
83
|
+
* the {@link FlagSource | source} it came from, an evaluation reason code,
|
|
84
|
+
* and when it was last fetched / loaded.
|
|
85
|
+
*
|
|
86
|
+
* Intended for wiring into an admin or support panel so operators can see
|
|
87
|
+
* exactly what value an application is serving and where it came from. This
|
|
88
|
+
* is a read-only snapshot — it does not contact the server or evaluate
|
|
89
|
+
* targeting rules for a specific user.
|
|
90
|
+
*/
|
|
91
|
+
inspect(): FlagInspection[];
|
|
71
92
|
register<T extends (...args: any[]) => any>(operation: string, handler: T, flagKey?: string): void;
|
|
72
93
|
dispatch<T extends (...args: any[]) => any>(operation: string, _context?: EvaluationContext): T;
|
|
73
94
|
private evaluate;
|
|
95
|
+
/**
|
|
96
|
+
* Load offline default values from a flag-export file. When the live API
|
|
97
|
+
* is unreachable and the cache has no entry, the SDK falls back to these
|
|
98
|
+
* defaults for {@link boolValue} / {@link stringValue} / {@link intValue}
|
|
99
|
+
* / {@link jsonValue} calls.
|
|
100
|
+
*
|
|
101
|
+
* Accepts the JSON (default) or YAML format produced by
|
|
102
|
+
* `deploysentry flags export`.
|
|
103
|
+
*
|
|
104
|
+
* Environment matching: the SDK matches the configured `environment`
|
|
105
|
+
* option against the export's `environments[]` first by UUID, then by
|
|
106
|
+
* name (case-insensitive). When a match is found, that env's per-flag
|
|
107
|
+
* `{enabled, value}` overrides the flag's global `default_value`.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* await client.loadDefaultsFromFile('flags.json');
|
|
111
|
+
*/
|
|
112
|
+
loadDefaultsFromFile(path: string): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Synchronous variant of {@link loadDefaultsFromFile} that accepts an
|
|
115
|
+
* already-parsed object (or a JSON string).
|
|
116
|
+
*/
|
|
117
|
+
loadDefaults(payload: unknown): void;
|
|
74
118
|
private fetchAllFlags;
|
|
119
|
+
private fetchFlag;
|
|
75
120
|
private post;
|
|
76
121
|
private request;
|
|
77
122
|
private authHeaders;
|
package/dist/cjs/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,IAAI,EACJ,YAAY,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/client.js
CHANGED
|
@@ -5,13 +5,32 @@ const cache_1 = require("./cache");
|
|
|
5
5
|
const streaming_1 = require("./streaming");
|
|
6
6
|
const file_loader_1 = require("./file-loader");
|
|
7
7
|
const local_evaluator_1 = require("./local-evaluator");
|
|
8
|
+
const defaults_1 = require("./defaults");
|
|
9
|
+
const status_reporter_1 = require("./status-reporter");
|
|
8
10
|
const DEFAULT_BASE_URL = 'https://api.dr-sentry.com';
|
|
11
|
+
/** Parse a string value into its typed representation based on flag_type. */
|
|
12
|
+
function parseValue(raw, flagType) {
|
|
13
|
+
if (!raw && raw !== '0')
|
|
14
|
+
return undefined;
|
|
15
|
+
switch (flagType) {
|
|
16
|
+
case 'boolean': return raw === 'true';
|
|
17
|
+
case 'integer':
|
|
18
|
+
case 'number': return Number(raw);
|
|
19
|
+
case 'json': try {
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return raw;
|
|
24
|
+
}
|
|
25
|
+
default: return raw;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
9
28
|
/** Map the API's snake_case flag response to the SDK's Flag type. */
|
|
10
29
|
function mapRawFlag(raw) {
|
|
11
30
|
return {
|
|
12
31
|
key: raw.key,
|
|
13
32
|
enabled: raw.enabled,
|
|
14
|
-
value: raw.default_value,
|
|
33
|
+
value: parseValue(raw.default_value, raw.flag_type),
|
|
15
34
|
metadata: {
|
|
16
35
|
category: raw.category,
|
|
17
36
|
purpose: raw.purpose ?? '',
|
|
@@ -51,11 +70,18 @@ class DeploySentryClient {
|
|
|
51
70
|
sessionId;
|
|
52
71
|
mode;
|
|
53
72
|
flagFilePath;
|
|
73
|
+
onFlagChange;
|
|
54
74
|
cache;
|
|
55
75
|
streamClient = null;
|
|
56
76
|
_initialized = false;
|
|
57
77
|
flagConfig = null;
|
|
78
|
+
offlineDefaults = null;
|
|
79
|
+
offlineDefaultsEnvUUID = '';
|
|
80
|
+
flagConfigLoadedAt;
|
|
81
|
+
offlineDefaultsLoadedAt;
|
|
58
82
|
registry = new Map();
|
|
83
|
+
statusReporter = null;
|
|
84
|
+
statusReporterOptions;
|
|
59
85
|
constructor(options) {
|
|
60
86
|
if (!options.apiKey)
|
|
61
87
|
throw new Error('apiKey is required');
|
|
@@ -74,7 +100,9 @@ class DeploySentryClient {
|
|
|
74
100
|
this.sessionId = options.sessionId;
|
|
75
101
|
this.mode = options.mode ?? 'server';
|
|
76
102
|
this.flagFilePath = options.flagFilePath;
|
|
103
|
+
this.onFlagChange = options.onFlagChange;
|
|
77
104
|
this.cache = new cache_1.FlagCache(options.cacheTimeout ?? DEFAULT_CACHE_TIMEOUT_MS);
|
|
105
|
+
this.statusReporterOptions = options;
|
|
78
106
|
}
|
|
79
107
|
// ---------------------------------------------------------------------------
|
|
80
108
|
// Lifecycle
|
|
@@ -86,6 +114,7 @@ class DeploySentryClient {
|
|
|
86
114
|
async initialize() {
|
|
87
115
|
if (this.mode === 'file') {
|
|
88
116
|
this.flagConfig = (0, file_loader_1.loadFlagConfig)(this.flagFilePath);
|
|
117
|
+
this.flagConfigLoadedAt = new Date().toISOString();
|
|
89
118
|
this._initialized = true;
|
|
90
119
|
return;
|
|
91
120
|
}
|
|
@@ -97,13 +126,21 @@ class DeploySentryClient {
|
|
|
97
126
|
// Fetch all flags for the project so the cache is warm.
|
|
98
127
|
const flags = await this.fetchAllFlags();
|
|
99
128
|
this.cache.setMany(flags);
|
|
100
|
-
// Start streaming updates.
|
|
129
|
+
// Start streaming updates. Each SSE event identifies the changed flag
|
|
130
|
+
// by ID — fetch just that flag to get the authoritative state.
|
|
101
131
|
this.streamClient = new streaming_1.FlagStreamClient({
|
|
102
132
|
url: `${this.baseURL}/api/v1/flags/stream?project_id=${enc(this.project)}&environment_id=${enc(this.environment)}&application=${enc(this.application)}`,
|
|
103
133
|
headers: this.authHeaders(),
|
|
104
|
-
onChange: () => {
|
|
105
|
-
|
|
106
|
-
|
|
134
|
+
onChange: (change) => {
|
|
135
|
+
if (!change.flagId)
|
|
136
|
+
return;
|
|
137
|
+
this.fetchFlag(change.flagId)
|
|
138
|
+
.then((flag) => {
|
|
139
|
+
if (flag) {
|
|
140
|
+
this.cache.set(flag);
|
|
141
|
+
this.onFlagChange?.([flag]);
|
|
142
|
+
}
|
|
143
|
+
})
|
|
107
144
|
.catch(() => { }); // stale cache still serves
|
|
108
145
|
},
|
|
109
146
|
onError: (err) => {
|
|
@@ -114,11 +151,13 @@ class DeploySentryClient {
|
|
|
114
151
|
// Fire-and-forget; the stream reconnects automatically.
|
|
115
152
|
this.streamClient.connect();
|
|
116
153
|
this._initialized = true;
|
|
154
|
+
this.startStatusReporter();
|
|
117
155
|
}
|
|
118
156
|
catch (err) {
|
|
119
157
|
if (this.mode === 'server-with-fallback') {
|
|
120
158
|
console.warn('[DeploySentry] Server unavailable, falling back to flag config file');
|
|
121
159
|
this.flagConfig = (0, file_loader_1.loadFlagConfig)(this.flagFilePath);
|
|
160
|
+
this.flagConfigLoadedAt = new Date().toISOString();
|
|
122
161
|
this._initialized = true;
|
|
123
162
|
return;
|
|
124
163
|
}
|
|
@@ -129,9 +168,32 @@ class DeploySentryClient {
|
|
|
129
168
|
close() {
|
|
130
169
|
this.streamClient?.close();
|
|
131
170
|
this.streamClient = null;
|
|
171
|
+
this.statusReporter?.stop();
|
|
172
|
+
this.statusReporter = null;
|
|
132
173
|
this.cache.clear();
|
|
133
174
|
this._initialized = false;
|
|
134
175
|
}
|
|
176
|
+
startStatusReporter() {
|
|
177
|
+
const o = this.statusReporterOptions;
|
|
178
|
+
if (!o.reportStatus)
|
|
179
|
+
return;
|
|
180
|
+
if (!o.applicationId) {
|
|
181
|
+
console.warn('[DeploySentry] reportStatus=true but applicationId is not set; status reporter disabled');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
this.statusReporter = new status_reporter_1.StatusReporter({
|
|
185
|
+
baseURL: this.baseURL,
|
|
186
|
+
apiKey: this.apiKey,
|
|
187
|
+
applicationId: o.applicationId,
|
|
188
|
+
intervalMs: o.reportStatusIntervalMs,
|
|
189
|
+
version: o.reportStatusVersion,
|
|
190
|
+
commitSha: o.reportStatusCommitSha,
|
|
191
|
+
deploySlot: o.reportStatusDeploySlot,
|
|
192
|
+
tags: o.reportStatusTags,
|
|
193
|
+
healthProvider: o.reportStatusHealthProvider,
|
|
194
|
+
});
|
|
195
|
+
this.statusReporter.start();
|
|
196
|
+
}
|
|
135
197
|
/**
|
|
136
198
|
* Clear the local cache and re-fetch all flags from the server.
|
|
137
199
|
* Useful when session state may have changed and the client needs
|
|
@@ -234,6 +296,72 @@ class DeploySentryClient {
|
|
|
234
296
|
allFlags() {
|
|
235
297
|
return this.cache.getAll();
|
|
236
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Return a provenance-tagged view of every flag the SDK currently knows
|
|
301
|
+
* about, drawn from all sources (live server cache, offline file config,
|
|
302
|
+
* and offline defaults). Each entry reports the resolved value, its type,
|
|
303
|
+
* the {@link FlagSource | source} it came from, an evaluation reason code,
|
|
304
|
+
* and when it was last fetched / loaded.
|
|
305
|
+
*
|
|
306
|
+
* Intended for wiring into an admin or support panel so operators can see
|
|
307
|
+
* exactly what value an application is serving and where it came from. This
|
|
308
|
+
* is a read-only snapshot — it does not contact the server or evaluate
|
|
309
|
+
* targeting rules for a specific user.
|
|
310
|
+
*/
|
|
311
|
+
inspect() {
|
|
312
|
+
const byKey = new Map();
|
|
313
|
+
// 1) Server cache (live + stale). Highest-fidelity source; recorded first.
|
|
314
|
+
for (const e of this.cache.entries()) {
|
|
315
|
+
byKey.set(e.flag.key, {
|
|
316
|
+
key: e.flag.key,
|
|
317
|
+
value: e.flag.value,
|
|
318
|
+
type: typeOfValue(e.flag.value),
|
|
319
|
+
enabled: e.flag.enabled,
|
|
320
|
+
source: e.stale ? 'cache' : 'server',
|
|
321
|
+
reason: e.stale ? 'CACHE' : 'LIVE',
|
|
322
|
+
fetchedAt: new Date(e.storedAt).toISOString(),
|
|
323
|
+
stale: e.stale,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
// 2) Offline file config (mode 'file' or fallback). Only fills gaps.
|
|
327
|
+
if (this.flagConfig) {
|
|
328
|
+
for (const f of this.flagConfig.flags) {
|
|
329
|
+
if (byKey.has(f.key))
|
|
330
|
+
continue;
|
|
331
|
+
const result = (0, local_evaluator_1.evaluateLocal)(this.flagConfig, this.environment, f.key);
|
|
332
|
+
const envState = f.environments[this.environment];
|
|
333
|
+
byKey.set(f.key, {
|
|
334
|
+
key: f.key,
|
|
335
|
+
value: parseValue(result.value, f.flag_type),
|
|
336
|
+
type: typeOfFlagType(f.flag_type),
|
|
337
|
+
enabled: envState?.enabled ?? false,
|
|
338
|
+
source: 'file',
|
|
339
|
+
reason: 'OFFLINE_FILE',
|
|
340
|
+
fetchedAt: this.flagConfigLoadedAt,
|
|
341
|
+
stale: false,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// 3) Offline defaults loaded via loadDefaults*(). Only fills gaps.
|
|
346
|
+
if (this.offlineDefaults) {
|
|
347
|
+
for (const [key, def] of this.offlineDefaults) {
|
|
348
|
+
if (byKey.has(key))
|
|
349
|
+
continue;
|
|
350
|
+
const resolved = (0, defaults_1.lookupOfflineDefault)(this.offlineDefaults, this.offlineDefaultsEnvUUID, key);
|
|
351
|
+
byKey.set(key, {
|
|
352
|
+
key,
|
|
353
|
+
value: resolved ? parseValue(resolved.value, def.flagType) : undefined,
|
|
354
|
+
type: typeOfFlagType(def.flagType),
|
|
355
|
+
enabled: resolved?.enabled ?? false,
|
|
356
|
+
source: 'file',
|
|
357
|
+
reason: 'OFFLINE_DEFAULT',
|
|
358
|
+
fetchedAt: this.offlineDefaultsLoadedAt,
|
|
359
|
+
stale: false,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return [...byKey.values()];
|
|
364
|
+
}
|
|
237
365
|
// ---------------------------------------------------------------------------
|
|
238
366
|
// Register / Dispatch
|
|
239
367
|
// ---------------------------------------------------------------------------
|
|
@@ -297,14 +425,78 @@ class DeploySentryClient {
|
|
|
297
425
|
return result.value;
|
|
298
426
|
}
|
|
299
427
|
catch {
|
|
300
|
-
//
|
|
428
|
+
// 1) Cached value (from a prior live call) wins over offline defaults.
|
|
301
429
|
const cached = this.cache.get(key);
|
|
302
|
-
|
|
430
|
+
if (cached?.value !== undefined)
|
|
431
|
+
return cached.value;
|
|
432
|
+
// 2) Offline defaults loaded via loadDefaults*().
|
|
433
|
+
const offline = this.offlineDefaults
|
|
434
|
+
? (0, defaults_1.lookupOfflineDefault)(this.offlineDefaults, this.offlineDefaultsEnvUUID, key)
|
|
435
|
+
: undefined;
|
|
436
|
+
if (offline) {
|
|
437
|
+
const requested = inferRequestedType(defaultValue);
|
|
438
|
+
const coerced = (0, defaults_1.coerceOfflineValue)(offline.value, requested);
|
|
439
|
+
if (coerced !== undefined)
|
|
440
|
+
return coerced;
|
|
441
|
+
}
|
|
442
|
+
return defaultValue;
|
|
303
443
|
}
|
|
304
444
|
}
|
|
445
|
+
// ---------------------------------------------------------------------------
|
|
446
|
+
// Offline defaults
|
|
447
|
+
// ---------------------------------------------------------------------------
|
|
448
|
+
/**
|
|
449
|
+
* Load offline default values from a flag-export file. When the live API
|
|
450
|
+
* is unreachable and the cache has no entry, the SDK falls back to these
|
|
451
|
+
* defaults for {@link boolValue} / {@link stringValue} / {@link intValue}
|
|
452
|
+
* / {@link jsonValue} calls.
|
|
453
|
+
*
|
|
454
|
+
* Accepts the JSON (default) or YAML format produced by
|
|
455
|
+
* `deploysentry flags export`.
|
|
456
|
+
*
|
|
457
|
+
* Environment matching: the SDK matches the configured `environment`
|
|
458
|
+
* option against the export's `environments[]` first by UUID, then by
|
|
459
|
+
* name (case-insensitive). When a match is found, that env's per-flag
|
|
460
|
+
* `{enabled, value}` overrides the flag's global `default_value`.
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* await client.loadDefaultsFromFile('flags.json');
|
|
464
|
+
*/
|
|
465
|
+
async loadDefaultsFromFile(path) {
|
|
466
|
+
const payload = await (0, defaults_1.readDefaultsFile)(path);
|
|
467
|
+
this.loadDefaults(payload);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Synchronous variant of {@link loadDefaultsFromFile} that accepts an
|
|
471
|
+
* already-parsed object (or a JSON string).
|
|
472
|
+
*/
|
|
473
|
+
loadDefaults(payload) {
|
|
474
|
+
const data = typeof payload === 'string' ? JSON.parse(payload) : payload;
|
|
475
|
+
const { defaults, envUUID } = (0, defaults_1.parseDefaults)(data, this.environment);
|
|
476
|
+
this.offlineDefaults = defaults;
|
|
477
|
+
this.offlineDefaultsEnvUUID = envUUID;
|
|
478
|
+
this.offlineDefaultsLoadedAt = new Date().toISOString();
|
|
479
|
+
}
|
|
305
480
|
async fetchAllFlags() {
|
|
306
|
-
const
|
|
307
|
-
|
|
481
|
+
const pageSize = 500;
|
|
482
|
+
const flags = [];
|
|
483
|
+
let offset = 0;
|
|
484
|
+
for (;;) {
|
|
485
|
+
const response = await this.request('GET', `/api/v1/flags?project_id=${enc(this.project)}&application=${enc(this.application)}&environment_id=${enc(this.environment)}&limit=${pageSize}&offset=${offset}`);
|
|
486
|
+
const page = Array.isArray(response) ? response : (response.flags ?? []);
|
|
487
|
+
flags.push(...page);
|
|
488
|
+
const total = Array.isArray(response) ? undefined : response.total_count;
|
|
489
|
+
if (Array.isArray(response) || page.length === 0 || total === undefined || flags.length >= total)
|
|
490
|
+
break;
|
|
491
|
+
offset += page.length;
|
|
492
|
+
}
|
|
493
|
+
return flags.map(mapRawFlag);
|
|
494
|
+
}
|
|
495
|
+
async fetchFlag(flagId) {
|
|
496
|
+
const raw = await this.request('GET', `/api/v1/flags/${enc(flagId)}?environment_id=${enc(this.environment)}`);
|
|
497
|
+
if (!raw?.key)
|
|
498
|
+
return null;
|
|
499
|
+
return mapRawFlag(raw);
|
|
308
500
|
}
|
|
309
501
|
async post(path, body) {
|
|
310
502
|
return this.request('POST', path, body);
|
|
@@ -380,4 +572,39 @@ exports.DeploySentryClient = DeploySentryClient;
|
|
|
380
572
|
function enc(value) {
|
|
381
573
|
return encodeURIComponent(value);
|
|
382
574
|
}
|
|
575
|
+
/** Map a flag_type string to the inspection `type` field. */
|
|
576
|
+
function typeOfFlagType(flagType) {
|
|
577
|
+
switch (flagType) {
|
|
578
|
+
case 'boolean': return 'boolean';
|
|
579
|
+
case 'integer': return 'integer';
|
|
580
|
+
case 'number': return 'number';
|
|
581
|
+
case 'json': return 'json';
|
|
582
|
+
case 'string': return 'string';
|
|
583
|
+
default: return 'unknown';
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/** Infer the inspection `type` field from an already-parsed value. */
|
|
587
|
+
function typeOfValue(value) {
|
|
588
|
+
switch (typeof value) {
|
|
589
|
+
case 'boolean': return 'boolean';
|
|
590
|
+
case 'number': return Number.isInteger(value) ? 'integer' : 'number';
|
|
591
|
+
case 'string': return 'string';
|
|
592
|
+
case 'object': return value === null ? 'unknown' : 'json';
|
|
593
|
+
default: return 'unknown';
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Infer which coercion to apply to an offline-default raw value based on
|
|
598
|
+
* the caller's `defaultValue` type. Falls back to 'json' for objects /
|
|
599
|
+
* arrays so JSON-typed flags round-trip correctly.
|
|
600
|
+
*/
|
|
601
|
+
function inferRequestedType(defaultValue) {
|
|
602
|
+
if (typeof defaultValue === 'boolean')
|
|
603
|
+
return 'bool';
|
|
604
|
+
if (typeof defaultValue === 'string')
|
|
605
|
+
return 'string';
|
|
606
|
+
if (typeof defaultValue === 'number')
|
|
607
|
+
return 'number';
|
|
608
|
+
return 'json';
|
|
609
|
+
}
|
|
383
610
|
//# sourceMappingURL=client.js.map
|