@quonfig/openfeature-node 2.1.0 → 3.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/README.md +2 -1
- package/dist/index.cjs +33 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +33 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -97,7 +97,8 @@ the native `@quonfig/node` SDK:
|
|
|
97
97
|
|
|
98
98
|
1. **Log levels** -- `shouldLog()` and `logger()` are native-only.
|
|
99
99
|
2. **`string_list` configs** -- must be accessed via `getObjectValue()` and cast to `string[]`.
|
|
100
|
-
3. **`duration` configs** --
|
|
100
|
+
3. **`duration` configs** -- native-only: read them with `getClient().getDuration()` (milliseconds).
|
|
101
|
+
`getNumberValue()` on a duration config returns your default with `TYPE_MISMATCH`.
|
|
101
102
|
4. **`bytes` configs** -- not accessible via OpenFeature (no binary type in OF).
|
|
102
103
|
5. **`keys()` and `rawConfig()`** -- native-only via `getClient()`.
|
|
103
104
|
6. **Context keys use dot-notation** -- `"user.email"`, not nested objects.
|
package/dist/index.cjs
CHANGED
|
@@ -123,17 +123,17 @@ var QuonfigProvider = class {
|
|
|
123
123
|
async resolveBooleanEvaluation(flagKey, defaultValue, context, _logger) {
|
|
124
124
|
const mappedCtx = mapContext(context, this.targetingKeyMapping);
|
|
125
125
|
const details = this.client.getBoolDetails(flagKey, mappedCtx);
|
|
126
|
-
return
|
|
126
|
+
return this.resolve(flagKey, details, defaultValue, BOOL_TYPES);
|
|
127
127
|
}
|
|
128
128
|
async resolveStringEvaluation(flagKey, defaultValue, context, _logger) {
|
|
129
129
|
const mappedCtx = mapContext(context, this.targetingKeyMapping);
|
|
130
130
|
const details = this.client.getStringDetails(flagKey, mappedCtx);
|
|
131
|
-
return
|
|
131
|
+
return this.resolve(flagKey, details, defaultValue, STRING_TYPES);
|
|
132
132
|
}
|
|
133
133
|
async resolveNumberEvaluation(flagKey, defaultValue, context, _logger) {
|
|
134
134
|
const mappedCtx = mapContext(context, this.targetingKeyMapping);
|
|
135
135
|
const details = this.client.getNumberDetails(flagKey, mappedCtx);
|
|
136
|
-
return
|
|
136
|
+
return this.resolve(flagKey, details, defaultValue, NUMBER_TYPES);
|
|
137
137
|
}
|
|
138
138
|
async resolveObjectEvaluation(flagKey, defaultValue, context, _logger) {
|
|
139
139
|
const mappedCtx = mapContext(context, this.targetingKeyMapping);
|
|
@@ -145,7 +145,32 @@ var QuonfigProvider = class {
|
|
|
145
145
|
);
|
|
146
146
|
}
|
|
147
147
|
const jsonDetails = this.client.getJSONDetails(flagKey, mappedCtx);
|
|
148
|
-
return
|
|
148
|
+
return this.resolve(flagKey, jsonDetails, defaultValue, OBJECT_TYPES);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Translate native details, telling a malformed value apart from a wrong-type
|
|
152
|
+
* read (qfg-9xsl). sdk-node's `*Details` getters report both as
|
|
153
|
+
* `TYPE_MISMATCH`, but a wrong-type read fails its check against the config's
|
|
154
|
+
* declared `valueType` before evaluating. So a `TYPE_MISMATCH` on a config
|
|
155
|
+
* whose declared type this read accepts means the value itself did not parse
|
|
156
|
+
* (a stored int "twelve", an ENV_VAR int set to "twelve"): `PARSE_ERROR`.
|
|
157
|
+
*/
|
|
158
|
+
resolve(flagKey, details, defaultValue, readTypes) {
|
|
159
|
+
const result = toResolutionDetails(details, defaultValue);
|
|
160
|
+
if (result.errorCode === import_server_sdk.ErrorCode.TYPE_MISMATCH) {
|
|
161
|
+
const valueType = this.declaredValueType(flagKey);
|
|
162
|
+
if (valueType !== void 0 && readTypes.includes(valueType)) {
|
|
163
|
+
result.errorCode = import_server_sdk.ErrorCode.PARSE_ERROR;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
declaredValueType(flagKey) {
|
|
169
|
+
try {
|
|
170
|
+
return this.client.rawConfig(flagKey)?.valueType;
|
|
171
|
+
} catch {
|
|
172
|
+
return void 0;
|
|
173
|
+
}
|
|
149
174
|
}
|
|
150
175
|
/**
|
|
151
176
|
* Escape hatch: access the underlying @quonfig/node client for native-only features
|
|
@@ -155,6 +180,10 @@ var QuonfigProvider = class {
|
|
|
155
180
|
return this.client;
|
|
156
181
|
}
|
|
157
182
|
};
|
|
183
|
+
var BOOL_TYPES = ["bool"];
|
|
184
|
+
var STRING_TYPES = ["string", "log_level"];
|
|
185
|
+
var NUMBER_TYPES = ["int", "double"];
|
|
186
|
+
var OBJECT_TYPES = ["string_list", "json"];
|
|
158
187
|
function toResolutionDetails(details, defaultValue) {
|
|
159
188
|
const common = {
|
|
160
189
|
variant: details.variant,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/context.ts","../src/errors.ts"],"sourcesContent":["export { QuonfigProvider } from \"./provider.js\";\nexport type { QuonfigProviderOptions } from \"./provider.js\";\nexport { mapContext } from \"./context.js\";\nexport { toErrorCode } from \"./errors.js\";\n","import {\n ErrorCode,\n EvaluationContext,\n JsonValue,\n Logger,\n OpenFeatureEventEmitter,\n Provider,\n ProviderEvents,\n ResolutionDetails,\n StandardResolutionReasons,\n} from \"@openfeature/server-sdk\";\nimport { EvaluationDetails, Quonfig, QuonfigOptions } from \"@quonfig/node\";\nimport { mapContext } from \"./context.js\";\n\nexport interface QuonfigProviderOptions extends Omit<QuonfigOptions, \"onConfigUpdate\"> {\n /**\n * Dot-notation path to map OpenFeature's `targetingKey` into Quonfig's nested context.\n * Defaults to \"user.id\".\n */\n targetingKeyMapping?: string;\n}\n\n/**\n * QuonfigProvider wraps the @quonfig/node native SDK and implements the\n * OpenFeature server-side Provider interface.\n *\n * Usage:\n * ```typescript\n * import { QuonfigProvider } from \"@quonfig/openfeature-node\";\n * import { OpenFeature } from \"@openfeature/server-sdk\";\n *\n * const provider = new QuonfigProvider({ sdkKey: \"qf_sk_...\" });\n * await OpenFeature.setProviderAndWait(provider);\n * const client = OpenFeature.getClient();\n * const enabled = await client.getBooleanValue(\"my-flag\", false);\n * ```\n */\nexport class QuonfigProvider implements Provider {\n readonly metadata = { name: \"quonfig\" } as const;\n readonly events = new OpenFeatureEventEmitter();\n readonly hooks = [];\n\n private readonly client: Quonfig;\n private readonly targetingKeyMapping: string;\n private closed = false;\n private initErrorTimer?: ReturnType<typeof setTimeout>;\n\n constructor(options: QuonfigProviderOptions) {\n this.targetingKeyMapping = options.targetingKeyMapping ?? \"user.id\";\n this.client = new Quonfig({\n ...options,\n onConfigUpdate: () => {\n this.events.emit(ProviderEvents.ConfigurationChanged, { flagsChanged: [] });\n },\n });\n }\n\n /**\n * Initialize the wrapped client. Status follows the client's `onInitFailure`\n * (design B.5), which you pass in the provider options with `initTimeout`:\n *\n * - `\"fail\"` (default): `init()` rejects, so this throws and the provider is\n * ERROR. The client is kept alive and retries in the background; on heal the\n * provider emits PROVIDER_READY.\n * - `\"wait\"`: blocks until the first config, so the provider is NOT_READY\n * throughout and READY when it resolves.\n * - `\"continue\"`: resolves on code defaults, then emits a non-fatal\n * PROVIDER_ERROR so the status is ERROR (evaluations still run and return\n * the caller default). On heal the provider emits PROVIDER_READY.\n */\n async initialize(_context?: EvaluationContext): Promise<void> {\n try {\n await this.client.init();\n } catch (err) {\n this.emitReadyOnHeal();\n throw err;\n }\n if (!this.client.isReady()) {\n // OpenFeature marks the provider READY once initialize() resolves, so\n // report the error after that has happened.\n this.initErrorTimer = setTimeout(() => {\n if (this.closed || this.client.isReady()) return;\n this.events.emit(ProviderEvents.Error, {\n message: \"[quonfig] no config loaded (onInitFailure=continue): serving code defaults\",\n errorCode: ErrorCode.PROVIDER_NOT_READY,\n });\n }, 0);\n this.emitReadyOnHeal();\n }\n }\n\n /** Emit PROVIDER_READY once the client's background retry loads config. */\n private emitReadyOnHeal(): void {\n this.client.waitForReady().then(\n () => {\n if (!this.closed) this.events.emit(ProviderEvents.Ready);\n },\n () => {} // closed before healing\n );\n }\n\n async shutdown(): Promise<void> {\n this.closed = true;\n clearTimeout(this.initErrorTimer);\n await this.client.close();\n }\n\n async resolveBooleanEvaluation(\n flagKey: string,\n defaultValue: boolean,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<boolean>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getBoolDetails(flagKey, mappedCtx);\n return toResolutionDetails(details, defaultValue);\n }\n\n async resolveStringEvaluation(\n flagKey: string,\n defaultValue: string,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<string>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getStringDetails(flagKey, mappedCtx);\n return toResolutionDetails(details, defaultValue);\n }\n\n async resolveNumberEvaluation(\n flagKey: string,\n defaultValue: number,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<number>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getNumberDetails(flagKey, mappedCtx);\n return toResolutionDetails(details, defaultValue);\n }\n\n async resolveObjectEvaluation<T extends JsonValue>(\n flagKey: string,\n defaultValue: T,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<T>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n // Try string_list first (returns string[]), fall back to JSON.\n const listDetails = this.client.getStringListDetails(flagKey, mappedCtx);\n if (\n listDetails.reason === \"STATIC\" ||\n listDetails.reason === \"TARGETING_MATCH\" ||\n listDetails.reason === \"SPLIT\"\n ) {\n return toResolutionDetails(\n listDetails as EvaluationDetails<unknown> as EvaluationDetails<T>,\n defaultValue\n );\n }\n const jsonDetails = this.client.getJSONDetails(flagKey, mappedCtx);\n return toResolutionDetails(jsonDetails as EvaluationDetails<T>, defaultValue);\n }\n\n /**\n * Escape hatch: access the underlying @quonfig/node client for native-only features\n * (shouldLog, keys, rawConfig, etc.)\n */\n getClient(): Quonfig {\n return this.client;\n }\n}\n\n/**\n * Translate a Quonfig EvaluationDetails<T> into an OpenFeature ResolutionDetails<T>.\n *\n * Reason mapping (from the brief):\n * STATIC → StandardResolutionReasons.STATIC\n * TARGETING_MATCH → StandardResolutionReasons.TARGETING_MATCH\n * SPLIT → StandardResolutionReasons.SPLIT\n * DEFAULT → defaultValue + StandardResolutionReasons.DEFAULT\n * ERROR → defaultValue + ERROR + errorCode (FLAG_NOT_FOUND / TYPE_MISMATCH /\n * PROVIDER_NOT_READY / GENERAL)\n */\nfunction toResolutionDetails<T>(\n details: EvaluationDetails<T>,\n defaultValue: T\n): ResolutionDetails<T> {\n const common = {\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n switch (details.reason) {\n case \"STATIC\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.STATIC,\n ...common,\n };\n case \"TARGETING_MATCH\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.TARGETING_MATCH,\n ...common,\n };\n case \"SPLIT\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.SPLIT,\n ...common,\n };\n case \"DEFAULT\":\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.DEFAULT,\n ...common,\n };\n case \"ERROR\":\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toOFErrorCode(details.errorCode),\n errorMessage: details.errorMessage,\n ...common,\n };\n }\n}\n\nfunction toOFErrorCode(code: EvaluationDetails<unknown>[\"errorCode\"]): ErrorCode {\n switch (code) {\n case \"FLAG_NOT_FOUND\":\n return ErrorCode.FLAG_NOT_FOUND;\n case \"TYPE_MISMATCH\":\n return ErrorCode.TYPE_MISMATCH;\n case \"PROVIDER_NOT_READY\":\n return ErrorCode.PROVIDER_NOT_READY;\n case \"GENERAL\":\n default:\n return ErrorCode.GENERAL;\n }\n}\n","import type { EvaluationContext } from \"@openfeature/server-sdk\";\nimport type { Contexts, ContextValue } from \"@quonfig/node\";\n\n/**\n * Maps an OpenFeature flat EvaluationContext to Quonfig's nested Contexts format.\n *\n * Rules:\n * - `targetingKey` maps to the namespace+property specified by `targetingKeyMapping` (default: \"user.id\")\n * - Keys with a dot are split on the first dot: \"user.email\" -> namespace \"user\", key \"email\"\n * - Keys without a dot go to the default (empty-string) namespace: \"country\" -> { \"\": { country: ... } }\n * - Multi-dot keys split on first dot only: \"user.ip.address\" -> { user: { \"ip.address\": ... } }\n */\nexport function mapContext(\n ofContext: EvaluationContext,\n targetingKeyMapping = \"user.id\"\n): Contexts {\n const result: Record<string, Record<string, ContextValue>> = {};\n\n for (const [key, value] of Object.entries(ofContext)) {\n if (value === undefined) continue;\n\n // Cast to ContextValue -- OpenFeature allows arbitrary nesting but Quonfig\n // contexts accept primitives and string arrays. Callers should pass only\n // primitive or string[] values for keys they want evaluated.\n const ctxValue = value as ContextValue;\n\n if (key === \"targetingKey\") {\n const dotIdx = targetingKeyMapping.indexOf(\".\");\n const ns = dotIdx === -1 ? \"\" : targetingKeyMapping.slice(0, dotIdx);\n const prop = dotIdx === -1 ? targetingKeyMapping : targetingKeyMapping.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = ctxValue;\n continue;\n }\n\n const dotIdx = key.indexOf(\".\");\n if (dotIdx === -1) {\n result[\"\"] ??= {};\n result[\"\"][key] = ctxValue;\n } else {\n const ns = key.slice(0, dotIdx);\n const prop = key.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = ctxValue;\n }\n }\n\n return result;\n}\n","import { ErrorCode } from \"@openfeature/server-sdk\";\n\n/**\n * Maps a native SDK error to an OpenFeature ErrorCode.\n *\n * The native SDK throws Error instances with message strings. We map by inspecting\n * the lowercased message.\n */\nexport function toErrorCode(err: unknown): ErrorCode {\n const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();\n\n if (\n msg.includes(\"not found\") ||\n msg.includes(\"flag not found\") ||\n msg.includes(\"no value found\") ||\n msg.includes(\"value found for key\")\n ) {\n return ErrorCode.FLAG_NOT_FOUND;\n }\n if (msg.includes(\"type mismatch\")) {\n return ErrorCode.TYPE_MISMATCH;\n }\n if (msg.includes(\"not initialized\") || msg.includes(\"provider not ready\")) {\n return ErrorCode.PROVIDER_NOT_READY;\n }\n return ErrorCode.GENERAL;\n}\n"],"mappings":"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","names":["dotIdx","import_server_sdk"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/context.ts","../src/errors.ts"],"sourcesContent":["export { QuonfigProvider } from \"./provider.js\";\nexport type { QuonfigProviderOptions } from \"./provider.js\";\nexport { mapContext } from \"./context.js\";\nexport { toErrorCode } from \"./errors.js\";\n","import {\n ErrorCode,\n EvaluationContext,\n JsonValue,\n Logger,\n OpenFeatureEventEmitter,\n Provider,\n ProviderEvents,\n ResolutionDetails,\n StandardResolutionReasons,\n} from \"@openfeature/server-sdk\";\nimport { EvaluationDetails, Quonfig, QuonfigOptions, ValueType } from \"@quonfig/node\";\nimport { mapContext } from \"./context.js\";\n\nexport interface QuonfigProviderOptions extends Omit<QuonfigOptions, \"onConfigUpdate\"> {\n /**\n * Dot-notation path to map OpenFeature's `targetingKey` into Quonfig's nested context.\n * Defaults to \"user.id\".\n */\n targetingKeyMapping?: string;\n}\n\n/**\n * QuonfigProvider wraps the @quonfig/node native SDK and implements the\n * OpenFeature server-side Provider interface.\n *\n * Usage:\n * ```typescript\n * import { QuonfigProvider } from \"@quonfig/openfeature-node\";\n * import { OpenFeature } from \"@openfeature/server-sdk\";\n *\n * const provider = new QuonfigProvider({ sdkKey: \"qf_sk_...\" });\n * await OpenFeature.setProviderAndWait(provider);\n * const client = OpenFeature.getClient();\n * const enabled = await client.getBooleanValue(\"my-flag\", false);\n * ```\n */\nexport class QuonfigProvider implements Provider {\n readonly metadata = { name: \"quonfig\" } as const;\n readonly events = new OpenFeatureEventEmitter();\n readonly hooks = [];\n\n private readonly client: Quonfig;\n private readonly targetingKeyMapping: string;\n private closed = false;\n private initErrorTimer?: ReturnType<typeof setTimeout>;\n\n constructor(options: QuonfigProviderOptions) {\n this.targetingKeyMapping = options.targetingKeyMapping ?? \"user.id\";\n this.client = new Quonfig({\n ...options,\n onConfigUpdate: () => {\n this.events.emit(ProviderEvents.ConfigurationChanged, { flagsChanged: [] });\n },\n });\n }\n\n /**\n * Initialize the wrapped client. Status follows the client's `onInitFailure`\n * (design B.5), which you pass in the provider options with `initTimeout`:\n *\n * - `\"fail\"` (default): `init()` rejects, so this throws and the provider is\n * ERROR. The client is kept alive and retries in the background; on heal the\n * provider emits PROVIDER_READY.\n * - `\"wait\"`: blocks until the first config, so the provider is NOT_READY\n * throughout and READY when it resolves.\n * - `\"continue\"`: resolves on code defaults, then emits a non-fatal\n * PROVIDER_ERROR so the status is ERROR (evaluations still run and return\n * the caller default). On heal the provider emits PROVIDER_READY.\n */\n async initialize(_context?: EvaluationContext): Promise<void> {\n try {\n await this.client.init();\n } catch (err) {\n this.emitReadyOnHeal();\n throw err;\n }\n if (!this.client.isReady()) {\n // OpenFeature marks the provider READY once initialize() resolves, so\n // report the error after that has happened.\n this.initErrorTimer = setTimeout(() => {\n if (this.closed || this.client.isReady()) return;\n this.events.emit(ProviderEvents.Error, {\n message: \"[quonfig] no config loaded (onInitFailure=continue): serving code defaults\",\n errorCode: ErrorCode.PROVIDER_NOT_READY,\n });\n }, 0);\n this.emitReadyOnHeal();\n }\n }\n\n /** Emit PROVIDER_READY once the client's background retry loads config. */\n private emitReadyOnHeal(): void {\n this.client.waitForReady().then(\n () => {\n if (!this.closed) this.events.emit(ProviderEvents.Ready);\n },\n () => {} // closed before healing\n );\n }\n\n async shutdown(): Promise<void> {\n this.closed = true;\n clearTimeout(this.initErrorTimer);\n await this.client.close();\n }\n\n async resolveBooleanEvaluation(\n flagKey: string,\n defaultValue: boolean,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<boolean>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getBoolDetails(flagKey, mappedCtx);\n return this.resolve(flagKey, details, defaultValue, BOOL_TYPES);\n }\n\n async resolveStringEvaluation(\n flagKey: string,\n defaultValue: string,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<string>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getStringDetails(flagKey, mappedCtx);\n return this.resolve(flagKey, details, defaultValue, STRING_TYPES);\n }\n\n async resolveNumberEvaluation(\n flagKey: string,\n defaultValue: number,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<number>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getNumberDetails(flagKey, mappedCtx);\n return this.resolve(flagKey, details, defaultValue, NUMBER_TYPES);\n }\n\n async resolveObjectEvaluation<T extends JsonValue>(\n flagKey: string,\n defaultValue: T,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<T>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n // Try string_list first (returns string[]), fall back to JSON.\n const listDetails = this.client.getStringListDetails(flagKey, mappedCtx);\n if (\n listDetails.reason === \"STATIC\" ||\n listDetails.reason === \"TARGETING_MATCH\" ||\n listDetails.reason === \"SPLIT\"\n ) {\n return toResolutionDetails(\n listDetails as EvaluationDetails<unknown> as EvaluationDetails<T>,\n defaultValue\n );\n }\n const jsonDetails = this.client.getJSONDetails(flagKey, mappedCtx);\n return this.resolve(flagKey, jsonDetails as EvaluationDetails<T>, defaultValue, OBJECT_TYPES);\n }\n\n /**\n * Translate native details, telling a malformed value apart from a wrong-type\n * read (qfg-9xsl). sdk-node's `*Details` getters report both as\n * `TYPE_MISMATCH`, but a wrong-type read fails its check against the config's\n * declared `valueType` before evaluating. So a `TYPE_MISMATCH` on a config\n * whose declared type this read accepts means the value itself did not parse\n * (a stored int \"twelve\", an ENV_VAR int set to \"twelve\"): `PARSE_ERROR`.\n */\n private resolve<T>(\n flagKey: string,\n details: EvaluationDetails<T>,\n defaultValue: T,\n readTypes: readonly ValueType[]\n ): ResolutionDetails<T> {\n const result = toResolutionDetails(details, defaultValue);\n if (result.errorCode === ErrorCode.TYPE_MISMATCH) {\n const valueType = this.declaredValueType(flagKey);\n if (valueType !== undefined && readTypes.includes(valueType)) {\n result.errorCode = ErrorCode.PARSE_ERROR;\n }\n }\n return result;\n }\n\n private declaredValueType(flagKey: string): ValueType | undefined {\n try {\n return this.client.rawConfig(flagKey)?.valueType;\n } catch {\n return undefined; // not initialized: keep the native error code\n }\n }\n\n /**\n * Escape hatch: access the underlying @quonfig/node client for native-only features\n * (shouldLog, keys, rawConfig, etc.)\n */\n getClient(): Quonfig {\n return this.client;\n }\n}\n\n// The declared valueTypes each OpenFeature read accepts; these mirror sdk-node's\n// own check of the requested type against a config's valueType.\nconst BOOL_TYPES: readonly ValueType[] = [\"bool\"];\nconst STRING_TYPES: readonly ValueType[] = [\"string\", \"log_level\"];\nconst NUMBER_TYPES: readonly ValueType[] = [\"int\", \"double\"];\nconst OBJECT_TYPES: readonly ValueType[] = [\"string_list\", \"json\"];\n\n/**\n * Translate a Quonfig EvaluationDetails<T> into an OpenFeature ResolutionDetails<T>.\n *\n * Reason mapping (from the brief):\n * STATIC → StandardResolutionReasons.STATIC\n * TARGETING_MATCH → StandardResolutionReasons.TARGETING_MATCH\n * SPLIT → StandardResolutionReasons.SPLIT\n * DEFAULT → defaultValue + StandardResolutionReasons.DEFAULT\n * ERROR → defaultValue + ERROR + errorCode (FLAG_NOT_FOUND / TYPE_MISMATCH /\n * PROVIDER_NOT_READY / GENERAL; QuonfigProvider.resolve refines a\n * malformed value's TYPE_MISMATCH to PARSE_ERROR)\n */\nfunction toResolutionDetails<T>(\n details: EvaluationDetails<T>,\n defaultValue: T\n): ResolutionDetails<T> {\n const common = {\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n switch (details.reason) {\n case \"STATIC\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.STATIC,\n ...common,\n };\n case \"TARGETING_MATCH\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.TARGETING_MATCH,\n ...common,\n };\n case \"SPLIT\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.SPLIT,\n ...common,\n };\n case \"DEFAULT\":\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.DEFAULT,\n ...common,\n };\n case \"ERROR\":\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toOFErrorCode(details.errorCode),\n errorMessage: details.errorMessage,\n ...common,\n };\n }\n}\n\nfunction toOFErrorCode(code: EvaluationDetails<unknown>[\"errorCode\"]): ErrorCode {\n switch (code) {\n case \"FLAG_NOT_FOUND\":\n return ErrorCode.FLAG_NOT_FOUND;\n case \"TYPE_MISMATCH\":\n return ErrorCode.TYPE_MISMATCH;\n case \"PROVIDER_NOT_READY\":\n return ErrorCode.PROVIDER_NOT_READY;\n case \"GENERAL\":\n default:\n return ErrorCode.GENERAL;\n }\n}\n","import type { EvaluationContext } from \"@openfeature/server-sdk\";\nimport type { Contexts, ContextValue } from \"@quonfig/node\";\n\n/**\n * Maps an OpenFeature flat EvaluationContext to Quonfig's nested Contexts format.\n *\n * Rules:\n * - `targetingKey` maps to the namespace+property specified by `targetingKeyMapping` (default: \"user.id\")\n * - Keys with a dot are split on the first dot: \"user.email\" -> namespace \"user\", key \"email\"\n * - Keys without a dot go to the default (empty-string) namespace: \"country\" -> { \"\": { country: ... } }\n * - Multi-dot keys split on first dot only: \"user.ip.address\" -> { user: { \"ip.address\": ... } }\n */\nexport function mapContext(\n ofContext: EvaluationContext,\n targetingKeyMapping = \"user.id\"\n): Contexts {\n const result: Record<string, Record<string, ContextValue>> = {};\n\n for (const [key, value] of Object.entries(ofContext)) {\n if (value === undefined) continue;\n\n // Cast to ContextValue -- OpenFeature allows arbitrary nesting but Quonfig\n // contexts accept primitives and string arrays. Callers should pass only\n // primitive or string[] values for keys they want evaluated.\n const ctxValue = value as ContextValue;\n\n if (key === \"targetingKey\") {\n const dotIdx = targetingKeyMapping.indexOf(\".\");\n const ns = dotIdx === -1 ? \"\" : targetingKeyMapping.slice(0, dotIdx);\n const prop = dotIdx === -1 ? targetingKeyMapping : targetingKeyMapping.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = ctxValue;\n continue;\n }\n\n const dotIdx = key.indexOf(\".\");\n if (dotIdx === -1) {\n result[\"\"] ??= {};\n result[\"\"][key] = ctxValue;\n } else {\n const ns = key.slice(0, dotIdx);\n const prop = key.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = ctxValue;\n }\n }\n\n return result;\n}\n","import { ErrorCode } from \"@openfeature/server-sdk\";\n\n/**\n * Maps a native SDK error to an OpenFeature ErrorCode.\n *\n * The native SDK throws Error instances with message strings. We map by inspecting\n * the lowercased message.\n */\nexport function toErrorCode(err: unknown): ErrorCode {\n const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();\n\n if (\n msg.includes(\"not found\") ||\n msg.includes(\"flag not found\") ||\n msg.includes(\"no value found\") ||\n msg.includes(\"value found for key\")\n ) {\n return ErrorCode.FLAG_NOT_FOUND;\n }\n if (msg.includes(\"type mismatch\")) {\n return ErrorCode.TYPE_MISMATCH;\n }\n if (msg.includes(\"not initialized\") || msg.includes(\"provider not ready\")) {\n return ErrorCode.PROVIDER_NOT_READY;\n }\n return ErrorCode.GENERAL;\n}\n"],"mappings":"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","names":["dotIdx","import_server_sdk"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -55,6 +55,16 @@ declare class QuonfigProvider implements Provider {
|
|
|
55
55
|
resolveStringEvaluation(flagKey: string, defaultValue: string, context: EvaluationContext, _logger: Logger): Promise<ResolutionDetails<string>>;
|
|
56
56
|
resolveNumberEvaluation(flagKey: string, defaultValue: number, context: EvaluationContext, _logger: Logger): Promise<ResolutionDetails<number>>;
|
|
57
57
|
resolveObjectEvaluation<T extends JsonValue>(flagKey: string, defaultValue: T, context: EvaluationContext, _logger: Logger): Promise<ResolutionDetails<T>>;
|
|
58
|
+
/**
|
|
59
|
+
* Translate native details, telling a malformed value apart from a wrong-type
|
|
60
|
+
* read (qfg-9xsl). sdk-node's `*Details` getters report both as
|
|
61
|
+
* `TYPE_MISMATCH`, but a wrong-type read fails its check against the config's
|
|
62
|
+
* declared `valueType` before evaluating. So a `TYPE_MISMATCH` on a config
|
|
63
|
+
* whose declared type this read accepts means the value itself did not parse
|
|
64
|
+
* (a stored int "twelve", an ENV_VAR int set to "twelve"): `PARSE_ERROR`.
|
|
65
|
+
*/
|
|
66
|
+
private resolve;
|
|
67
|
+
private declaredValueType;
|
|
58
68
|
/**
|
|
59
69
|
* Escape hatch: access the underlying @quonfig/node client for native-only features
|
|
60
70
|
* (shouldLog, keys, rawConfig, etc.)
|
package/dist/index.d.ts
CHANGED
|
@@ -55,6 +55,16 @@ declare class QuonfigProvider implements Provider {
|
|
|
55
55
|
resolveStringEvaluation(flagKey: string, defaultValue: string, context: EvaluationContext, _logger: Logger): Promise<ResolutionDetails<string>>;
|
|
56
56
|
resolveNumberEvaluation(flagKey: string, defaultValue: number, context: EvaluationContext, _logger: Logger): Promise<ResolutionDetails<number>>;
|
|
57
57
|
resolveObjectEvaluation<T extends JsonValue>(flagKey: string, defaultValue: T, context: EvaluationContext, _logger: Logger): Promise<ResolutionDetails<T>>;
|
|
58
|
+
/**
|
|
59
|
+
* Translate native details, telling a malformed value apart from a wrong-type
|
|
60
|
+
* read (qfg-9xsl). sdk-node's `*Details` getters report both as
|
|
61
|
+
* `TYPE_MISMATCH`, but a wrong-type read fails its check against the config's
|
|
62
|
+
* declared `valueType` before evaluating. So a `TYPE_MISMATCH` on a config
|
|
63
|
+
* whose declared type this read accepts means the value itself did not parse
|
|
64
|
+
* (a stored int "twelve", an ENV_VAR int set to "twelve"): `PARSE_ERROR`.
|
|
65
|
+
*/
|
|
66
|
+
private resolve;
|
|
67
|
+
private declaredValueType;
|
|
58
68
|
/**
|
|
59
69
|
* Escape hatch: access the underlying @quonfig/node client for native-only features
|
|
60
70
|
* (shouldLog, keys, rawConfig, etc.)
|
package/dist/index.js
CHANGED
|
@@ -100,17 +100,17 @@ var QuonfigProvider = class {
|
|
|
100
100
|
async resolveBooleanEvaluation(flagKey, defaultValue, context, _logger) {
|
|
101
101
|
const mappedCtx = mapContext(context, this.targetingKeyMapping);
|
|
102
102
|
const details = this.client.getBoolDetails(flagKey, mappedCtx);
|
|
103
|
-
return
|
|
103
|
+
return this.resolve(flagKey, details, defaultValue, BOOL_TYPES);
|
|
104
104
|
}
|
|
105
105
|
async resolveStringEvaluation(flagKey, defaultValue, context, _logger) {
|
|
106
106
|
const mappedCtx = mapContext(context, this.targetingKeyMapping);
|
|
107
107
|
const details = this.client.getStringDetails(flagKey, mappedCtx);
|
|
108
|
-
return
|
|
108
|
+
return this.resolve(flagKey, details, defaultValue, STRING_TYPES);
|
|
109
109
|
}
|
|
110
110
|
async resolveNumberEvaluation(flagKey, defaultValue, context, _logger) {
|
|
111
111
|
const mappedCtx = mapContext(context, this.targetingKeyMapping);
|
|
112
112
|
const details = this.client.getNumberDetails(flagKey, mappedCtx);
|
|
113
|
-
return
|
|
113
|
+
return this.resolve(flagKey, details, defaultValue, NUMBER_TYPES);
|
|
114
114
|
}
|
|
115
115
|
async resolveObjectEvaluation(flagKey, defaultValue, context, _logger) {
|
|
116
116
|
const mappedCtx = mapContext(context, this.targetingKeyMapping);
|
|
@@ -122,7 +122,32 @@ var QuonfigProvider = class {
|
|
|
122
122
|
);
|
|
123
123
|
}
|
|
124
124
|
const jsonDetails = this.client.getJSONDetails(flagKey, mappedCtx);
|
|
125
|
-
return
|
|
125
|
+
return this.resolve(flagKey, jsonDetails, defaultValue, OBJECT_TYPES);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Translate native details, telling a malformed value apart from a wrong-type
|
|
129
|
+
* read (qfg-9xsl). sdk-node's `*Details` getters report both as
|
|
130
|
+
* `TYPE_MISMATCH`, but a wrong-type read fails its check against the config's
|
|
131
|
+
* declared `valueType` before evaluating. So a `TYPE_MISMATCH` on a config
|
|
132
|
+
* whose declared type this read accepts means the value itself did not parse
|
|
133
|
+
* (a stored int "twelve", an ENV_VAR int set to "twelve"): `PARSE_ERROR`.
|
|
134
|
+
*/
|
|
135
|
+
resolve(flagKey, details, defaultValue, readTypes) {
|
|
136
|
+
const result = toResolutionDetails(details, defaultValue);
|
|
137
|
+
if (result.errorCode === ErrorCode.TYPE_MISMATCH) {
|
|
138
|
+
const valueType = this.declaredValueType(flagKey);
|
|
139
|
+
if (valueType !== void 0 && readTypes.includes(valueType)) {
|
|
140
|
+
result.errorCode = ErrorCode.PARSE_ERROR;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
declaredValueType(flagKey) {
|
|
146
|
+
try {
|
|
147
|
+
return this.client.rawConfig(flagKey)?.valueType;
|
|
148
|
+
} catch {
|
|
149
|
+
return void 0;
|
|
150
|
+
}
|
|
126
151
|
}
|
|
127
152
|
/**
|
|
128
153
|
* Escape hatch: access the underlying @quonfig/node client for native-only features
|
|
@@ -132,6 +157,10 @@ var QuonfigProvider = class {
|
|
|
132
157
|
return this.client;
|
|
133
158
|
}
|
|
134
159
|
};
|
|
160
|
+
var BOOL_TYPES = ["bool"];
|
|
161
|
+
var STRING_TYPES = ["string", "log_level"];
|
|
162
|
+
var NUMBER_TYPES = ["int", "double"];
|
|
163
|
+
var OBJECT_TYPES = ["string_list", "json"];
|
|
135
164
|
function toResolutionDetails(details, defaultValue) {
|
|
136
165
|
const common = {
|
|
137
166
|
variant: details.variant,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.ts","../src/context.ts","../src/errors.ts"],"sourcesContent":["import {\n ErrorCode,\n EvaluationContext,\n JsonValue,\n Logger,\n OpenFeatureEventEmitter,\n Provider,\n ProviderEvents,\n ResolutionDetails,\n StandardResolutionReasons,\n} from \"@openfeature/server-sdk\";\nimport { EvaluationDetails, Quonfig, QuonfigOptions } from \"@quonfig/node\";\nimport { mapContext } from \"./context.js\";\n\nexport interface QuonfigProviderOptions extends Omit<QuonfigOptions, \"onConfigUpdate\"> {\n /**\n * Dot-notation path to map OpenFeature's `targetingKey` into Quonfig's nested context.\n * Defaults to \"user.id\".\n */\n targetingKeyMapping?: string;\n}\n\n/**\n * QuonfigProvider wraps the @quonfig/node native SDK and implements the\n * OpenFeature server-side Provider interface.\n *\n * Usage:\n * ```typescript\n * import { QuonfigProvider } from \"@quonfig/openfeature-node\";\n * import { OpenFeature } from \"@openfeature/server-sdk\";\n *\n * const provider = new QuonfigProvider({ sdkKey: \"qf_sk_...\" });\n * await OpenFeature.setProviderAndWait(provider);\n * const client = OpenFeature.getClient();\n * const enabled = await client.getBooleanValue(\"my-flag\", false);\n * ```\n */\nexport class QuonfigProvider implements Provider {\n readonly metadata = { name: \"quonfig\" } as const;\n readonly events = new OpenFeatureEventEmitter();\n readonly hooks = [];\n\n private readonly client: Quonfig;\n private readonly targetingKeyMapping: string;\n private closed = false;\n private initErrorTimer?: ReturnType<typeof setTimeout>;\n\n constructor(options: QuonfigProviderOptions) {\n this.targetingKeyMapping = options.targetingKeyMapping ?? \"user.id\";\n this.client = new Quonfig({\n ...options,\n onConfigUpdate: () => {\n this.events.emit(ProviderEvents.ConfigurationChanged, { flagsChanged: [] });\n },\n });\n }\n\n /**\n * Initialize the wrapped client. Status follows the client's `onInitFailure`\n * (design B.5), which you pass in the provider options with `initTimeout`:\n *\n * - `\"fail\"` (default): `init()` rejects, so this throws and the provider is\n * ERROR. The client is kept alive and retries in the background; on heal the\n * provider emits PROVIDER_READY.\n * - `\"wait\"`: blocks until the first config, so the provider is NOT_READY\n * throughout and READY when it resolves.\n * - `\"continue\"`: resolves on code defaults, then emits a non-fatal\n * PROVIDER_ERROR so the status is ERROR (evaluations still run and return\n * the caller default). On heal the provider emits PROVIDER_READY.\n */\n async initialize(_context?: EvaluationContext): Promise<void> {\n try {\n await this.client.init();\n } catch (err) {\n this.emitReadyOnHeal();\n throw err;\n }\n if (!this.client.isReady()) {\n // OpenFeature marks the provider READY once initialize() resolves, so\n // report the error after that has happened.\n this.initErrorTimer = setTimeout(() => {\n if (this.closed || this.client.isReady()) return;\n this.events.emit(ProviderEvents.Error, {\n message: \"[quonfig] no config loaded (onInitFailure=continue): serving code defaults\",\n errorCode: ErrorCode.PROVIDER_NOT_READY,\n });\n }, 0);\n this.emitReadyOnHeal();\n }\n }\n\n /** Emit PROVIDER_READY once the client's background retry loads config. */\n private emitReadyOnHeal(): void {\n this.client.waitForReady().then(\n () => {\n if (!this.closed) this.events.emit(ProviderEvents.Ready);\n },\n () => {} // closed before healing\n );\n }\n\n async shutdown(): Promise<void> {\n this.closed = true;\n clearTimeout(this.initErrorTimer);\n await this.client.close();\n }\n\n async resolveBooleanEvaluation(\n flagKey: string,\n defaultValue: boolean,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<boolean>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getBoolDetails(flagKey, mappedCtx);\n return toResolutionDetails(details, defaultValue);\n }\n\n async resolveStringEvaluation(\n flagKey: string,\n defaultValue: string,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<string>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getStringDetails(flagKey, mappedCtx);\n return toResolutionDetails(details, defaultValue);\n }\n\n async resolveNumberEvaluation(\n flagKey: string,\n defaultValue: number,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<number>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getNumberDetails(flagKey, mappedCtx);\n return toResolutionDetails(details, defaultValue);\n }\n\n async resolveObjectEvaluation<T extends JsonValue>(\n flagKey: string,\n defaultValue: T,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<T>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n // Try string_list first (returns string[]), fall back to JSON.\n const listDetails = this.client.getStringListDetails(flagKey, mappedCtx);\n if (\n listDetails.reason === \"STATIC\" ||\n listDetails.reason === \"TARGETING_MATCH\" ||\n listDetails.reason === \"SPLIT\"\n ) {\n return toResolutionDetails(\n listDetails as EvaluationDetails<unknown> as EvaluationDetails<T>,\n defaultValue\n );\n }\n const jsonDetails = this.client.getJSONDetails(flagKey, mappedCtx);\n return toResolutionDetails(jsonDetails as EvaluationDetails<T>, defaultValue);\n }\n\n /**\n * Escape hatch: access the underlying @quonfig/node client for native-only features\n * (shouldLog, keys, rawConfig, etc.)\n */\n getClient(): Quonfig {\n return this.client;\n }\n}\n\n/**\n * Translate a Quonfig EvaluationDetails<T> into an OpenFeature ResolutionDetails<T>.\n *\n * Reason mapping (from the brief):\n * STATIC → StandardResolutionReasons.STATIC\n * TARGETING_MATCH → StandardResolutionReasons.TARGETING_MATCH\n * SPLIT → StandardResolutionReasons.SPLIT\n * DEFAULT → defaultValue + StandardResolutionReasons.DEFAULT\n * ERROR → defaultValue + ERROR + errorCode (FLAG_NOT_FOUND / TYPE_MISMATCH /\n * PROVIDER_NOT_READY / GENERAL)\n */\nfunction toResolutionDetails<T>(\n details: EvaluationDetails<T>,\n defaultValue: T\n): ResolutionDetails<T> {\n const common = {\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n switch (details.reason) {\n case \"STATIC\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.STATIC,\n ...common,\n };\n case \"TARGETING_MATCH\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.TARGETING_MATCH,\n ...common,\n };\n case \"SPLIT\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.SPLIT,\n ...common,\n };\n case \"DEFAULT\":\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.DEFAULT,\n ...common,\n };\n case \"ERROR\":\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toOFErrorCode(details.errorCode),\n errorMessage: details.errorMessage,\n ...common,\n };\n }\n}\n\nfunction toOFErrorCode(code: EvaluationDetails<unknown>[\"errorCode\"]): ErrorCode {\n switch (code) {\n case \"FLAG_NOT_FOUND\":\n return ErrorCode.FLAG_NOT_FOUND;\n case \"TYPE_MISMATCH\":\n return ErrorCode.TYPE_MISMATCH;\n case \"PROVIDER_NOT_READY\":\n return ErrorCode.PROVIDER_NOT_READY;\n case \"GENERAL\":\n default:\n return ErrorCode.GENERAL;\n }\n}\n","import type { EvaluationContext } from \"@openfeature/server-sdk\";\nimport type { Contexts, ContextValue } from \"@quonfig/node\";\n\n/**\n * Maps an OpenFeature flat EvaluationContext to Quonfig's nested Contexts format.\n *\n * Rules:\n * - `targetingKey` maps to the namespace+property specified by `targetingKeyMapping` (default: \"user.id\")\n * - Keys with a dot are split on the first dot: \"user.email\" -> namespace \"user\", key \"email\"\n * - Keys without a dot go to the default (empty-string) namespace: \"country\" -> { \"\": { country: ... } }\n * - Multi-dot keys split on first dot only: \"user.ip.address\" -> { user: { \"ip.address\": ... } }\n */\nexport function mapContext(\n ofContext: EvaluationContext,\n targetingKeyMapping = \"user.id\"\n): Contexts {\n const result: Record<string, Record<string, ContextValue>> = {};\n\n for (const [key, value] of Object.entries(ofContext)) {\n if (value === undefined) continue;\n\n // Cast to ContextValue -- OpenFeature allows arbitrary nesting but Quonfig\n // contexts accept primitives and string arrays. Callers should pass only\n // primitive or string[] values for keys they want evaluated.\n const ctxValue = value as ContextValue;\n\n if (key === \"targetingKey\") {\n const dotIdx = targetingKeyMapping.indexOf(\".\");\n const ns = dotIdx === -1 ? \"\" : targetingKeyMapping.slice(0, dotIdx);\n const prop = dotIdx === -1 ? targetingKeyMapping : targetingKeyMapping.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = ctxValue;\n continue;\n }\n\n const dotIdx = key.indexOf(\".\");\n if (dotIdx === -1) {\n result[\"\"] ??= {};\n result[\"\"][key] = ctxValue;\n } else {\n const ns = key.slice(0, dotIdx);\n const prop = key.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = ctxValue;\n }\n }\n\n return result;\n}\n","import { ErrorCode } from \"@openfeature/server-sdk\";\n\n/**\n * Maps a native SDK error to an OpenFeature ErrorCode.\n *\n * The native SDK throws Error instances with message strings. We map by inspecting\n * the lowercased message.\n */\nexport function toErrorCode(err: unknown): ErrorCode {\n const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();\n\n if (\n msg.includes(\"not found\") ||\n msg.includes(\"flag not found\") ||\n msg.includes(\"no value found\") ||\n msg.includes(\"value found for key\")\n ) {\n return ErrorCode.FLAG_NOT_FOUND;\n }\n if (msg.includes(\"type mismatch\")) {\n return ErrorCode.TYPE_MISMATCH;\n }\n if (msg.includes(\"not initialized\") || msg.includes(\"provider not ready\")) {\n return ErrorCode.PROVIDER_NOT_READY;\n }\n return ErrorCode.GENERAL;\n}\n"],"mappings":"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","names":["dotIdx","ErrorCode"]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.ts","../src/context.ts","../src/errors.ts"],"sourcesContent":["import {\n ErrorCode,\n EvaluationContext,\n JsonValue,\n Logger,\n OpenFeatureEventEmitter,\n Provider,\n ProviderEvents,\n ResolutionDetails,\n StandardResolutionReasons,\n} from \"@openfeature/server-sdk\";\nimport { EvaluationDetails, Quonfig, QuonfigOptions, ValueType } from \"@quonfig/node\";\nimport { mapContext } from \"./context.js\";\n\nexport interface QuonfigProviderOptions extends Omit<QuonfigOptions, \"onConfigUpdate\"> {\n /**\n * Dot-notation path to map OpenFeature's `targetingKey` into Quonfig's nested context.\n * Defaults to \"user.id\".\n */\n targetingKeyMapping?: string;\n}\n\n/**\n * QuonfigProvider wraps the @quonfig/node native SDK and implements the\n * OpenFeature server-side Provider interface.\n *\n * Usage:\n * ```typescript\n * import { QuonfigProvider } from \"@quonfig/openfeature-node\";\n * import { OpenFeature } from \"@openfeature/server-sdk\";\n *\n * const provider = new QuonfigProvider({ sdkKey: \"qf_sk_...\" });\n * await OpenFeature.setProviderAndWait(provider);\n * const client = OpenFeature.getClient();\n * const enabled = await client.getBooleanValue(\"my-flag\", false);\n * ```\n */\nexport class QuonfigProvider implements Provider {\n readonly metadata = { name: \"quonfig\" } as const;\n readonly events = new OpenFeatureEventEmitter();\n readonly hooks = [];\n\n private readonly client: Quonfig;\n private readonly targetingKeyMapping: string;\n private closed = false;\n private initErrorTimer?: ReturnType<typeof setTimeout>;\n\n constructor(options: QuonfigProviderOptions) {\n this.targetingKeyMapping = options.targetingKeyMapping ?? \"user.id\";\n this.client = new Quonfig({\n ...options,\n onConfigUpdate: () => {\n this.events.emit(ProviderEvents.ConfigurationChanged, { flagsChanged: [] });\n },\n });\n }\n\n /**\n * Initialize the wrapped client. Status follows the client's `onInitFailure`\n * (design B.5), which you pass in the provider options with `initTimeout`:\n *\n * - `\"fail\"` (default): `init()` rejects, so this throws and the provider is\n * ERROR. The client is kept alive and retries in the background; on heal the\n * provider emits PROVIDER_READY.\n * - `\"wait\"`: blocks until the first config, so the provider is NOT_READY\n * throughout and READY when it resolves.\n * - `\"continue\"`: resolves on code defaults, then emits a non-fatal\n * PROVIDER_ERROR so the status is ERROR (evaluations still run and return\n * the caller default). On heal the provider emits PROVIDER_READY.\n */\n async initialize(_context?: EvaluationContext): Promise<void> {\n try {\n await this.client.init();\n } catch (err) {\n this.emitReadyOnHeal();\n throw err;\n }\n if (!this.client.isReady()) {\n // OpenFeature marks the provider READY once initialize() resolves, so\n // report the error after that has happened.\n this.initErrorTimer = setTimeout(() => {\n if (this.closed || this.client.isReady()) return;\n this.events.emit(ProviderEvents.Error, {\n message: \"[quonfig] no config loaded (onInitFailure=continue): serving code defaults\",\n errorCode: ErrorCode.PROVIDER_NOT_READY,\n });\n }, 0);\n this.emitReadyOnHeal();\n }\n }\n\n /** Emit PROVIDER_READY once the client's background retry loads config. */\n private emitReadyOnHeal(): void {\n this.client.waitForReady().then(\n () => {\n if (!this.closed) this.events.emit(ProviderEvents.Ready);\n },\n () => {} // closed before healing\n );\n }\n\n async shutdown(): Promise<void> {\n this.closed = true;\n clearTimeout(this.initErrorTimer);\n await this.client.close();\n }\n\n async resolveBooleanEvaluation(\n flagKey: string,\n defaultValue: boolean,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<boolean>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getBoolDetails(flagKey, mappedCtx);\n return this.resolve(flagKey, details, defaultValue, BOOL_TYPES);\n }\n\n async resolveStringEvaluation(\n flagKey: string,\n defaultValue: string,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<string>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getStringDetails(flagKey, mappedCtx);\n return this.resolve(flagKey, details, defaultValue, STRING_TYPES);\n }\n\n async resolveNumberEvaluation(\n flagKey: string,\n defaultValue: number,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<number>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n const details = this.client.getNumberDetails(flagKey, mappedCtx);\n return this.resolve(flagKey, details, defaultValue, NUMBER_TYPES);\n }\n\n async resolveObjectEvaluation<T extends JsonValue>(\n flagKey: string,\n defaultValue: T,\n context: EvaluationContext,\n _logger: Logger\n ): Promise<ResolutionDetails<T>> {\n const mappedCtx = mapContext(context, this.targetingKeyMapping);\n // Try string_list first (returns string[]), fall back to JSON.\n const listDetails = this.client.getStringListDetails(flagKey, mappedCtx);\n if (\n listDetails.reason === \"STATIC\" ||\n listDetails.reason === \"TARGETING_MATCH\" ||\n listDetails.reason === \"SPLIT\"\n ) {\n return toResolutionDetails(\n listDetails as EvaluationDetails<unknown> as EvaluationDetails<T>,\n defaultValue\n );\n }\n const jsonDetails = this.client.getJSONDetails(flagKey, mappedCtx);\n return this.resolve(flagKey, jsonDetails as EvaluationDetails<T>, defaultValue, OBJECT_TYPES);\n }\n\n /**\n * Translate native details, telling a malformed value apart from a wrong-type\n * read (qfg-9xsl). sdk-node's `*Details` getters report both as\n * `TYPE_MISMATCH`, but a wrong-type read fails its check against the config's\n * declared `valueType` before evaluating. So a `TYPE_MISMATCH` on a config\n * whose declared type this read accepts means the value itself did not parse\n * (a stored int \"twelve\", an ENV_VAR int set to \"twelve\"): `PARSE_ERROR`.\n */\n private resolve<T>(\n flagKey: string,\n details: EvaluationDetails<T>,\n defaultValue: T,\n readTypes: readonly ValueType[]\n ): ResolutionDetails<T> {\n const result = toResolutionDetails(details, defaultValue);\n if (result.errorCode === ErrorCode.TYPE_MISMATCH) {\n const valueType = this.declaredValueType(flagKey);\n if (valueType !== undefined && readTypes.includes(valueType)) {\n result.errorCode = ErrorCode.PARSE_ERROR;\n }\n }\n return result;\n }\n\n private declaredValueType(flagKey: string): ValueType | undefined {\n try {\n return this.client.rawConfig(flagKey)?.valueType;\n } catch {\n return undefined; // not initialized: keep the native error code\n }\n }\n\n /**\n * Escape hatch: access the underlying @quonfig/node client for native-only features\n * (shouldLog, keys, rawConfig, etc.)\n */\n getClient(): Quonfig {\n return this.client;\n }\n}\n\n// The declared valueTypes each OpenFeature read accepts; these mirror sdk-node's\n// own check of the requested type against a config's valueType.\nconst BOOL_TYPES: readonly ValueType[] = [\"bool\"];\nconst STRING_TYPES: readonly ValueType[] = [\"string\", \"log_level\"];\nconst NUMBER_TYPES: readonly ValueType[] = [\"int\", \"double\"];\nconst OBJECT_TYPES: readonly ValueType[] = [\"string_list\", \"json\"];\n\n/**\n * Translate a Quonfig EvaluationDetails<T> into an OpenFeature ResolutionDetails<T>.\n *\n * Reason mapping (from the brief):\n * STATIC → StandardResolutionReasons.STATIC\n * TARGETING_MATCH → StandardResolutionReasons.TARGETING_MATCH\n * SPLIT → StandardResolutionReasons.SPLIT\n * DEFAULT → defaultValue + StandardResolutionReasons.DEFAULT\n * ERROR → defaultValue + ERROR + errorCode (FLAG_NOT_FOUND / TYPE_MISMATCH /\n * PROVIDER_NOT_READY / GENERAL; QuonfigProvider.resolve refines a\n * malformed value's TYPE_MISMATCH to PARSE_ERROR)\n */\nfunction toResolutionDetails<T>(\n details: EvaluationDetails<T>,\n defaultValue: T\n): ResolutionDetails<T> {\n const common = {\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n switch (details.reason) {\n case \"STATIC\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.STATIC,\n ...common,\n };\n case \"TARGETING_MATCH\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.TARGETING_MATCH,\n ...common,\n };\n case \"SPLIT\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.SPLIT,\n ...common,\n };\n case \"DEFAULT\":\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.DEFAULT,\n ...common,\n };\n case \"ERROR\":\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toOFErrorCode(details.errorCode),\n errorMessage: details.errorMessage,\n ...common,\n };\n }\n}\n\nfunction toOFErrorCode(code: EvaluationDetails<unknown>[\"errorCode\"]): ErrorCode {\n switch (code) {\n case \"FLAG_NOT_FOUND\":\n return ErrorCode.FLAG_NOT_FOUND;\n case \"TYPE_MISMATCH\":\n return ErrorCode.TYPE_MISMATCH;\n case \"PROVIDER_NOT_READY\":\n return ErrorCode.PROVIDER_NOT_READY;\n case \"GENERAL\":\n default:\n return ErrorCode.GENERAL;\n }\n}\n","import type { EvaluationContext } from \"@openfeature/server-sdk\";\nimport type { Contexts, ContextValue } from \"@quonfig/node\";\n\n/**\n * Maps an OpenFeature flat EvaluationContext to Quonfig's nested Contexts format.\n *\n * Rules:\n * - `targetingKey` maps to the namespace+property specified by `targetingKeyMapping` (default: \"user.id\")\n * - Keys with a dot are split on the first dot: \"user.email\" -> namespace \"user\", key \"email\"\n * - Keys without a dot go to the default (empty-string) namespace: \"country\" -> { \"\": { country: ... } }\n * - Multi-dot keys split on first dot only: \"user.ip.address\" -> { user: { \"ip.address\": ... } }\n */\nexport function mapContext(\n ofContext: EvaluationContext,\n targetingKeyMapping = \"user.id\"\n): Contexts {\n const result: Record<string, Record<string, ContextValue>> = {};\n\n for (const [key, value] of Object.entries(ofContext)) {\n if (value === undefined) continue;\n\n // Cast to ContextValue -- OpenFeature allows arbitrary nesting but Quonfig\n // contexts accept primitives and string arrays. Callers should pass only\n // primitive or string[] values for keys they want evaluated.\n const ctxValue = value as ContextValue;\n\n if (key === \"targetingKey\") {\n const dotIdx = targetingKeyMapping.indexOf(\".\");\n const ns = dotIdx === -1 ? \"\" : targetingKeyMapping.slice(0, dotIdx);\n const prop = dotIdx === -1 ? targetingKeyMapping : targetingKeyMapping.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = ctxValue;\n continue;\n }\n\n const dotIdx = key.indexOf(\".\");\n if (dotIdx === -1) {\n result[\"\"] ??= {};\n result[\"\"][key] = ctxValue;\n } else {\n const ns = key.slice(0, dotIdx);\n const prop = key.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = ctxValue;\n }\n }\n\n return result;\n}\n","import { ErrorCode } from \"@openfeature/server-sdk\";\n\n/**\n * Maps a native SDK error to an OpenFeature ErrorCode.\n *\n * The native SDK throws Error instances with message strings. We map by inspecting\n * the lowercased message.\n */\nexport function toErrorCode(err: unknown): ErrorCode {\n const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();\n\n if (\n msg.includes(\"not found\") ||\n msg.includes(\"flag not found\") ||\n msg.includes(\"no value found\") ||\n msg.includes(\"value found for key\")\n ) {\n return ErrorCode.FLAG_NOT_FOUND;\n }\n if (msg.includes(\"type mismatch\")) {\n return ErrorCode.TYPE_MISMATCH;\n }\n if (msg.includes(\"not initialized\") || msg.includes(\"provider not ready\")) {\n return ErrorCode.PROVIDER_NOT_READY;\n }\n return ErrorCode.GENERAL;\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EAIA;AAAA,EAEA;AAAA,EAEA;AAAA,OACK;AACP,SAA4B,eAA0C;;;ACC/D,SAAS,WACd,WACA,sBAAsB,WACZ;AACV,QAAM,SAAuD,CAAC;AAE9D,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,QAAI,UAAU,OAAW;AAKzB,UAAM,WAAW;AAEjB,QAAI,QAAQ,gBAAgB;AAC1B,YAAMA,UAAS,oBAAoB,QAAQ,GAAG;AAC9C,YAAM,KAAKA,YAAW,KAAK,KAAK,oBAAoB,MAAM,GAAGA,OAAM;AACnE,YAAM,OAAOA,YAAW,KAAK,sBAAsB,oBAAoB,MAAMA,UAAS,CAAC;AACvF,aAAO,EAAE,MAAM,CAAC;AAChB,aAAO,EAAE,EAAE,IAAI,IAAI;AACnB;AAAA,IACF;AAEA,UAAM,SAAS,IAAI,QAAQ,GAAG;AAC9B,QAAI,WAAW,IAAI;AACjB,aAAO,EAAE,MAAM,CAAC;AAChB,aAAO,EAAE,EAAE,GAAG,IAAI;AAAA,IACpB,OAAO;AACL,YAAM,KAAK,IAAI,MAAM,GAAG,MAAM;AAC9B,YAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACjC,aAAO,EAAE,MAAM,CAAC;AAChB,aAAO,EAAE,EAAE,IAAI,IAAI;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;;;ADXO,IAAM,kBAAN,MAA0C;AAAA,EAU/C,YAAY,SAAiC;AAT7C,SAAS,WAAW,EAAE,MAAM,UAAU;AACtC,SAAS,SAAS,IAAI,wBAAwB;AAC9C,SAAS,QAAQ,CAAC;AAIlB,SAAQ,SAAS;AAIf,SAAK,sBAAsB,QAAQ,uBAAuB;AAC1D,SAAK,SAAS,IAAI,QAAQ;AAAA,MACxB,GAAG;AAAA,MACH,gBAAgB,MAAM;AACpB,aAAK,OAAO,KAAK,eAAe,sBAAsB,EAAE,cAAc,CAAC,EAAE,CAAC;AAAA,MAC5E;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,WAAW,UAA6C;AAC5D,QAAI;AACF,YAAM,KAAK,OAAO,KAAK;AAAA,IACzB,SAAS,KAAK;AACZ,WAAK,gBAAgB;AACrB,YAAM;AAAA,IACR;AACA,QAAI,CAAC,KAAK,OAAO,QAAQ,GAAG;AAG1B,WAAK,iBAAiB,WAAW,MAAM;AACrC,YAAI,KAAK,UAAU,KAAK,OAAO,QAAQ,EAAG;AAC1C,aAAK,OAAO,KAAK,eAAe,OAAO;AAAA,UACrC,SAAS;AAAA,UACT,WAAW,UAAU;AAAA,QACvB,CAAC;AAAA,MACH,GAAG,CAAC;AACJ,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AAAA;AAAA,EAGQ,kBAAwB;AAC9B,SAAK,OAAO,aAAa,EAAE;AAAA,MACzB,MAAM;AACJ,YAAI,CAAC,KAAK,OAAQ,MAAK,OAAO,KAAK,eAAe,KAAK;AAAA,MACzD;AAAA,MACA,MAAM;AAAA,MAAC;AAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,WAA0B;AAC9B,SAAK,SAAS;AACd,iBAAa,KAAK,cAAc;AAChC,UAAM,KAAK,OAAO,MAAM;AAAA,EAC1B;AAAA,EAEA,MAAM,yBACJ,SACA,cACA,SACA,SACqC;AACrC,UAAM,YAAY,WAAW,SAAS,KAAK,mBAAmB;AAC9D,UAAM,UAAU,KAAK,OAAO,eAAe,SAAS,SAAS;AAC7D,WAAO,KAAK,QAAQ,SAAS,SAAS,cAAc,UAAU;AAAA,EAChE;AAAA,EAEA,MAAM,wBACJ,SACA,cACA,SACA,SACoC;AACpC,UAAM,YAAY,WAAW,SAAS,KAAK,mBAAmB;AAC9D,UAAM,UAAU,KAAK,OAAO,iBAAiB,SAAS,SAAS;AAC/D,WAAO,KAAK,QAAQ,SAAS,SAAS,cAAc,YAAY;AAAA,EAClE;AAAA,EAEA,MAAM,wBACJ,SACA,cACA,SACA,SACoC;AACpC,UAAM,YAAY,WAAW,SAAS,KAAK,mBAAmB;AAC9D,UAAM,UAAU,KAAK,OAAO,iBAAiB,SAAS,SAAS;AAC/D,WAAO,KAAK,QAAQ,SAAS,SAAS,cAAc,YAAY;AAAA,EAClE;AAAA,EAEA,MAAM,wBACJ,SACA,cACA,SACA,SAC+B;AAC/B,UAAM,YAAY,WAAW,SAAS,KAAK,mBAAmB;AAE9D,UAAM,cAAc,KAAK,OAAO,qBAAqB,SAAS,SAAS;AACvE,QACE,YAAY,WAAW,YACvB,YAAY,WAAW,qBACvB,YAAY,WAAW,SACvB;AACA,aAAO;AAAA,QACL;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,cAAc,KAAK,OAAO,eAAe,SAAS,SAAS;AACjE,WAAO,KAAK,QAAQ,SAAS,aAAqC,cAAc,YAAY;AAAA,EAC9F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,QACN,SACA,SACA,cACA,WACsB;AACtB,UAAM,SAAS,oBAAoB,SAAS,YAAY;AACxD,QAAI,OAAO,cAAc,UAAU,eAAe;AAChD,YAAM,YAAY,KAAK,kBAAkB,OAAO;AAChD,UAAI,cAAc,UAAa,UAAU,SAAS,SAAS,GAAG;AAC5D,eAAO,YAAY,UAAU;AAAA,MAC/B;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,kBAAkB,SAAwC;AAChE,QAAI;AACF,aAAO,KAAK,OAAO,UAAU,OAAO,GAAG;AAAA,IACzC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AACF;AAIA,IAAM,aAAmC,CAAC,MAAM;AAChD,IAAM,eAAqC,CAAC,UAAU,WAAW;AACjE,IAAM,eAAqC,CAAC,OAAO,QAAQ;AAC3D,IAAM,eAAqC,CAAC,eAAe,MAAM;AAcjE,SAAS,oBACP,SACA,cACsB;AACtB,QAAM,SAAS;AAAA,IACb,SAAS,QAAQ;AAAA,IACjB,cAAc,QAAQ;AAAA,EACxB;AACA,UAAQ,QAAQ,QAAQ;AAAA,IACtB,KAAK;AACH,aAAO;AAAA,QACL,OAAO,QAAQ;AAAA,QACf,QAAQ,0BAA0B;AAAA,QAClC,GAAG;AAAA,MACL;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,OAAO,QAAQ;AAAA,QACf,QAAQ,0BAA0B;AAAA,QAClC,GAAG;AAAA,MACL;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,OAAO,QAAQ;AAAA,QACf,QAAQ,0BAA0B;AAAA,QAClC,GAAG;AAAA,MACL;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ,0BAA0B;AAAA,QAClC,GAAG;AAAA,MACL;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ,0BAA0B;AAAA,QAClC,WAAW,cAAc,QAAQ,SAAS;AAAA,QAC1C,cAAc,QAAQ;AAAA,QACtB,GAAG;AAAA,MACL;AAAA,EACJ;AACF;AAEA,SAAS,cAAc,MAA0D;AAC/E,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,UAAU;AAAA,IACnB,KAAK;AACH,aAAO,UAAU;AAAA,IACnB,KAAK;AACH,aAAO,UAAU;AAAA,IACnB,KAAK;AAAA,IACL;AACE,aAAO,UAAU;AAAA,EACrB;AACF;;;AEvRA,SAAS,aAAAC,kBAAiB;AAQnB,SAAS,YAAY,KAAyB;AACnD,QAAM,MAAM,eAAe,QAAQ,IAAI,QAAQ,YAAY,IAAI,OAAO,GAAG,EAAE,YAAY;AAEvF,MACE,IAAI,SAAS,WAAW,KACxB,IAAI,SAAS,gBAAgB,KAC7B,IAAI,SAAS,gBAAgB,KAC7B,IAAI,SAAS,qBAAqB,GAClC;AACA,WAAOA,WAAU;AAAA,EACnB;AACA,MAAI,IAAI,SAAS,eAAe,GAAG;AACjC,WAAOA,WAAU;AAAA,EACnB;AACA,MAAI,IAAI,SAAS,iBAAiB,KAAK,IAAI,SAAS,oBAAoB,GAAG;AACzE,WAAOA,WAAU;AAAA,EACnB;AACA,SAAOA,WAAU;AACnB;","names":["dotIdx","ErrorCode"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quonfig/openfeature-node",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "OpenFeature provider for Quonfig -- Node.js",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"version": "prettier --write CHANGELOG.md README.md && git add CHANGELOG.md README.md"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@quonfig/node": ">=
|
|
28
|
+
"@quonfig/node": ">=2.0.0",
|
|
29
29
|
"@openfeature/server-sdk": ">=1.0.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@quonfig/node": "^
|
|
32
|
+
"@quonfig/node": "^2.0.0",
|
|
33
33
|
"@openfeature/server-sdk": "^1.7.0",
|
|
34
34
|
"@types/node": "^26.6.5",
|
|
35
35
|
"prettier": "^3.0.0",
|