@quonfig/node 0.0.1 → 0.0.2
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 +2 -1
- package/dist/index.cjs +169 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -6
- package/dist/index.d.ts +32 -6
- package/dist/index.js +169 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -56,6 +56,23 @@ interface ConfigEnvelope {
|
|
|
56
56
|
configs: ConfigResponse[];
|
|
57
57
|
meta: Meta;
|
|
58
58
|
}
|
|
59
|
+
interface WorkspaceEnvironment {
|
|
60
|
+
id: string;
|
|
61
|
+
rules: Rule[];
|
|
62
|
+
}
|
|
63
|
+
interface WorkspaceConfigDocument {
|
|
64
|
+
id?: string;
|
|
65
|
+
key: string;
|
|
66
|
+
type: ConfigTypeString;
|
|
67
|
+
valueType: ValueType;
|
|
68
|
+
sendToClientSdk?: boolean;
|
|
69
|
+
default?: RuleSet;
|
|
70
|
+
environments?: WorkspaceEnvironment[];
|
|
71
|
+
}
|
|
72
|
+
type QuonfigDatadirEnvironments = Record<string, string> | Array<string | {
|
|
73
|
+
id?: string;
|
|
74
|
+
name?: string;
|
|
75
|
+
}>;
|
|
59
76
|
type ContextValue = string | number | boolean | string[] | null | undefined;
|
|
60
77
|
type Contexts = {
|
|
61
78
|
[contextName: string]: {
|
|
@@ -67,7 +84,10 @@ type OnNoDefault = "error" | "warn" | "ignore";
|
|
|
67
84
|
type ContextUploadMode = "none" | "shapes_only" | "periodic_example";
|
|
68
85
|
interface QuonfigOptions {
|
|
69
86
|
sdkKey: string;
|
|
87
|
+
/** @deprecated Use apiUrls instead. If provided, used as a single-element URL list. */
|
|
70
88
|
apiUrl?: string;
|
|
89
|
+
/** Ordered list of API base URLs to try. Defaults to ["https://primary.quonfig.com", "https://secondary.quonfig.com"]. */
|
|
90
|
+
apiUrls?: string[];
|
|
71
91
|
/** Base URL for the dedicated telemetry service. Defaults to https://telemetry.quonfig.com. Overridden by QUONFIG_TELEMETRY_URL env var. */
|
|
72
92
|
telemetryUrl?: string;
|
|
73
93
|
enableSSE?: boolean;
|
|
@@ -80,6 +100,7 @@ interface QuonfigOptions {
|
|
|
80
100
|
collectLoggerCounts?: boolean;
|
|
81
101
|
contextUploadMode?: ContextUploadMode;
|
|
82
102
|
initTimeout?: number;
|
|
103
|
+
datadir?: string;
|
|
83
104
|
datafile?: string | object;
|
|
84
105
|
}
|
|
85
106
|
interface EvalMatch {
|
|
@@ -196,7 +217,7 @@ declare class BoundQuonfig {
|
|
|
196
217
|
*/
|
|
197
218
|
declare class Quonfig {
|
|
198
219
|
private readonly sdkKey;
|
|
199
|
-
private readonly
|
|
220
|
+
private readonly apiUrls;
|
|
200
221
|
private readonly telemetryUrl?;
|
|
201
222
|
private readonly enableSSE;
|
|
202
223
|
private readonly enablePolling;
|
|
@@ -205,6 +226,7 @@ declare class Quonfig {
|
|
|
205
226
|
private readonly onNoDefault;
|
|
206
227
|
private readonly globalContext?;
|
|
207
228
|
private readonly initTimeout;
|
|
229
|
+
private readonly datadir?;
|
|
208
230
|
private readonly datafile?;
|
|
209
231
|
private store;
|
|
210
232
|
private evaluator;
|
|
@@ -221,7 +243,7 @@ declare class Quonfig {
|
|
|
221
243
|
private exampleContexts;
|
|
222
244
|
constructor(options: QuonfigOptions);
|
|
223
245
|
/**
|
|
224
|
-
* Initialize the SDK. Downloads configs from the API (or loads from datafile)
|
|
246
|
+
* Initialize the SDK. Downloads configs from the API (or loads from datadir/datafile)
|
|
225
247
|
* and starts background update mechanisms (SSE/polling).
|
|
226
248
|
*
|
|
227
249
|
* Must be called before using any get* methods.
|
|
@@ -287,7 +309,8 @@ declare class Quonfig {
|
|
|
287
309
|
close(): void;
|
|
288
310
|
private requireInitialized;
|
|
289
311
|
private handleNoDefault;
|
|
290
|
-
private
|
|
312
|
+
private loadLocalData;
|
|
313
|
+
private loadLocalEnvelope;
|
|
291
314
|
private fetchAndInstall;
|
|
292
315
|
private startSSE;
|
|
293
316
|
private startPolling;
|
|
@@ -466,11 +489,12 @@ interface FetchResult {
|
|
|
466
489
|
notChanged: boolean;
|
|
467
490
|
}
|
|
468
491
|
declare class Transport {
|
|
469
|
-
private
|
|
492
|
+
private baseUrls;
|
|
493
|
+
private activeBaseUrl;
|
|
470
494
|
private telemetryBaseUrl;
|
|
471
495
|
private sdkKey;
|
|
472
496
|
private etag;
|
|
473
|
-
constructor(
|
|
497
|
+
constructor(baseUrls: string[], sdkKey: string, telemetryBaseUrl?: string);
|
|
474
498
|
/**
|
|
475
499
|
* Build the Basic auth header value.
|
|
476
500
|
* Uses username "1" like the Go SDK: base64("1:{sdkKey}")
|
|
@@ -483,6 +507,7 @@ declare class Transport {
|
|
|
483
507
|
/**
|
|
484
508
|
* Fetch configs from GET /api/v2/configs with ETag caching.
|
|
485
509
|
*
|
|
510
|
+
* Tries each base URL in order. Returns the first successful result.
|
|
486
511
|
* Returns `{ notChanged: true }` if the server responds with 304.
|
|
487
512
|
*/
|
|
488
513
|
fetchConfigs(): Promise<FetchResult>;
|
|
@@ -492,6 +517,7 @@ declare class Transport {
|
|
|
492
517
|
postTelemetry(data: any): Promise<void>;
|
|
493
518
|
/**
|
|
494
519
|
* Get the SSE URL for config streaming.
|
|
520
|
+
* Uses whichever base URL last succeeded for fetchConfigs.
|
|
495
521
|
*/
|
|
496
522
|
getSSEUrl(): string;
|
|
497
523
|
/**
|
|
@@ -752,4 +778,4 @@ declare const encryption: {
|
|
|
752
778
|
generateNewHexKey: typeof generateNewHexKey;
|
|
753
779
|
};
|
|
754
780
|
|
|
755
|
-
export { BoundQuonfig, Client, type ClientOptions, type ConfigEnvelope, type ConfigResponse, ConfigStore, ConfigType, type ConfigTypeString, type ConfigValue, ConfigValueType, ContextShapeCollector, type ContextUploadMode, type ContextValue, type Contexts, type Criterion, type Environment, type EvalMatch, type Evaluation, EvaluationSummaryCollector, Evaluator, ExampleContextCollector, type GetValue, LOG_LEVEL_PREFIX, type LogLevelName, type LogLevelNumber, type Meta, type NodeServerConfigurationAccessor, type NodeServerConfigurationRaw, OP_ALWAYS_TRUE, OP_HIERARCHICAL_MATCH, OP_IN_INT_RANGE, OP_IN_SEG, OP_NOT_IN_SEG, OP_NOT_SET, OP_PROP_AFTER, OP_PROP_BEFORE, OP_PROP_CONTAINS_ONE_OF, OP_PROP_DOES_NOT_CONTAIN_ONE_OF, OP_PROP_DOES_NOT_END_WITH_ONE_OF, OP_PROP_DOES_NOT_MATCH, OP_PROP_DOES_NOT_START_WITH_ONE_OF, OP_PROP_ENDS_WITH_ONE_OF, OP_PROP_GREATER_THAN, OP_PROP_GREATER_THAN_OR_EQUAL, OP_PROP_IS_NOT_ONE_OF, OP_PROP_IS_ONE_OF, OP_PROP_LESS_THAN, OP_PROP_LESS_THAN_OR_EQUAL, OP_PROP_MATCHES, OP_PROP_SEMVER_EQUAL, OP_PROP_SEMVER_GREATER_THAN, OP_PROP_SEMVER_LESS_THAN, OP_PROP_STARTS_WITH_ONE_OF, type OnNoDefault, type ProjectEnvId, type ProvidedData, ProvidedSource, Quonfig, type QuonfigOptions, Resolver, type Rule, type RuleSet, type SchemaData, type SegmentResolver, type SemanticVersion, TelemetryReporter, Transport, type TypedNodeServerConfigurationAccessor, type TypedNodeServerConfigurationRaw, type Value, type ValueType, type WeightedValue, WeightedValueResolver, type WeightedValuesData, compareSemver, contextLookup, decrypt, durationToMilliseconds, encrypt, encryption, evaluateCriterion, generateNewHexKey, getContextValue, getProjectEnvFromSdkKey, hashZeroToOne, mergeContexts, parseLevel, parseSemver, shouldLog, valueTypeStringForConfig, wordLevelToNumber };
|
|
781
|
+
export { BoundQuonfig, Client, type ClientOptions, type ConfigEnvelope, type ConfigResponse, ConfigStore, ConfigType, type ConfigTypeString, type ConfigValue, ConfigValueType, ContextShapeCollector, type ContextUploadMode, type ContextValue, type Contexts, type Criterion, type Environment, type EvalMatch, type Evaluation, EvaluationSummaryCollector, Evaluator, ExampleContextCollector, type GetValue, LOG_LEVEL_PREFIX, type LogLevelName, type LogLevelNumber, type Meta, type NodeServerConfigurationAccessor, type NodeServerConfigurationRaw, OP_ALWAYS_TRUE, OP_HIERARCHICAL_MATCH, OP_IN_INT_RANGE, OP_IN_SEG, OP_NOT_IN_SEG, OP_NOT_SET, OP_PROP_AFTER, OP_PROP_BEFORE, OP_PROP_CONTAINS_ONE_OF, OP_PROP_DOES_NOT_CONTAIN_ONE_OF, OP_PROP_DOES_NOT_END_WITH_ONE_OF, OP_PROP_DOES_NOT_MATCH, OP_PROP_DOES_NOT_START_WITH_ONE_OF, OP_PROP_ENDS_WITH_ONE_OF, OP_PROP_GREATER_THAN, OP_PROP_GREATER_THAN_OR_EQUAL, OP_PROP_IS_NOT_ONE_OF, OP_PROP_IS_ONE_OF, OP_PROP_LESS_THAN, OP_PROP_LESS_THAN_OR_EQUAL, OP_PROP_MATCHES, OP_PROP_SEMVER_EQUAL, OP_PROP_SEMVER_GREATER_THAN, OP_PROP_SEMVER_LESS_THAN, OP_PROP_STARTS_WITH_ONE_OF, type OnNoDefault, type ProjectEnvId, type ProvidedData, ProvidedSource, Quonfig, type QuonfigDatadirEnvironments, type QuonfigOptions, Resolver, type Rule, type RuleSet, type SchemaData, type SegmentResolver, type SemanticVersion, TelemetryReporter, Transport, type TypedNodeServerConfigurationAccessor, type TypedNodeServerConfigurationRaw, type Value, type ValueType, type WeightedValue, WeightedValueResolver, type WeightedValuesData, type WorkspaceConfigDocument, type WorkspaceEnvironment, compareSemver, contextLookup, decrypt, durationToMilliseconds, encrypt, encryption, evaluateCriterion, generateNewHexKey, getContextValue, getProjectEnvFromSdkKey, hashZeroToOne, mergeContexts, parseLevel, parseSemver, shouldLog, valueTypeStringForConfig, wordLevelToNumber };
|
package/dist/index.d.ts
CHANGED
|
@@ -56,6 +56,23 @@ interface ConfigEnvelope {
|
|
|
56
56
|
configs: ConfigResponse[];
|
|
57
57
|
meta: Meta;
|
|
58
58
|
}
|
|
59
|
+
interface WorkspaceEnvironment {
|
|
60
|
+
id: string;
|
|
61
|
+
rules: Rule[];
|
|
62
|
+
}
|
|
63
|
+
interface WorkspaceConfigDocument {
|
|
64
|
+
id?: string;
|
|
65
|
+
key: string;
|
|
66
|
+
type: ConfigTypeString;
|
|
67
|
+
valueType: ValueType;
|
|
68
|
+
sendToClientSdk?: boolean;
|
|
69
|
+
default?: RuleSet;
|
|
70
|
+
environments?: WorkspaceEnvironment[];
|
|
71
|
+
}
|
|
72
|
+
type QuonfigDatadirEnvironments = Record<string, string> | Array<string | {
|
|
73
|
+
id?: string;
|
|
74
|
+
name?: string;
|
|
75
|
+
}>;
|
|
59
76
|
type ContextValue = string | number | boolean | string[] | null | undefined;
|
|
60
77
|
type Contexts = {
|
|
61
78
|
[contextName: string]: {
|
|
@@ -67,7 +84,10 @@ type OnNoDefault = "error" | "warn" | "ignore";
|
|
|
67
84
|
type ContextUploadMode = "none" | "shapes_only" | "periodic_example";
|
|
68
85
|
interface QuonfigOptions {
|
|
69
86
|
sdkKey: string;
|
|
87
|
+
/** @deprecated Use apiUrls instead. If provided, used as a single-element URL list. */
|
|
70
88
|
apiUrl?: string;
|
|
89
|
+
/** Ordered list of API base URLs to try. Defaults to ["https://primary.quonfig.com", "https://secondary.quonfig.com"]. */
|
|
90
|
+
apiUrls?: string[];
|
|
71
91
|
/** Base URL for the dedicated telemetry service. Defaults to https://telemetry.quonfig.com. Overridden by QUONFIG_TELEMETRY_URL env var. */
|
|
72
92
|
telemetryUrl?: string;
|
|
73
93
|
enableSSE?: boolean;
|
|
@@ -80,6 +100,7 @@ interface QuonfigOptions {
|
|
|
80
100
|
collectLoggerCounts?: boolean;
|
|
81
101
|
contextUploadMode?: ContextUploadMode;
|
|
82
102
|
initTimeout?: number;
|
|
103
|
+
datadir?: string;
|
|
83
104
|
datafile?: string | object;
|
|
84
105
|
}
|
|
85
106
|
interface EvalMatch {
|
|
@@ -196,7 +217,7 @@ declare class BoundQuonfig {
|
|
|
196
217
|
*/
|
|
197
218
|
declare class Quonfig {
|
|
198
219
|
private readonly sdkKey;
|
|
199
|
-
private readonly
|
|
220
|
+
private readonly apiUrls;
|
|
200
221
|
private readonly telemetryUrl?;
|
|
201
222
|
private readonly enableSSE;
|
|
202
223
|
private readonly enablePolling;
|
|
@@ -205,6 +226,7 @@ declare class Quonfig {
|
|
|
205
226
|
private readonly onNoDefault;
|
|
206
227
|
private readonly globalContext?;
|
|
207
228
|
private readonly initTimeout;
|
|
229
|
+
private readonly datadir?;
|
|
208
230
|
private readonly datafile?;
|
|
209
231
|
private store;
|
|
210
232
|
private evaluator;
|
|
@@ -221,7 +243,7 @@ declare class Quonfig {
|
|
|
221
243
|
private exampleContexts;
|
|
222
244
|
constructor(options: QuonfigOptions);
|
|
223
245
|
/**
|
|
224
|
-
* Initialize the SDK. Downloads configs from the API (or loads from datafile)
|
|
246
|
+
* Initialize the SDK. Downloads configs from the API (or loads from datadir/datafile)
|
|
225
247
|
* and starts background update mechanisms (SSE/polling).
|
|
226
248
|
*
|
|
227
249
|
* Must be called before using any get* methods.
|
|
@@ -287,7 +309,8 @@ declare class Quonfig {
|
|
|
287
309
|
close(): void;
|
|
288
310
|
private requireInitialized;
|
|
289
311
|
private handleNoDefault;
|
|
290
|
-
private
|
|
312
|
+
private loadLocalData;
|
|
313
|
+
private loadLocalEnvelope;
|
|
291
314
|
private fetchAndInstall;
|
|
292
315
|
private startSSE;
|
|
293
316
|
private startPolling;
|
|
@@ -466,11 +489,12 @@ interface FetchResult {
|
|
|
466
489
|
notChanged: boolean;
|
|
467
490
|
}
|
|
468
491
|
declare class Transport {
|
|
469
|
-
private
|
|
492
|
+
private baseUrls;
|
|
493
|
+
private activeBaseUrl;
|
|
470
494
|
private telemetryBaseUrl;
|
|
471
495
|
private sdkKey;
|
|
472
496
|
private etag;
|
|
473
|
-
constructor(
|
|
497
|
+
constructor(baseUrls: string[], sdkKey: string, telemetryBaseUrl?: string);
|
|
474
498
|
/**
|
|
475
499
|
* Build the Basic auth header value.
|
|
476
500
|
* Uses username "1" like the Go SDK: base64("1:{sdkKey}")
|
|
@@ -483,6 +507,7 @@ declare class Transport {
|
|
|
483
507
|
/**
|
|
484
508
|
* Fetch configs from GET /api/v2/configs with ETag caching.
|
|
485
509
|
*
|
|
510
|
+
* Tries each base URL in order. Returns the first successful result.
|
|
486
511
|
* Returns `{ notChanged: true }` if the server responds with 304.
|
|
487
512
|
*/
|
|
488
513
|
fetchConfigs(): Promise<FetchResult>;
|
|
@@ -492,6 +517,7 @@ declare class Transport {
|
|
|
492
517
|
postTelemetry(data: any): Promise<void>;
|
|
493
518
|
/**
|
|
494
519
|
* Get the SSE URL for config streaming.
|
|
520
|
+
* Uses whichever base URL last succeeded for fetchConfigs.
|
|
495
521
|
*/
|
|
496
522
|
getSSEUrl(): string;
|
|
497
523
|
/**
|
|
@@ -752,4 +778,4 @@ declare const encryption: {
|
|
|
752
778
|
generateNewHexKey: typeof generateNewHexKey;
|
|
753
779
|
};
|
|
754
780
|
|
|
755
|
-
export { BoundQuonfig, Client, type ClientOptions, type ConfigEnvelope, type ConfigResponse, ConfigStore, ConfigType, type ConfigTypeString, type ConfigValue, ConfigValueType, ContextShapeCollector, type ContextUploadMode, type ContextValue, type Contexts, type Criterion, type Environment, type EvalMatch, type Evaluation, EvaluationSummaryCollector, Evaluator, ExampleContextCollector, type GetValue, LOG_LEVEL_PREFIX, type LogLevelName, type LogLevelNumber, type Meta, type NodeServerConfigurationAccessor, type NodeServerConfigurationRaw, OP_ALWAYS_TRUE, OP_HIERARCHICAL_MATCH, OP_IN_INT_RANGE, OP_IN_SEG, OP_NOT_IN_SEG, OP_NOT_SET, OP_PROP_AFTER, OP_PROP_BEFORE, OP_PROP_CONTAINS_ONE_OF, OP_PROP_DOES_NOT_CONTAIN_ONE_OF, OP_PROP_DOES_NOT_END_WITH_ONE_OF, OP_PROP_DOES_NOT_MATCH, OP_PROP_DOES_NOT_START_WITH_ONE_OF, OP_PROP_ENDS_WITH_ONE_OF, OP_PROP_GREATER_THAN, OP_PROP_GREATER_THAN_OR_EQUAL, OP_PROP_IS_NOT_ONE_OF, OP_PROP_IS_ONE_OF, OP_PROP_LESS_THAN, OP_PROP_LESS_THAN_OR_EQUAL, OP_PROP_MATCHES, OP_PROP_SEMVER_EQUAL, OP_PROP_SEMVER_GREATER_THAN, OP_PROP_SEMVER_LESS_THAN, OP_PROP_STARTS_WITH_ONE_OF, type OnNoDefault, type ProjectEnvId, type ProvidedData, ProvidedSource, Quonfig, type QuonfigOptions, Resolver, type Rule, type RuleSet, type SchemaData, type SegmentResolver, type SemanticVersion, TelemetryReporter, Transport, type TypedNodeServerConfigurationAccessor, type TypedNodeServerConfigurationRaw, type Value, type ValueType, type WeightedValue, WeightedValueResolver, type WeightedValuesData, compareSemver, contextLookup, decrypt, durationToMilliseconds, encrypt, encryption, evaluateCriterion, generateNewHexKey, getContextValue, getProjectEnvFromSdkKey, hashZeroToOne, mergeContexts, parseLevel, parseSemver, shouldLog, valueTypeStringForConfig, wordLevelToNumber };
|
|
781
|
+
export { BoundQuonfig, Client, type ClientOptions, type ConfigEnvelope, type ConfigResponse, ConfigStore, ConfigType, type ConfigTypeString, type ConfigValue, ConfigValueType, ContextShapeCollector, type ContextUploadMode, type ContextValue, type Contexts, type Criterion, type Environment, type EvalMatch, type Evaluation, EvaluationSummaryCollector, Evaluator, ExampleContextCollector, type GetValue, LOG_LEVEL_PREFIX, type LogLevelName, type LogLevelNumber, type Meta, type NodeServerConfigurationAccessor, type NodeServerConfigurationRaw, OP_ALWAYS_TRUE, OP_HIERARCHICAL_MATCH, OP_IN_INT_RANGE, OP_IN_SEG, OP_NOT_IN_SEG, OP_NOT_SET, OP_PROP_AFTER, OP_PROP_BEFORE, OP_PROP_CONTAINS_ONE_OF, OP_PROP_DOES_NOT_CONTAIN_ONE_OF, OP_PROP_DOES_NOT_END_WITH_ONE_OF, OP_PROP_DOES_NOT_MATCH, OP_PROP_DOES_NOT_START_WITH_ONE_OF, OP_PROP_ENDS_WITH_ONE_OF, OP_PROP_GREATER_THAN, OP_PROP_GREATER_THAN_OR_EQUAL, OP_PROP_IS_NOT_ONE_OF, OP_PROP_IS_ONE_OF, OP_PROP_LESS_THAN, OP_PROP_LESS_THAN_OR_EQUAL, OP_PROP_MATCHES, OP_PROP_SEMVER_EQUAL, OP_PROP_SEMVER_GREATER_THAN, OP_PROP_SEMVER_LESS_THAN, OP_PROP_STARTS_WITH_ONE_OF, type OnNoDefault, type ProjectEnvId, type ProvidedData, ProvidedSource, Quonfig, type QuonfigDatadirEnvironments, type QuonfigOptions, Resolver, type Rule, type RuleSet, type SchemaData, type SegmentResolver, type SemanticVersion, TelemetryReporter, Transport, type TypedNodeServerConfigurationAccessor, type TypedNodeServerConfigurationRaw, type Value, type ValueType, type WeightedValue, WeightedValueResolver, type WeightedValuesData, type WorkspaceConfigDocument, type WorkspaceEnvironment, compareSemver, contextLookup, decrypt, durationToMilliseconds, encrypt, encryption, evaluateCriterion, generateNewHexKey, getContextValue, getProjectEnvFromSdkKey, hashZeroToOne, mergeContexts, parseLevel, parseSemver, shouldLog, valueTypeStringForConfig, wordLevelToNumber };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/quonfig.ts
|
|
2
2
|
import { randomUUID } from "crypto";
|
|
3
|
-
import { readFileSync } from "fs";
|
|
3
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
4
4
|
|
|
5
5
|
// src/store.ts
|
|
6
6
|
var ConfigStore = class {
|
|
@@ -825,12 +825,14 @@ function valueTypeForCoerced(valueType) {
|
|
|
825
825
|
var SDK_VERSION = "0.1.0";
|
|
826
826
|
var DEFAULT_TELEMETRY_URL = "https://telemetry.quonfig.com";
|
|
827
827
|
var Transport = class {
|
|
828
|
-
|
|
828
|
+
baseUrls;
|
|
829
|
+
activeBaseUrl;
|
|
829
830
|
telemetryBaseUrl;
|
|
830
831
|
sdkKey;
|
|
831
832
|
etag = "";
|
|
832
|
-
constructor(
|
|
833
|
-
this.
|
|
833
|
+
constructor(baseUrls, sdkKey, telemetryBaseUrl) {
|
|
834
|
+
this.baseUrls = baseUrls.map((u) => u.replace(/\/$/, ""));
|
|
835
|
+
this.activeBaseUrl = this.baseUrls[0];
|
|
834
836
|
const envUrl = process.env.QUONFIG_TELEMETRY_URL;
|
|
835
837
|
const url = envUrl || telemetryBaseUrl || DEFAULT_TELEMETRY_URL;
|
|
836
838
|
this.telemetryBaseUrl = url.replace(/\/$/, "");
|
|
@@ -857,30 +859,41 @@ var Transport = class {
|
|
|
857
859
|
/**
|
|
858
860
|
* Fetch configs from GET /api/v2/configs with ETag caching.
|
|
859
861
|
*
|
|
862
|
+
* Tries each base URL in order. Returns the first successful result.
|
|
860
863
|
* Returns `{ notChanged: true }` if the server responds with 304.
|
|
861
864
|
*/
|
|
862
865
|
async fetchConfigs() {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
866
|
+
let lastError;
|
|
867
|
+
for (const baseUrl of this.baseUrls) {
|
|
868
|
+
try {
|
|
869
|
+
const headers = this.getHeaders();
|
|
870
|
+
if (this.etag) {
|
|
871
|
+
headers["If-None-Match"] = this.etag;
|
|
872
|
+
}
|
|
873
|
+
const response = await fetch(`${baseUrl}/api/v2/configs`, {
|
|
874
|
+
method: "GET",
|
|
875
|
+
headers
|
|
876
|
+
});
|
|
877
|
+
if (response.status === 304) {
|
|
878
|
+
this.activeBaseUrl = baseUrl;
|
|
879
|
+
return { notChanged: true };
|
|
880
|
+
}
|
|
881
|
+
if (!response.ok) {
|
|
882
|
+
const body = await response.text().catch(() => "");
|
|
883
|
+
throw new Error(`Unexpected status ${response.status} from ${baseUrl}: ${body}`);
|
|
884
|
+
}
|
|
885
|
+
const etag = response.headers.get("ETag");
|
|
886
|
+
if (etag) {
|
|
887
|
+
this.etag = etag;
|
|
888
|
+
}
|
|
889
|
+
this.activeBaseUrl = baseUrl;
|
|
890
|
+
const envelope = await response.json();
|
|
891
|
+
return { envelope, notChanged: false };
|
|
892
|
+
} catch (err) {
|
|
893
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
894
|
+
}
|
|
881
895
|
}
|
|
882
|
-
|
|
883
|
-
return { envelope, notChanged: false };
|
|
896
|
+
throw lastError ?? new Error("All API URLs failed");
|
|
884
897
|
}
|
|
885
898
|
/**
|
|
886
899
|
* Post telemetry data to the telemetry endpoint.
|
|
@@ -901,9 +914,10 @@ var Transport = class {
|
|
|
901
914
|
}
|
|
902
915
|
/**
|
|
903
916
|
* Get the SSE URL for config streaming.
|
|
917
|
+
* Uses whichever base URL last succeeded for fetchConfigs.
|
|
904
918
|
*/
|
|
905
919
|
getSSEUrl() {
|
|
906
|
-
return `${this.
|
|
920
|
+
return `${this.activeBaseUrl}/api/v2/sse/config`;
|
|
907
921
|
}
|
|
908
922
|
/**
|
|
909
923
|
* Get auth headers for SSE connection.
|
|
@@ -1018,6 +1032,87 @@ function shouldLog(args) {
|
|
|
1018
1032
|
return defaultLevel <= desiredLevel;
|
|
1019
1033
|
}
|
|
1020
1034
|
|
|
1035
|
+
// src/datadir.ts
|
|
1036
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
1037
|
+
import { join } from "path";
|
|
1038
|
+
var CONFIG_SUBDIRS = ["configs", "feature-flags", "segments", "schemas", "log-levels"];
|
|
1039
|
+
function loadEnvelopeFromDatadir(datadir) {
|
|
1040
|
+
const environmentId = loadEnvironmentId(join(datadir, "environments.json"));
|
|
1041
|
+
const configs = [];
|
|
1042
|
+
for (const subdir of CONFIG_SUBDIRS) {
|
|
1043
|
+
const dir = join(datadir, subdir);
|
|
1044
|
+
if (!existsSync(dir)) {
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
const filenames = readdirSync(dir).filter((filename) => filename.endsWith(".json")).sort((a, b) => a.localeCompare(b));
|
|
1048
|
+
for (const filename of filenames) {
|
|
1049
|
+
const raw = JSON.parse(readFileSync(join(dir, filename), "utf-8"));
|
|
1050
|
+
configs.push(toConfigResponse(raw, environmentId));
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return {
|
|
1054
|
+
configs,
|
|
1055
|
+
meta: {
|
|
1056
|
+
version: `datadir:${datadir}`,
|
|
1057
|
+
environment: environmentId
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function loadEnvironmentId(environmentsPath) {
|
|
1062
|
+
if (!existsSync(environmentsPath)) {
|
|
1063
|
+
throw new Error(`[quonfig] Datadir is missing environments.json: ${environmentsPath}`);
|
|
1064
|
+
}
|
|
1065
|
+
const environments = JSON.parse(readFileSync(environmentsPath, "utf-8"));
|
|
1066
|
+
const candidates = normalizeEnvironmentCandidates(
|
|
1067
|
+
isWrappedEnvironmentList(environments) ? environments.environments : environments
|
|
1068
|
+
);
|
|
1069
|
+
if (candidates.length === 0) {
|
|
1070
|
+
return "";
|
|
1071
|
+
}
|
|
1072
|
+
return candidates[0];
|
|
1073
|
+
}
|
|
1074
|
+
function isWrappedEnvironmentList(value) {
|
|
1075
|
+
return Boolean(
|
|
1076
|
+
value && typeof value === "object" && !Array.isArray(value) && "environments" in value
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
function normalizeEnvironmentCandidates(environments) {
|
|
1080
|
+
if (!environments) {
|
|
1081
|
+
return [];
|
|
1082
|
+
}
|
|
1083
|
+
if (Array.isArray(environments)) {
|
|
1084
|
+
return environments.map((entry) => {
|
|
1085
|
+
if (typeof entry === "string") {
|
|
1086
|
+
return entry;
|
|
1087
|
+
}
|
|
1088
|
+
if (entry && typeof entry === "object") {
|
|
1089
|
+
return entry.id ?? entry.name;
|
|
1090
|
+
}
|
|
1091
|
+
return void 0;
|
|
1092
|
+
}).filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
1093
|
+
}
|
|
1094
|
+
if (environments && typeof environments === "object") {
|
|
1095
|
+
const values = Object.values(environments).map((value) => typeof value === "string" && value.length > 0 ? value : void 0).filter((value) => typeof value === "string");
|
|
1096
|
+
if (values.length > 0) {
|
|
1097
|
+
return values;
|
|
1098
|
+
}
|
|
1099
|
+
return Object.keys(environments).filter((key) => key.length > 0);
|
|
1100
|
+
}
|
|
1101
|
+
return [];
|
|
1102
|
+
}
|
|
1103
|
+
function toConfigResponse(raw, environmentId) {
|
|
1104
|
+
const environment = raw.environments?.find((candidate) => candidate.id === environmentId);
|
|
1105
|
+
return {
|
|
1106
|
+
id: raw.id ?? "",
|
|
1107
|
+
key: raw.key,
|
|
1108
|
+
type: raw.type,
|
|
1109
|
+
valueType: raw.valueType,
|
|
1110
|
+
sendToClientSdk: raw.sendToClientSdk ?? false,
|
|
1111
|
+
default: raw.default ?? { rules: [] },
|
|
1112
|
+
environment
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1021
1116
|
// src/telemetry/evaluationSummaries.ts
|
|
1022
1117
|
var EvaluationSummaryCollector = class {
|
|
1023
1118
|
enabled;
|
|
@@ -1287,7 +1382,10 @@ var TelemetryReporter = class {
|
|
|
1287
1382
|
};
|
|
1288
1383
|
|
|
1289
1384
|
// src/quonfig.ts
|
|
1290
|
-
var
|
|
1385
|
+
var DEFAULT_API_URLS = [
|
|
1386
|
+
"https://primary.quonfig.com",
|
|
1387
|
+
"https://secondary.quonfig.com"
|
|
1388
|
+
];
|
|
1291
1389
|
var DEFAULT_POLL_INTERVAL = 6e4;
|
|
1292
1390
|
var DEFAULT_INIT_TIMEOUT = 1e4;
|
|
1293
1391
|
var DEFAULT_LOG_LEVEL = 5;
|
|
@@ -1337,7 +1435,7 @@ var BoundQuonfig = class _BoundQuonfig {
|
|
|
1337
1435
|
};
|
|
1338
1436
|
var Quonfig = class {
|
|
1339
1437
|
sdkKey;
|
|
1340
|
-
|
|
1438
|
+
apiUrls;
|
|
1341
1439
|
telemetryUrl;
|
|
1342
1440
|
enableSSE;
|
|
1343
1441
|
enablePolling;
|
|
@@ -1346,6 +1444,7 @@ var Quonfig = class {
|
|
|
1346
1444
|
onNoDefault;
|
|
1347
1445
|
globalContext;
|
|
1348
1446
|
initTimeout;
|
|
1447
|
+
datadir;
|
|
1349
1448
|
datafile;
|
|
1350
1449
|
store;
|
|
1351
1450
|
evaluator;
|
|
@@ -1363,7 +1462,10 @@ var Quonfig = class {
|
|
|
1363
1462
|
exampleContexts;
|
|
1364
1463
|
constructor(options) {
|
|
1365
1464
|
this.sdkKey = options.sdkKey;
|
|
1366
|
-
this.
|
|
1465
|
+
this.apiUrls = options.apiUrls ?? (options.apiUrl ? [options.apiUrl] : DEFAULT_API_URLS);
|
|
1466
|
+
if (this.apiUrls.length === 0) {
|
|
1467
|
+
throw new Error("[quonfig] apiUrls must not be empty");
|
|
1468
|
+
}
|
|
1367
1469
|
this.telemetryUrl = options.telemetryUrl;
|
|
1368
1470
|
this.enableSSE = options.enableSSE ?? true;
|
|
1369
1471
|
this.enablePolling = options.enablePolling ?? false;
|
|
@@ -1372,12 +1474,13 @@ var Quonfig = class {
|
|
|
1372
1474
|
this.onNoDefault = options.onNoDefault ?? "error";
|
|
1373
1475
|
this.globalContext = options.globalContext;
|
|
1374
1476
|
this.initTimeout = options.initTimeout ?? DEFAULT_INIT_TIMEOUT;
|
|
1477
|
+
this.datadir = options.datadir;
|
|
1375
1478
|
this.datafile = options.datafile;
|
|
1376
1479
|
this.instanceHash = randomUUID();
|
|
1377
1480
|
this.store = new ConfigStore();
|
|
1378
1481
|
this.evaluator = new Evaluator(this.store);
|
|
1379
1482
|
this.resolver = new Resolver(this.store, this.evaluator);
|
|
1380
|
-
this.transport = new Transport(this.
|
|
1483
|
+
this.transport = new Transport(this.apiUrls, this.sdkKey, this.telemetryUrl);
|
|
1381
1484
|
const contextUploadMode = options.contextUploadMode ?? "periodic_example";
|
|
1382
1485
|
this.evaluationSummaries = new EvaluationSummaryCollector(
|
|
1383
1486
|
options.collectEvaluationSummaries ?? true
|
|
@@ -1386,14 +1489,14 @@ var Quonfig = class {
|
|
|
1386
1489
|
this.exampleContexts = new ExampleContextCollector(contextUploadMode);
|
|
1387
1490
|
}
|
|
1388
1491
|
/**
|
|
1389
|
-
* Initialize the SDK. Downloads configs from the API (or loads from datafile)
|
|
1492
|
+
* Initialize the SDK. Downloads configs from the API (or loads from datadir/datafile)
|
|
1390
1493
|
* and starts background update mechanisms (SSE/polling).
|
|
1391
1494
|
*
|
|
1392
1495
|
* Must be called before using any get* methods.
|
|
1393
1496
|
*/
|
|
1394
1497
|
async init() {
|
|
1395
|
-
if (this.datafile) {
|
|
1396
|
-
this.
|
|
1498
|
+
if (this.datadir || this.datafile) {
|
|
1499
|
+
this.loadLocalData();
|
|
1397
1500
|
this.initialized = true;
|
|
1398
1501
|
return;
|
|
1399
1502
|
}
|
|
@@ -1598,19 +1701,24 @@ var Quonfig = class {
|
|
|
1598
1701
|
return void 0;
|
|
1599
1702
|
}
|
|
1600
1703
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
if (typeof this.datafile === "string") {
|
|
1604
|
-
const raw = readFileSync(this.datafile, "utf-8");
|
|
1605
|
-
data = JSON.parse(raw);
|
|
1606
|
-
} else if (typeof this.datafile === "object") {
|
|
1607
|
-
data = this.datafile;
|
|
1608
|
-
} else {
|
|
1609
|
-
throw new Error("Invalid datafile option");
|
|
1610
|
-
}
|
|
1704
|
+
loadLocalData() {
|
|
1705
|
+
const data = this.loadLocalEnvelope();
|
|
1611
1706
|
this.store.update(data);
|
|
1612
1707
|
this.environmentId = data.meta.environment;
|
|
1613
1708
|
}
|
|
1709
|
+
loadLocalEnvelope() {
|
|
1710
|
+
if (this.datadir) {
|
|
1711
|
+
return loadEnvelopeFromDatadir(this.datadir);
|
|
1712
|
+
}
|
|
1713
|
+
if (typeof this.datafile === "string") {
|
|
1714
|
+
const raw = readFileSync2(this.datafile, "utf-8");
|
|
1715
|
+
return JSON.parse(raw);
|
|
1716
|
+
}
|
|
1717
|
+
if (typeof this.datafile === "object") {
|
|
1718
|
+
return this.datafile;
|
|
1719
|
+
}
|
|
1720
|
+
throw new Error("Invalid local configuration: expected datadir or datafile");
|
|
1721
|
+
}
|
|
1614
1722
|
async fetchAndInstall() {
|
|
1615
1723
|
const result = await this.transport.fetchConfigs();
|
|
1616
1724
|
if (result.notChanged) {
|
|
@@ -1704,11 +1812,28 @@ var Client = class {
|
|
|
1704
1812
|
}
|
|
1705
1813
|
async post(path, payload) {
|
|
1706
1814
|
const url = `${this.apiUrl}${path}`;
|
|
1815
|
+
const isORPC = path.startsWith("/api/v1/");
|
|
1707
1816
|
this.log("ApiClient", `POST ${url}`);
|
|
1708
|
-
|
|
1817
|
+
const body = isORPC ? JSON.stringify({ json: payload }) : JSON.stringify(payload);
|
|
1818
|
+
const raw = await fetch(url, {
|
|
1709
1819
|
method: "POST",
|
|
1710
1820
|
headers: this.headers(),
|
|
1711
|
-
body
|
|
1821
|
+
body
|
|
1822
|
+
});
|
|
1823
|
+
if (!isORPC) return raw;
|
|
1824
|
+
const text = await raw.text();
|
|
1825
|
+
let unwrapped = text;
|
|
1826
|
+
try {
|
|
1827
|
+
const parsed = JSON.parse(text);
|
|
1828
|
+
if (parsed && typeof parsed === "object" && "json" in parsed) {
|
|
1829
|
+
unwrapped = JSON.stringify(parsed.json);
|
|
1830
|
+
}
|
|
1831
|
+
} catch {
|
|
1832
|
+
}
|
|
1833
|
+
return new Response(unwrapped, {
|
|
1834
|
+
status: raw.status,
|
|
1835
|
+
statusText: raw.statusText,
|
|
1836
|
+
headers: raw.headers
|
|
1712
1837
|
});
|
|
1713
1838
|
}
|
|
1714
1839
|
async put(path, payload) {
|