@replanejs/sdk 0.6.1 → 0.7.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.cjs +10 -606
- package/dist/index.d.ts +199 -44
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +565 -341
- package/dist/index.js.map +1 -0
- package/package.json +15 -24
- package/README.md +0 -479
package/dist/index.cjs
CHANGED
|
@@ -18,616 +18,20 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var src_exports = {};
|
|
20
20
|
__export(src_exports, {
|
|
21
|
-
ReplaneError: () => ReplaneError,
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
ReplaneError: () => import_error.ReplaneError,
|
|
22
|
+
ReplaneErrorCode: () => import_error.ReplaneErrorCode,
|
|
23
|
+
createInMemoryReplaneClient: () => import_client.createInMemoryReplaneClient,
|
|
24
|
+
createReplaneClient: () => import_client.createReplaneClient,
|
|
25
|
+
restoreReplaneClient: () => import_client.restoreReplaneClient
|
|
24
26
|
});
|
|
25
27
|
module.exports = __toCommonJS(src_exports);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
init: true
|
|
29
|
-
});
|
|
30
|
-
function fnv1a32(input) {
|
|
31
|
-
const encoder = new TextEncoder();
|
|
32
|
-
const bytes = encoder.encode(input);
|
|
33
|
-
let hash = 2166136261 >>> 0;
|
|
34
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
35
|
-
hash ^= bytes[i];
|
|
36
|
-
hash = Math.imul(hash, 16777619) >>> 0;
|
|
37
|
-
}
|
|
38
|
-
return hash >>> 0;
|
|
39
|
-
}
|
|
40
|
-
function fnv1a32ToUnit(input) {
|
|
41
|
-
const h = fnv1a32(input);
|
|
42
|
-
return h / 2 ** 32;
|
|
43
|
-
}
|
|
44
|
-
function evaluateOverrides(baseValue, overrides, context, logger) {
|
|
45
|
-
for (const override of overrides) {
|
|
46
|
-
let overrideResult = "matched";
|
|
47
|
-
const results = override.conditions.map((c) => evaluateCondition(c, context, logger));
|
|
48
|
-
if (results.some((r) => r === "not_matched")) {
|
|
49
|
-
overrideResult = "not_matched";
|
|
50
|
-
} else if (results.some((r) => r === "unknown")) {
|
|
51
|
-
overrideResult = "unknown";
|
|
52
|
-
}
|
|
53
|
-
if (overrideResult === "matched") {
|
|
54
|
-
return override.value;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return baseValue;
|
|
58
|
-
}
|
|
59
|
-
function evaluateCondition(condition, context, logger) {
|
|
60
|
-
const operator = condition.operator;
|
|
61
|
-
if (operator === "and") {
|
|
62
|
-
const results = condition.conditions.map((c) => evaluateCondition(c, context, logger));
|
|
63
|
-
if (results.some((r) => r === "not_matched")) return "not_matched";
|
|
64
|
-
if (results.some((r) => r === "unknown")) return "unknown";
|
|
65
|
-
return "matched";
|
|
66
|
-
}
|
|
67
|
-
if (operator === "or") {
|
|
68
|
-
const results = condition.conditions.map((c) => evaluateCondition(c, context, logger));
|
|
69
|
-
if (results.some((r) => r === "matched")) return "matched";
|
|
70
|
-
if (results.some((r) => r === "unknown")) return "unknown";
|
|
71
|
-
return "not_matched";
|
|
72
|
-
}
|
|
73
|
-
if (operator === "not") {
|
|
74
|
-
const result = evaluateCondition(condition.condition, context, logger);
|
|
75
|
-
if (result === "matched") return "not_matched";
|
|
76
|
-
if (result === "not_matched") return "matched";
|
|
77
|
-
return "unknown";
|
|
78
|
-
}
|
|
79
|
-
if (operator === "segmentation") {
|
|
80
|
-
const contextValue2 = context[condition.property];
|
|
81
|
-
if (contextValue2 === void 0 || contextValue2 === null) {
|
|
82
|
-
return "unknown";
|
|
83
|
-
}
|
|
84
|
-
const hashInput = String(contextValue2) + condition.seed;
|
|
85
|
-
const unitValue = fnv1a32ToUnit(hashInput);
|
|
86
|
-
return unitValue >= condition.fromPercentage / 100 && unitValue < condition.toPercentage / 100 ? "matched" : "not_matched";
|
|
87
|
-
}
|
|
88
|
-
const property = condition.property;
|
|
89
|
-
const contextValue = context[property];
|
|
90
|
-
const expectedValue = condition.value;
|
|
91
|
-
if (contextValue === void 0) {
|
|
92
|
-
return "unknown";
|
|
93
|
-
}
|
|
94
|
-
const castedValue = castToContextType(expectedValue, contextValue);
|
|
95
|
-
switch (operator) {
|
|
96
|
-
case "equals":
|
|
97
|
-
return contextValue === castedValue ? "matched" : "not_matched";
|
|
98
|
-
case "in":
|
|
99
|
-
if (!Array.isArray(castedValue)) return "unknown";
|
|
100
|
-
return castedValue.includes(contextValue) ? "matched" : "not_matched";
|
|
101
|
-
case "not_in":
|
|
102
|
-
if (!Array.isArray(castedValue)) return "unknown";
|
|
103
|
-
return !castedValue.includes(contextValue) ? "matched" : "not_matched";
|
|
104
|
-
case "less_than":
|
|
105
|
-
if (typeof contextValue === "number" && typeof castedValue === "number") {
|
|
106
|
-
return contextValue < castedValue ? "matched" : "not_matched";
|
|
107
|
-
}
|
|
108
|
-
if (typeof contextValue === "string" && typeof castedValue === "string") {
|
|
109
|
-
return contextValue < castedValue ? "matched" : "not_matched";
|
|
110
|
-
}
|
|
111
|
-
return "not_matched";
|
|
112
|
-
case "less_than_or_equal":
|
|
113
|
-
if (typeof contextValue === "number" && typeof castedValue === "number") {
|
|
114
|
-
return contextValue <= castedValue ? "matched" : "not_matched";
|
|
115
|
-
}
|
|
116
|
-
if (typeof contextValue === "string" && typeof castedValue === "string") {
|
|
117
|
-
return contextValue <= castedValue ? "matched" : "not_matched";
|
|
118
|
-
}
|
|
119
|
-
return "not_matched";
|
|
120
|
-
case "greater_than":
|
|
121
|
-
if (typeof contextValue === "number" && typeof castedValue === "number") {
|
|
122
|
-
return contextValue > castedValue ? "matched" : "not_matched";
|
|
123
|
-
}
|
|
124
|
-
if (typeof contextValue === "string" && typeof castedValue === "string") {
|
|
125
|
-
return contextValue > castedValue ? "matched" : "not_matched";
|
|
126
|
-
}
|
|
127
|
-
return "not_matched";
|
|
128
|
-
case "greater_than_or_equal":
|
|
129
|
-
if (typeof contextValue === "number" && typeof castedValue === "number") {
|
|
130
|
-
return contextValue >= castedValue ? "matched" : "not_matched";
|
|
131
|
-
}
|
|
132
|
-
if (typeof contextValue === "string" && typeof castedValue === "string") {
|
|
133
|
-
return contextValue >= castedValue ? "matched" : "not_matched";
|
|
134
|
-
}
|
|
135
|
-
return "not_matched";
|
|
136
|
-
default:
|
|
137
|
-
warnNever(operator, logger, `Unexpected operator: ${operator}`);
|
|
138
|
-
return "unknown";
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function warnNever(value, logger, message) {
|
|
142
|
-
logger.warn(message, { value });
|
|
143
|
-
}
|
|
144
|
-
function castToContextType(expectedValue, contextValue) {
|
|
145
|
-
if (typeof contextValue === "number") {
|
|
146
|
-
if (typeof expectedValue === "string") {
|
|
147
|
-
const num = Number(expectedValue);
|
|
148
|
-
return isNaN(num) ? expectedValue : num;
|
|
149
|
-
}
|
|
150
|
-
return expectedValue;
|
|
151
|
-
}
|
|
152
|
-
if (typeof contextValue === "boolean") {
|
|
153
|
-
if (typeof expectedValue === "string") {
|
|
154
|
-
if (expectedValue === "true") return true;
|
|
155
|
-
if (expectedValue === "false") return false;
|
|
156
|
-
}
|
|
157
|
-
if (typeof expectedValue === "number") {
|
|
158
|
-
return expectedValue !== 0;
|
|
159
|
-
}
|
|
160
|
-
return expectedValue;
|
|
161
|
-
}
|
|
162
|
-
if (typeof contextValue === "string") {
|
|
163
|
-
if (typeof expectedValue === "number" || typeof expectedValue === "boolean") {
|
|
164
|
-
return String(expectedValue);
|
|
165
|
-
}
|
|
166
|
-
return expectedValue;
|
|
167
|
-
}
|
|
168
|
-
return expectedValue;
|
|
169
|
-
}
|
|
170
|
-
async function delay(ms) {
|
|
171
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
172
|
-
}
|
|
173
|
-
async function retryDelay(averageDelay) {
|
|
174
|
-
const jitter = averageDelay / 5;
|
|
175
|
-
const delayMs = averageDelay + Math.random() * jitter - jitter / 2;
|
|
176
|
-
await delay(delayMs);
|
|
177
|
-
}
|
|
178
|
-
class ReplaneRemoteStorage {
|
|
179
|
-
closeController = new AbortController();
|
|
180
|
-
// never throws
|
|
181
|
-
async *startReplicationStream(options) {
|
|
182
|
-
const { signal, cleanUpSignals } = combineAbortSignals([
|
|
183
|
-
this.closeController.signal,
|
|
184
|
-
options.signal
|
|
185
|
-
]);
|
|
186
|
-
try {
|
|
187
|
-
let failedAttempts = 0;
|
|
188
|
-
while (!signal.aborted) {
|
|
189
|
-
try {
|
|
190
|
-
for await (const event of this.startReplicationStreamImpl({
|
|
191
|
-
...options,
|
|
192
|
-
signal,
|
|
193
|
-
onConnect: () => {
|
|
194
|
-
failedAttempts = 0;
|
|
195
|
-
}
|
|
196
|
-
})) {
|
|
197
|
-
yield event;
|
|
198
|
-
}
|
|
199
|
-
} catch (error) {
|
|
200
|
-
failedAttempts++;
|
|
201
|
-
const retryDelayMs = Math.min(options.retryDelayMs * 2 ** (failedAttempts - 1), 1e4);
|
|
202
|
-
if (!signal.aborted) {
|
|
203
|
-
options.logger.error(
|
|
204
|
-
`Failed to fetch project events, retrying in ${retryDelayMs}ms...`,
|
|
205
|
-
error
|
|
206
|
-
);
|
|
207
|
-
await retryDelay(retryDelayMs);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
} finally {
|
|
212
|
-
cleanUpSignals();
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
async *startReplicationStreamImpl(options) {
|
|
216
|
-
const inactivityAbortController = new AbortController();
|
|
217
|
-
const { signal: combinedSignal, cleanUpSignals } = options.signal ? combineAbortSignals([options.signal, inactivityAbortController.signal]) : { signal: inactivityAbortController.signal, cleanUpSignals: () => {
|
|
218
|
-
} };
|
|
219
|
-
let inactivityTimer = null;
|
|
220
|
-
const resetInactivityTimer = () => {
|
|
221
|
-
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
222
|
-
inactivityTimer = setTimeout(() => {
|
|
223
|
-
inactivityAbortController.abort();
|
|
224
|
-
}, options.inactivityTimeoutMs);
|
|
225
|
-
};
|
|
226
|
-
try {
|
|
227
|
-
const rawEvents = fetchSse({
|
|
228
|
-
fetchFn: options.fetchFn,
|
|
229
|
-
headers: {
|
|
230
|
-
Authorization: this.getAuthHeader(options),
|
|
231
|
-
"Content-Type": "application/json"
|
|
232
|
-
},
|
|
233
|
-
body: JSON.stringify(options.getBody()),
|
|
234
|
-
timeoutMs: options.requestTimeoutMs,
|
|
235
|
-
method: "POST",
|
|
236
|
-
signal: combinedSignal,
|
|
237
|
-
url: this.getApiEndpoint(`/sdk/v1/replication/stream`, options),
|
|
238
|
-
onConnect: () => {
|
|
239
|
-
resetInactivityTimer();
|
|
240
|
-
options.onConnect?.();
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
for await (const sseEvent of rawEvents) {
|
|
244
|
-
resetInactivityTimer();
|
|
245
|
-
if (sseEvent.type === "comment") continue;
|
|
246
|
-
const event = JSON.parse(sseEvent.data);
|
|
247
|
-
if (typeof event === "object" && event !== null && "type" in event && typeof event.type === "string" && SUPPORTED_REPLICATION_STREAM_RECORD_TYPES.includes(event.type)) {
|
|
248
|
-
yield event;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
} finally {
|
|
252
|
-
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
253
|
-
cleanUpSignals();
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
close() {
|
|
257
|
-
this.closeController.abort();
|
|
258
|
-
}
|
|
259
|
-
getAuthHeader(options) {
|
|
260
|
-
return `Bearer ${options.sdkKey}`;
|
|
261
|
-
}
|
|
262
|
-
getApiEndpoint(path, options) {
|
|
263
|
-
return `${options.baseUrl}/api${path}`;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
var ReplaneErrorCode = /* @__PURE__ */ ((ReplaneErrorCode2) => {
|
|
267
|
-
ReplaneErrorCode2["NotFound"] = "not_found";
|
|
268
|
-
ReplaneErrorCode2["Timeout"] = "timeout";
|
|
269
|
-
ReplaneErrorCode2["NetworkError"] = "network_error";
|
|
270
|
-
ReplaneErrorCode2["AuthError"] = "auth_error";
|
|
271
|
-
ReplaneErrorCode2["Forbidden"] = "forbidden";
|
|
272
|
-
ReplaneErrorCode2["ServerError"] = "server_error";
|
|
273
|
-
ReplaneErrorCode2["ClientError"] = "client_error";
|
|
274
|
-
ReplaneErrorCode2["Closed"] = "closed";
|
|
275
|
-
ReplaneErrorCode2["NotInitialized"] = "not_initialized";
|
|
276
|
-
ReplaneErrorCode2["Unknown"] = "unknown";
|
|
277
|
-
return ReplaneErrorCode2;
|
|
278
|
-
})(ReplaneErrorCode || {});
|
|
279
|
-
class ReplaneError extends Error {
|
|
280
|
-
code;
|
|
281
|
-
constructor(params) {
|
|
282
|
-
super(params.message, { cause: params.cause });
|
|
283
|
-
this.name = "ReplaneError";
|
|
284
|
-
this.code = params.code;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
async function createReplaneClient(sdkOptions) {
|
|
288
|
-
const storage = new ReplaneRemoteStorage();
|
|
289
|
-
return await _createReplaneClient(toFinalOptions(sdkOptions), storage);
|
|
290
|
-
}
|
|
291
|
-
function createInMemoryReplaneClient(initialData) {
|
|
292
|
-
return {
|
|
293
|
-
get: (configName) => {
|
|
294
|
-
const config = initialData[configName];
|
|
295
|
-
if (config === void 0) {
|
|
296
|
-
throw new ReplaneError({
|
|
297
|
-
message: `Config not found: ${String(configName)}`,
|
|
298
|
-
code: "not_found" /* NotFound */
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
return config;
|
|
302
|
-
},
|
|
303
|
-
subscribe: () => {
|
|
304
|
-
return () => {
|
|
305
|
-
};
|
|
306
|
-
},
|
|
307
|
-
close: () => {
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
async function _createReplaneClient(sdkOptions, storage) {
|
|
312
|
-
if (!sdkOptions.sdkKey) throw new Error("SDK key is required");
|
|
313
|
-
const configs = new Map(
|
|
314
|
-
sdkOptions.fallbacks.map((config) => [config.name, config])
|
|
315
|
-
);
|
|
316
|
-
const clientReady = new Deferred();
|
|
317
|
-
const configSubscriptions = /* @__PURE__ */ new Map();
|
|
318
|
-
const clientSubscriptions = /* @__PURE__ */ new Set();
|
|
319
|
-
(async () => {
|
|
320
|
-
try {
|
|
321
|
-
const replicationStream = storage.startReplicationStream({
|
|
322
|
-
...sdkOptions,
|
|
323
|
-
getBody: () => ({
|
|
324
|
-
currentConfigs: [...configs.values()].map((config) => ({
|
|
325
|
-
name: config.name,
|
|
326
|
-
overrides: config.overrides,
|
|
327
|
-
value: config.value
|
|
328
|
-
})),
|
|
329
|
-
requiredConfigs: sdkOptions.requiredConfigs
|
|
330
|
-
})
|
|
331
|
-
});
|
|
332
|
-
for await (const event of replicationStream) {
|
|
333
|
-
const updatedConfigs = event.type === "config_change" ? [event.config] : event.configs;
|
|
334
|
-
for (const config of updatedConfigs) {
|
|
335
|
-
configs.set(config.name, {
|
|
336
|
-
name: config.name,
|
|
337
|
-
overrides: config.overrides,
|
|
338
|
-
value: config.value
|
|
339
|
-
});
|
|
340
|
-
for (const callback of clientSubscriptions) {
|
|
341
|
-
callback({ name: config.name, value: config.value });
|
|
342
|
-
}
|
|
343
|
-
for (const callback of configSubscriptions.get(config.name) ?? []) {
|
|
344
|
-
callback({ name: config.name, value: config.value });
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
clientReady.resolve();
|
|
348
|
-
}
|
|
349
|
-
} catch (error) {
|
|
350
|
-
sdkOptions.logger.error("Replane: error initializing client:", error);
|
|
351
|
-
clientReady.reject(error);
|
|
352
|
-
}
|
|
353
|
-
})();
|
|
354
|
-
function get(configName, getConfigOptions = {}) {
|
|
355
|
-
const config = configs.get(String(configName));
|
|
356
|
-
if (config === void 0) {
|
|
357
|
-
throw new ReplaneError({
|
|
358
|
-
message: `Config not found: ${String(configName)}`,
|
|
359
|
-
code: "not_found" /* NotFound */
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
try {
|
|
363
|
-
return evaluateOverrides(
|
|
364
|
-
config.value,
|
|
365
|
-
config.overrides,
|
|
366
|
-
{ ...sdkOptions.context, ...getConfigOptions?.context ?? {} },
|
|
367
|
-
sdkOptions.logger
|
|
368
|
-
);
|
|
369
|
-
} catch (error) {
|
|
370
|
-
sdkOptions.logger.error(
|
|
371
|
-
`Replane: error evaluating overrides for config ${String(configName)}:`,
|
|
372
|
-
error
|
|
373
|
-
);
|
|
374
|
-
return config.value;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
const subscribe = (callbackOrConfigName, callbackOrUndefined) => {
|
|
378
|
-
let configName = void 0;
|
|
379
|
-
let callback;
|
|
380
|
-
if (typeof callbackOrConfigName === "function") {
|
|
381
|
-
callback = callbackOrConfigName;
|
|
382
|
-
} else {
|
|
383
|
-
configName = callbackOrConfigName;
|
|
384
|
-
if (callbackOrUndefined === void 0) {
|
|
385
|
-
throw new Error("callback is required when config name is provided");
|
|
386
|
-
}
|
|
387
|
-
callback = callbackOrUndefined;
|
|
388
|
-
}
|
|
389
|
-
const originalCallback = callback;
|
|
390
|
-
callback = (...args) => {
|
|
391
|
-
originalCallback(...args);
|
|
392
|
-
};
|
|
393
|
-
if (configName === void 0) {
|
|
394
|
-
clientSubscriptions.add(callback);
|
|
395
|
-
return () => {
|
|
396
|
-
clientSubscriptions.delete(callback);
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
if (!configSubscriptions.has(configName)) {
|
|
400
|
-
configSubscriptions.set(configName, /* @__PURE__ */ new Set());
|
|
401
|
-
}
|
|
402
|
-
configSubscriptions.get(configName).add(callback);
|
|
403
|
-
return () => {
|
|
404
|
-
configSubscriptions.get(configName)?.delete(callback);
|
|
405
|
-
if (configSubscriptions.get(configName)?.size === 0) {
|
|
406
|
-
configSubscriptions.delete(configName);
|
|
407
|
-
}
|
|
408
|
-
};
|
|
409
|
-
};
|
|
410
|
-
const close = () => storage.close();
|
|
411
|
-
const initializationTimeoutId = setTimeout(() => {
|
|
412
|
-
if (sdkOptions.fallbacks.length === 0) {
|
|
413
|
-
close();
|
|
414
|
-
clientReady.reject(
|
|
415
|
-
new ReplaneError({
|
|
416
|
-
message: "Replane client initialization timed out",
|
|
417
|
-
code: "timeout" /* Timeout */
|
|
418
|
-
})
|
|
419
|
-
);
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
const missingRequiredConfigs = [];
|
|
423
|
-
for (const requiredConfigName of sdkOptions.requiredConfigs) {
|
|
424
|
-
if (!configs.has(requiredConfigName)) {
|
|
425
|
-
missingRequiredConfigs.push(requiredConfigName);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (missingRequiredConfigs.length > 0) {
|
|
429
|
-
close();
|
|
430
|
-
clientReady.reject(
|
|
431
|
-
new ReplaneError({
|
|
432
|
-
message: `Required configs are missing: ${missingRequiredConfigs.join(", ")}`,
|
|
433
|
-
code: "not_found" /* NotFound */
|
|
434
|
-
})
|
|
435
|
-
);
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
clientReady.resolve();
|
|
439
|
-
}, sdkOptions.initializationTimeoutMs);
|
|
440
|
-
clientReady.promise.then(() => clearTimeout(initializationTimeoutId));
|
|
441
|
-
await clientReady.promise;
|
|
442
|
-
return {
|
|
443
|
-
get,
|
|
444
|
-
subscribe,
|
|
445
|
-
close
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
function toFinalOptions(defaults) {
|
|
449
|
-
return {
|
|
450
|
-
sdkKey: defaults.sdkKey,
|
|
451
|
-
baseUrl: defaults.baseUrl.replace(/\/+$/, ""),
|
|
452
|
-
fetchFn: defaults.fetchFn ?? // some browsers require binding the fetch function to window
|
|
453
|
-
globalThis.fetch.bind(globalThis),
|
|
454
|
-
requestTimeoutMs: defaults.requestTimeoutMs ?? 2e3,
|
|
455
|
-
initializationTimeoutMs: defaults.initializationTimeoutMs ?? 5e3,
|
|
456
|
-
inactivityTimeoutMs: defaults.inactivityTimeoutMs ?? 3e4,
|
|
457
|
-
logger: defaults.logger ?? console,
|
|
458
|
-
retryDelayMs: defaults.retryDelayMs ?? 200,
|
|
459
|
-
context: {
|
|
460
|
-
...defaults.context ?? {}
|
|
461
|
-
},
|
|
462
|
-
requiredConfigs: Array.isArray(defaults.required) ? defaults.required.map((name) => String(name)) : Object.entries(defaults.required ?? {}).filter(([_, value]) => value !== void 0).map(([name]) => name),
|
|
463
|
-
fallbacks: Object.entries(defaults.fallbacks ?? {}).filter(([_, value]) => value !== void 0).map(([name, value]) => ({
|
|
464
|
-
name,
|
|
465
|
-
overrides: [],
|
|
466
|
-
version: -1,
|
|
467
|
-
value
|
|
468
|
-
}))
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
async function fetchWithTimeout(input, init, timeoutMs, fetchFn) {
|
|
472
|
-
if (!fetchFn) {
|
|
473
|
-
throw new Error("Global fetch is not available. Provide options.fetchFn.");
|
|
474
|
-
}
|
|
475
|
-
if (!timeoutMs) return fetchFn(input, init);
|
|
476
|
-
const timeoutController = new AbortController();
|
|
477
|
-
const t = setTimeout(() => timeoutController.abort(), timeoutMs);
|
|
478
|
-
const { signal } = combineAbortSignals([init.signal, timeoutController.signal]);
|
|
479
|
-
try {
|
|
480
|
-
return await fetchFn(input, {
|
|
481
|
-
...init,
|
|
482
|
-
signal
|
|
483
|
-
});
|
|
484
|
-
} finally {
|
|
485
|
-
clearTimeout(t);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
const SSE_DATA_PREFIX = "data:";
|
|
489
|
-
async function* fetchSse(params) {
|
|
490
|
-
const abortController = new AbortController();
|
|
491
|
-
const { signal, cleanUpSignals } = params.signal ? combineAbortSignals([params.signal, abortController.signal]) : { signal: abortController.signal, cleanUpSignals: () => {
|
|
492
|
-
} };
|
|
493
|
-
try {
|
|
494
|
-
const res = await fetchWithTimeout(
|
|
495
|
-
params.url,
|
|
496
|
-
{
|
|
497
|
-
method: params.method ?? "GET",
|
|
498
|
-
headers: { Accept: "text/event-stream", ...params.headers ?? {} },
|
|
499
|
-
body: params.body,
|
|
500
|
-
signal
|
|
501
|
-
},
|
|
502
|
-
params.timeoutMs,
|
|
503
|
-
params.fetchFn
|
|
504
|
-
);
|
|
505
|
-
await ensureSuccessfulResponse(res, `SSE ${params.url}`);
|
|
506
|
-
const responseContentType = res.headers.get("content-type") ?? "";
|
|
507
|
-
if (!responseContentType.includes("text/event-stream")) {
|
|
508
|
-
throw new ReplaneError({
|
|
509
|
-
message: `Expected text/event-stream, got "${responseContentType}"`,
|
|
510
|
-
code: "server_error" /* ServerError */
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
if (!res.body) {
|
|
514
|
-
throw new ReplaneError({
|
|
515
|
-
message: `Failed to fetch SSE ${params.url}: body is empty`,
|
|
516
|
-
code: "unknown" /* Unknown */
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
if (params.onConnect) {
|
|
520
|
-
params.onConnect();
|
|
521
|
-
}
|
|
522
|
-
const decoded = res.body.pipeThrough(new TextDecoderStream());
|
|
523
|
-
const reader = decoded.getReader();
|
|
524
|
-
let buffer = "";
|
|
525
|
-
try {
|
|
526
|
-
while (true) {
|
|
527
|
-
const { value, done } = await reader.read();
|
|
528
|
-
if (done) break;
|
|
529
|
-
buffer += value;
|
|
530
|
-
const frames = buffer.split(/\r?\n\r?\n/);
|
|
531
|
-
buffer = frames.pop() ?? "";
|
|
532
|
-
for (const frame of frames) {
|
|
533
|
-
const dataLines = [];
|
|
534
|
-
let comment = null;
|
|
535
|
-
for (const rawLine of frame.split(/\r?\n/)) {
|
|
536
|
-
if (!rawLine) continue;
|
|
537
|
-
if (rawLine.startsWith(":")) {
|
|
538
|
-
comment = rawLine.slice(1);
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
if (rawLine.startsWith(SSE_DATA_PREFIX)) {
|
|
542
|
-
const line = rawLine.slice(SSE_DATA_PREFIX.length).replace(/^\s/, "");
|
|
543
|
-
dataLines.push(line);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
if (dataLines.length) {
|
|
547
|
-
const data = dataLines.join("\n");
|
|
548
|
-
yield { type: "data", data };
|
|
549
|
-
} else if (comment !== null) {
|
|
550
|
-
yield { type: "comment", comment };
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
} finally {
|
|
555
|
-
try {
|
|
556
|
-
await reader.cancel();
|
|
557
|
-
} catch {
|
|
558
|
-
}
|
|
559
|
-
abortController.abort();
|
|
560
|
-
}
|
|
561
|
-
} finally {
|
|
562
|
-
cleanUpSignals();
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
async function ensureSuccessfulResponse(response, message) {
|
|
566
|
-
if (response.status === 404) {
|
|
567
|
-
throw new ReplaneError({
|
|
568
|
-
message: `Not found: ${message}`,
|
|
569
|
-
code: "not_found" /* NotFound */
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
if (response.status === 401) {
|
|
573
|
-
throw new ReplaneError({
|
|
574
|
-
message: `Unauthorized access: ${message}`,
|
|
575
|
-
code: "auth_error" /* AuthError */
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
if (response.status === 403) {
|
|
579
|
-
throw new ReplaneError({
|
|
580
|
-
message: `Forbidden access: ${message}`,
|
|
581
|
-
code: "forbidden" /* Forbidden */
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
if (!response.ok) {
|
|
585
|
-
let body;
|
|
586
|
-
try {
|
|
587
|
-
body = await response.text();
|
|
588
|
-
} catch {
|
|
589
|
-
body = "<unable to read response body>";
|
|
590
|
-
}
|
|
591
|
-
const code = response.status >= 500 ? "server_error" /* ServerError */ : response.status >= 400 ? "client_error" /* ClientError */ : "unknown" /* Unknown */;
|
|
592
|
-
throw new ReplaneError({
|
|
593
|
-
message: `Fetch response isn't successful (${message}): ${response.status} ${response.statusText} - ${body}`,
|
|
594
|
-
code
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
function combineAbortSignals(signals) {
|
|
599
|
-
const controller = new AbortController();
|
|
600
|
-
const onAbort = () => {
|
|
601
|
-
controller.abort();
|
|
602
|
-
cleanUpSignals();
|
|
603
|
-
};
|
|
604
|
-
const cleanUpSignals = () => {
|
|
605
|
-
for (const s of signals) {
|
|
606
|
-
s?.removeEventListener("abort", onAbort);
|
|
607
|
-
}
|
|
608
|
-
};
|
|
609
|
-
for (const s of signals) {
|
|
610
|
-
s?.addEventListener("abort", onAbort, { once: true });
|
|
611
|
-
}
|
|
612
|
-
if (signals.some((s) => s?.aborted)) {
|
|
613
|
-
onAbort();
|
|
614
|
-
}
|
|
615
|
-
return { signal: controller.signal, cleanUpSignals };
|
|
616
|
-
}
|
|
617
|
-
class Deferred {
|
|
618
|
-
promise;
|
|
619
|
-
resolve;
|
|
620
|
-
reject;
|
|
621
|
-
constructor() {
|
|
622
|
-
this.promise = new Promise((resolve, reject) => {
|
|
623
|
-
this.resolve = resolve;
|
|
624
|
-
this.reject = reject;
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
}
|
|
28
|
+
var import_client = require("./client");
|
|
29
|
+
var import_error = require("./error");
|
|
628
30
|
// Annotate the CommonJS export names for ESM import in node:
|
|
629
31
|
0 && (module.exports = {
|
|
630
32
|
ReplaneError,
|
|
33
|
+
ReplaneErrorCode,
|
|
631
34
|
createInMemoryReplaneClient,
|
|
632
|
-
createReplaneClient
|
|
35
|
+
createReplaneClient,
|
|
36
|
+
restoreReplaneClient
|
|
633
37
|
});
|