@redpennon/node-sdk 1.0.0 → 1.1.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/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +57 -24
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -79,9 +79,20 @@ export type EventPayload = {
|
|
|
79
79
|
occurred_at?: string;
|
|
80
80
|
/** Opaque trace token from a prior {@link VariableResult.evaluation_trace}. */
|
|
81
81
|
evaluation_trace?: string;
|
|
82
|
+
/**
|
|
83
|
+
* De-duplication key, unique per environment, at most 200 characters.
|
|
84
|
+
*
|
|
85
|
+
* Send one and a retry after a timeout is free: the platform skips
|
|
86
|
+
* keys it has already stored instead of counting the event twice.
|
|
87
|
+
* Omit it and repeats are stored — which is correct for genuinely
|
|
88
|
+
* repeated events, since nothing else can tell them apart.
|
|
89
|
+
*/
|
|
90
|
+
event_id?: string;
|
|
82
91
|
};
|
|
83
92
|
export type TrackEventsResult = {
|
|
84
93
|
accepted: number;
|
|
94
|
+
/** Rows skipped because their `event_id` was already stored. */
|
|
95
|
+
duplicates?: number;
|
|
85
96
|
};
|
|
86
97
|
export declare class APIError extends Error {
|
|
87
98
|
readonly statusCode: number;
|
|
@@ -96,12 +107,28 @@ export declare class APIError extends Error {
|
|
|
96
107
|
readonly code: string | null;
|
|
97
108
|
constructor(statusCode: number, message: string, body: string, code?: string | null);
|
|
98
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Default per-request timeout, in milliseconds.
|
|
112
|
+
*
|
|
113
|
+
* A flag lookup that has not answered within a second is not going to
|
|
114
|
+
* be useful to the render that asked for it. Without a bound, a hung
|
|
115
|
+
* API hangs the calling application indefinitely — the exact failure
|
|
116
|
+
* the fail-open design exists to prevent, and one `variableValue`
|
|
117
|
+
* cannot catch because the request never returns.
|
|
118
|
+
*/
|
|
119
|
+
export declare const DEFAULT_TIMEOUT_MS = 1000;
|
|
99
120
|
export type ClientOptions = {
|
|
100
121
|
apiKey: string;
|
|
101
122
|
/** Override the API origin. Defaults to {@link DEFAULT_API_BASE_URL}. */
|
|
102
123
|
baseUrl?: string;
|
|
103
124
|
/** Override the fetch implementation (handy for tests). */
|
|
104
125
|
fetchImpl?: typeof fetch;
|
|
126
|
+
/**
|
|
127
|
+
* Per-request timeout in milliseconds. Defaults to
|
|
128
|
+
* {@link DEFAULT_TIMEOUT_MS}. Pass 0 to disable the bound entirely —
|
|
129
|
+
* only sensible when the caller imposes its own.
|
|
130
|
+
*/
|
|
131
|
+
timeoutMs?: number;
|
|
105
132
|
};
|
|
106
133
|
export type EvalOptions = {
|
|
107
134
|
user?: UserContext;
|
|
@@ -115,7 +142,10 @@ export declare class RedPennonClient {
|
|
|
115
142
|
readonly origin: string;
|
|
116
143
|
readonly apiKey: string;
|
|
117
144
|
readonly fetchImpl: typeof fetch;
|
|
145
|
+
readonly timeoutMs: number;
|
|
118
146
|
constructor(options: ClientOptions);
|
|
147
|
+
/** Request init shared by every call, including the timeout signal. */
|
|
148
|
+
private requestInit;
|
|
119
149
|
/**
|
|
120
150
|
* Resolve a single variable to its full result object.
|
|
121
151
|
*
|
|
@@ -148,5 +178,16 @@ export declare class RedPennonClient {
|
|
|
148
178
|
* `variable_not_found` inline.
|
|
149
179
|
*/
|
|
150
180
|
variables(keys: string[], options?: EvalOptions): Promise<BatchResults>;
|
|
181
|
+
/**
|
|
182
|
+
* Batch counterpart of {@link variableValue}: resolve many keys and
|
|
183
|
+
* return a value for each, falling back to the matching entry in
|
|
184
|
+
* `defaults` whenever the platform served no value or the call failed
|
|
185
|
+
* outright.
|
|
186
|
+
*
|
|
187
|
+
* Without this, only the single-key path actually failed open — a
|
|
188
|
+
* caller batching for efficiency had to hand-roll the try/catch that
|
|
189
|
+
* makes the SDK's central promise true.
|
|
190
|
+
*/
|
|
191
|
+
variableValues<T extends Record<string, unknown>>(defaults: T, options?: EvalOptions): Promise<T>;
|
|
151
192
|
}
|
|
152
193
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,eAAO,MAAM,oBAAoB,8BAA8B,CAAC;AAEhE,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GACxB,wBAAwB,GACxB,mBAAmB,GACnB,iBAAiB,GACjB,oBAAoB,GACpB,kBAAkB,GAClB,iBAAiB,GACjB,kBAAkB,GAClB,yBAAyB,GACzB,oBAAoB,CAAC;AAEzB,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAChB,iEAAiE;IACjE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,gBAAgB,CAAC;IACzB,kEAAkE;IAClE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,oNAAoN;IACpN,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAEnE,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,16 @@ function parseErrorCode(body) {
|
|
|
52
52
|
}
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Default per-request timeout, in milliseconds.
|
|
57
|
+
*
|
|
58
|
+
* A flag lookup that has not answered within a second is not going to
|
|
59
|
+
* be useful to the render that asked for it. Without a bound, a hung
|
|
60
|
+
* API hangs the calling application indefinitely — the exact failure
|
|
61
|
+
* the fail-open design exists to prevent, and one `variableValue`
|
|
62
|
+
* cannot catch because the request never returns.
|
|
63
|
+
*/
|
|
64
|
+
export const DEFAULT_TIMEOUT_MS = 1000;
|
|
55
65
|
/**
|
|
56
66
|
* Thin client around the variable-key evaluation endpoints.
|
|
57
67
|
* Stateless — one instance can serve any number of concurrent
|
|
@@ -61,10 +71,27 @@ export class RedPennonClient {
|
|
|
61
71
|
origin;
|
|
62
72
|
apiKey;
|
|
63
73
|
fetchImpl;
|
|
74
|
+
timeoutMs;
|
|
64
75
|
constructor(options) {
|
|
65
76
|
this.origin = (options.baseUrl ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
|
|
66
77
|
this.apiKey = options.apiKey;
|
|
67
78
|
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
79
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
80
|
+
}
|
|
81
|
+
/** Request init shared by every call, including the timeout signal. */
|
|
82
|
+
requestInit(body) {
|
|
83
|
+
const init = {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: {
|
|
86
|
+
"X-API-Key": this.apiKey,
|
|
87
|
+
"Content-Type": "application/json",
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify(body),
|
|
90
|
+
};
|
|
91
|
+
if (this.timeoutMs > 0) {
|
|
92
|
+
init.signal = AbortSignal.timeout(this.timeoutMs);
|
|
93
|
+
}
|
|
94
|
+
return init;
|
|
68
95
|
}
|
|
69
96
|
/**
|
|
70
97
|
* Resolve a single variable to its full result object.
|
|
@@ -77,14 +104,7 @@ export class RedPennonClient {
|
|
|
77
104
|
const body = {};
|
|
78
105
|
if (options.user)
|
|
79
106
|
body.user = options.user;
|
|
80
|
-
const response = await this.fetchImpl(`${this.origin}/v1/variables/${encodeURIComponent(key)}`,
|
|
81
|
-
method: "POST",
|
|
82
|
-
headers: {
|
|
83
|
-
"X-API-Key": this.apiKey,
|
|
84
|
-
"Content-Type": "application/json",
|
|
85
|
-
},
|
|
86
|
-
body: JSON.stringify(body),
|
|
87
|
-
});
|
|
107
|
+
const response = await this.fetchImpl(`${this.origin}/v1/variables/${encodeURIComponent(key)}`, this.requestInit(body));
|
|
88
108
|
if (!response.ok) {
|
|
89
109
|
const text = await response.text();
|
|
90
110
|
throw new APIError(response.status, response.statusText, text, parseErrorCode(text));
|
|
@@ -120,14 +140,7 @@ export class RedPennonClient {
|
|
|
120
140
|
* errors (`events_batch_too_large`, `rate_limit_exceeded`, etc.).
|
|
121
141
|
*/
|
|
122
142
|
async trackEvents(events) {
|
|
123
|
-
const response = await this.fetchImpl(`${this.origin}/v1/events`, {
|
|
124
|
-
method: "POST",
|
|
125
|
-
headers: {
|
|
126
|
-
"X-API-Key": this.apiKey,
|
|
127
|
-
"Content-Type": "application/json",
|
|
128
|
-
},
|
|
129
|
-
body: JSON.stringify({ events }),
|
|
130
|
-
});
|
|
143
|
+
const response = await this.fetchImpl(`${this.origin}/v1/events`, this.requestInit({ events }));
|
|
131
144
|
if (!response.ok) {
|
|
132
145
|
const text = await response.text();
|
|
133
146
|
throw new APIError(response.status, response.statusText, text, parseErrorCode(text));
|
|
@@ -143,14 +156,7 @@ export class RedPennonClient {
|
|
|
143
156
|
const body = { keys };
|
|
144
157
|
if (options.user)
|
|
145
158
|
body.user = options.user;
|
|
146
|
-
const response = await this.fetchImpl(`${this.origin}/v1/variables`,
|
|
147
|
-
method: "POST",
|
|
148
|
-
headers: {
|
|
149
|
-
"X-API-Key": this.apiKey,
|
|
150
|
-
"Content-Type": "application/json",
|
|
151
|
-
},
|
|
152
|
-
body: JSON.stringify(body),
|
|
153
|
-
});
|
|
159
|
+
const response = await this.fetchImpl(`${this.origin}/v1/variables`, this.requestInit(body));
|
|
154
160
|
if (!response.ok) {
|
|
155
161
|
const text = await response.text();
|
|
156
162
|
throw new APIError(response.status, response.statusText, text, parseErrorCode(text));
|
|
@@ -158,4 +164,31 @@ export class RedPennonClient {
|
|
|
158
164
|
const parsed = (await response.json());
|
|
159
165
|
return parsed.results;
|
|
160
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Batch counterpart of {@link variableValue}: resolve many keys and
|
|
169
|
+
* return a value for each, falling back to the matching entry in
|
|
170
|
+
* `defaults` whenever the platform served no value or the call failed
|
|
171
|
+
* outright.
|
|
172
|
+
*
|
|
173
|
+
* Without this, only the single-key path actually failed open — a
|
|
174
|
+
* caller batching for efficiency had to hand-roll the try/catch that
|
|
175
|
+
* makes the SDK's central promise true.
|
|
176
|
+
*/
|
|
177
|
+
async variableValues(defaults, options = {}) {
|
|
178
|
+
const keys = Object.keys(defaults);
|
|
179
|
+
try {
|
|
180
|
+
const results = await this.variables(keys, options);
|
|
181
|
+
const out = { ...defaults };
|
|
182
|
+
for (const key of keys) {
|
|
183
|
+
const value = results[key]?.value;
|
|
184
|
+
if (value !== null && value !== undefined) {
|
|
185
|
+
out[key] = value;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return { ...defaults };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
161
194
|
}
|