@quonfig/openfeature-node 0.0.1 → 0.0.3

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 CHANGED
@@ -27,7 +27,7 @@ __export(index_exports, {
27
27
  module.exports = __toCommonJS(index_exports);
28
28
 
29
29
  // src/provider.ts
30
- var import_server_sdk2 = require("@openfeature/server-sdk");
30
+ var import_server_sdk = require("@openfeature/server-sdk");
31
31
  var import_node = require("@quonfig/node");
32
32
 
33
33
  // src/context.ts
@@ -58,33 +58,17 @@ function mapContext(ofContext, targetingKeyMapping = "user.id") {
58
58
  return result;
59
59
  }
60
60
 
61
- // src/errors.ts
62
- var import_server_sdk = require("@openfeature/server-sdk");
63
- function toErrorCode(err) {
64
- const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
65
- if (msg.includes("not found") || msg.includes("flag not found") || msg.includes("no value found") || msg.includes("value found for key")) {
66
- return import_server_sdk.ErrorCode.FLAG_NOT_FOUND;
67
- }
68
- if (msg.includes("type mismatch")) {
69
- return import_server_sdk.ErrorCode.TYPE_MISMATCH;
70
- }
71
- if (msg.includes("not initialized") || msg.includes("provider not ready")) {
72
- return import_server_sdk.ErrorCode.PROVIDER_NOT_READY;
73
- }
74
- return import_server_sdk.ErrorCode.GENERAL;
75
- }
76
-
77
61
  // src/provider.ts
78
62
  var QuonfigProvider = class {
79
63
  constructor(options) {
80
64
  this.metadata = { name: "quonfig" };
81
- this.events = new import_server_sdk2.OpenFeatureEventEmitter();
65
+ this.events = new import_server_sdk.OpenFeatureEventEmitter();
82
66
  this.hooks = [];
83
67
  this.targetingKeyMapping = options.targetingKeyMapping ?? "user.id";
84
68
  this.client = new import_node.Quonfig({
85
69
  ...options,
86
70
  onConfigUpdate: () => {
87
- this.events.emit(import_server_sdk2.ProviderEvents.ConfigurationChanged, { flagsChanged: [] });
71
+ this.events.emit(import_server_sdk.ProviderEvents.ConfigurationChanged, { flagsChanged: [] });
88
72
  }
89
73
  });
90
74
  }
@@ -96,78 +80,30 @@ var QuonfigProvider = class {
96
80
  }
97
81
  async resolveBooleanEvaluation(flagKey, defaultValue, context, _logger) {
98
82
  const mappedCtx = mapContext(context, this.targetingKeyMapping);
99
- try {
100
- const value = this.client.getBool(flagKey, mappedCtx);
101
- if (value === void 0) {
102
- return { value: defaultValue, reason: import_server_sdk2.StandardResolutionReasons.DEFAULT };
103
- }
104
- return { value, reason: import_server_sdk2.StandardResolutionReasons.TARGETING_MATCH };
105
- } catch (err) {
106
- return {
107
- value: defaultValue,
108
- reason: import_server_sdk2.StandardResolutionReasons.ERROR,
109
- errorCode: toErrorCode(err),
110
- errorMessage: err instanceof Error ? err.message : String(err)
111
- };
112
- }
83
+ const details = this.client.getBoolDetails(flagKey, mappedCtx);
84
+ return toResolutionDetails(details, defaultValue);
113
85
  }
114
86
  async resolveStringEvaluation(flagKey, defaultValue, context, _logger) {
115
87
  const mappedCtx = mapContext(context, this.targetingKeyMapping);
116
- try {
117
- const value = this.client.getString(flagKey, mappedCtx);
118
- if (value === void 0) {
119
- return { value: defaultValue, reason: import_server_sdk2.StandardResolutionReasons.DEFAULT };
120
- }
121
- return { value, reason: import_server_sdk2.StandardResolutionReasons.TARGETING_MATCH };
122
- } catch (err) {
123
- return {
124
- value: defaultValue,
125
- reason: import_server_sdk2.StandardResolutionReasons.ERROR,
126
- errorCode: toErrorCode(err),
127
- errorMessage: err instanceof Error ? err.message : String(err)
128
- };
129
- }
88
+ const details = this.client.getStringDetails(flagKey, mappedCtx);
89
+ return toResolutionDetails(details, defaultValue);
130
90
  }
131
91
  async resolveNumberEvaluation(flagKey, defaultValue, context, _logger) {
132
92
  const mappedCtx = mapContext(context, this.targetingKeyMapping);
133
- try {
134
- const value = this.client.getNumber(flagKey, mappedCtx);
135
- if (value === void 0) {
136
- return { value: defaultValue, reason: import_server_sdk2.StandardResolutionReasons.DEFAULT };
137
- }
138
- return { value, reason: import_server_sdk2.StandardResolutionReasons.TARGETING_MATCH };
139
- } catch (err) {
140
- return {
141
- value: defaultValue,
142
- reason: import_server_sdk2.StandardResolutionReasons.ERROR,
143
- errorCode: toErrorCode(err),
144
- errorMessage: err instanceof Error ? err.message : String(err)
145
- };
146
- }
93
+ const details = this.client.getNumberDetails(flagKey, mappedCtx);
94
+ return toResolutionDetails(details, defaultValue);
147
95
  }
148
96
  async resolveObjectEvaluation(flagKey, defaultValue, context, _logger) {
149
97
  const mappedCtx = mapContext(context, this.targetingKeyMapping);
150
- try {
151
- const listVal = this.client.getStringList(flagKey, mappedCtx);
152
- if (listVal !== void 0) {
153
- return {
154
- value: listVal,
155
- reason: import_server_sdk2.StandardResolutionReasons.TARGETING_MATCH
156
- };
157
- }
158
- const jsonVal = this.client.getJSON(flagKey, mappedCtx);
159
- if (jsonVal !== void 0) {
160
- return { value: jsonVal, reason: import_server_sdk2.StandardResolutionReasons.TARGETING_MATCH };
161
- }
162
- return { value: defaultValue, reason: import_server_sdk2.StandardResolutionReasons.DEFAULT };
163
- } catch (err) {
164
- return {
165
- value: defaultValue,
166
- reason: import_server_sdk2.StandardResolutionReasons.ERROR,
167
- errorCode: toErrorCode(err),
168
- errorMessage: err instanceof Error ? err.message : String(err)
169
- };
98
+ const listDetails = this.client.getStringListDetails(flagKey, mappedCtx);
99
+ if (listDetails.reason === "STATIC" || listDetails.reason === "TARGETING_MATCH" || listDetails.reason === "SPLIT") {
100
+ return toResolutionDetails(
101
+ listDetails,
102
+ defaultValue
103
+ );
170
104
  }
105
+ const jsonDetails = this.client.getJSONDetails(flagKey, mappedCtx);
106
+ return toResolutionDetails(jsonDetails, defaultValue);
171
107
  }
172
108
  /**
173
109
  * Escape hatch: access the underlying @quonfig/node client for native-only features
@@ -177,6 +113,54 @@ var QuonfigProvider = class {
177
113
  return this.client;
178
114
  }
179
115
  };
116
+ function toResolutionDetails(details, defaultValue) {
117
+ switch (details.reason) {
118
+ case "STATIC":
119
+ return { value: details.value, reason: import_server_sdk.StandardResolutionReasons.STATIC };
120
+ case "TARGETING_MATCH":
121
+ return {
122
+ value: details.value,
123
+ reason: import_server_sdk.StandardResolutionReasons.TARGETING_MATCH
124
+ };
125
+ case "SPLIT":
126
+ return { value: details.value, reason: import_server_sdk.StandardResolutionReasons.SPLIT };
127
+ case "DEFAULT":
128
+ return { value: defaultValue, reason: import_server_sdk.StandardResolutionReasons.DEFAULT };
129
+ case "ERROR":
130
+ return {
131
+ value: defaultValue,
132
+ reason: import_server_sdk.StandardResolutionReasons.ERROR,
133
+ errorCode: toOFErrorCode(details.errorCode)
134
+ };
135
+ }
136
+ }
137
+ function toOFErrorCode(code) {
138
+ switch (code) {
139
+ case "FLAG_NOT_FOUND":
140
+ return import_server_sdk.ErrorCode.FLAG_NOT_FOUND;
141
+ case "TYPE_MISMATCH":
142
+ return import_server_sdk.ErrorCode.TYPE_MISMATCH;
143
+ case "GENERAL":
144
+ default:
145
+ return import_server_sdk.ErrorCode.GENERAL;
146
+ }
147
+ }
148
+
149
+ // src/errors.ts
150
+ var import_server_sdk2 = require("@openfeature/server-sdk");
151
+ function toErrorCode(err) {
152
+ const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
153
+ if (msg.includes("not found") || msg.includes("flag not found") || msg.includes("no value found") || msg.includes("value found for key")) {
154
+ return import_server_sdk2.ErrorCode.FLAG_NOT_FOUND;
155
+ }
156
+ if (msg.includes("type mismatch")) {
157
+ return import_server_sdk2.ErrorCode.TYPE_MISMATCH;
158
+ }
159
+ if (msg.includes("not initialized") || msg.includes("provider not ready")) {
160
+ return import_server_sdk2.ErrorCode.PROVIDER_NOT_READY;
161
+ }
162
+ return import_server_sdk2.ErrorCode.GENERAL;
163
+ }
180
164
  // Annotate the CommonJS export names for ESM import in node:
181
165
  0 && (module.exports = {
182
166
  QuonfigProvider,
@@ -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 EvaluationContext,\n JsonValue,\n Logger,\n OpenFeatureEventEmitter,\n Provider,\n ProviderEvents,\n ResolutionDetails,\n StandardResolutionReasons,\n} from \"@openfeature/server-sdk\";\nimport { Quonfig, QuonfigOptions } from \"@quonfig/node\";\nimport { mapContext } from \"./context.js\";\nimport { toErrorCode } from \"./errors.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\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 async initialize(_context?: EvaluationContext): Promise<void> {\n await this.client.init();\n }\n\n async shutdown(): Promise<void> {\n 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 try {\n const value = this.client.getBool(flagKey, mappedCtx);\n if (value === undefined) {\n return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };\n }\n return { value, reason: StandardResolutionReasons.TARGETING_MATCH };\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n errorMessage: err instanceof Error ? err.message : String(err),\n };\n }\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 try {\n const value = this.client.getString(flagKey, mappedCtx);\n if (value === undefined) {\n return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };\n }\n return { value, reason: StandardResolutionReasons.TARGETING_MATCH };\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n errorMessage: err instanceof Error ? err.message : String(err),\n };\n }\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 try {\n const value = this.client.getNumber(flagKey, mappedCtx);\n if (value === undefined) {\n return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };\n }\n return { value, reason: StandardResolutionReasons.TARGETING_MATCH };\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n errorMessage: err instanceof Error ? err.message : String(err),\n };\n }\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 {\n // Try string_list first (returns string[])\n const listVal = this.client.getStringList(flagKey, mappedCtx);\n if (listVal !== undefined) {\n return {\n value: listVal as unknown as T,\n reason: StandardResolutionReasons.TARGETING_MATCH,\n };\n }\n // Fall back to JSON\n const jsonVal = this.client.getJSON(flagKey, mappedCtx);\n if (jsonVal !== undefined) {\n return { value: jsonVal as T, reason: StandardResolutionReasons.TARGETING_MATCH };\n }\n return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n errorMessage: err instanceof Error ? err.message : String(err),\n };\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","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 =\n 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":["import_server_sdk","dotIdx"]}
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\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 async initialize(_context?: EvaluationContext): Promise<void> {\n await this.client.init();\n }\n\n async shutdown(): Promise<void> {\n 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 / GENERAL)\n */\nfunction toResolutionDetails<T>(\n details: EvaluationDetails<T>,\n defaultValue: T,\n): ResolutionDetails<T> {\n switch (details.reason) {\n case \"STATIC\":\n return { value: details.value as T, reason: StandardResolutionReasons.STATIC };\n case \"TARGETING_MATCH\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.TARGETING_MATCH,\n };\n case \"SPLIT\":\n return { value: details.value as T, reason: StandardResolutionReasons.SPLIT };\n case \"DEFAULT\":\n return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };\n case \"ERROR\":\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toOFErrorCode(details.errorCode),\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 \"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 =\n 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.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/provider.ts
2
2
  import {
3
+ ErrorCode,
3
4
  OpenFeatureEventEmitter,
4
5
  ProviderEvents,
5
6
  StandardResolutionReasons
@@ -34,22 +35,6 @@ function mapContext(ofContext, targetingKeyMapping = "user.id") {
34
35
  return result;
35
36
  }
36
37
 
37
- // src/errors.ts
38
- import { ErrorCode } from "@openfeature/server-sdk";
39
- function toErrorCode(err) {
40
- const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
41
- if (msg.includes("not found") || msg.includes("flag not found") || msg.includes("no value found") || msg.includes("value found for key")) {
42
- return ErrorCode.FLAG_NOT_FOUND;
43
- }
44
- if (msg.includes("type mismatch")) {
45
- return ErrorCode.TYPE_MISMATCH;
46
- }
47
- if (msg.includes("not initialized") || msg.includes("provider not ready")) {
48
- return ErrorCode.PROVIDER_NOT_READY;
49
- }
50
- return ErrorCode.GENERAL;
51
- }
52
-
53
38
  // src/provider.ts
54
39
  var QuonfigProvider = class {
55
40
  constructor(options) {
@@ -72,78 +57,30 @@ var QuonfigProvider = class {
72
57
  }
73
58
  async resolveBooleanEvaluation(flagKey, defaultValue, context, _logger) {
74
59
  const mappedCtx = mapContext(context, this.targetingKeyMapping);
75
- try {
76
- const value = this.client.getBool(flagKey, mappedCtx);
77
- if (value === void 0) {
78
- return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };
79
- }
80
- return { value, reason: StandardResolutionReasons.TARGETING_MATCH };
81
- } catch (err) {
82
- return {
83
- value: defaultValue,
84
- reason: StandardResolutionReasons.ERROR,
85
- errorCode: toErrorCode(err),
86
- errorMessage: err instanceof Error ? err.message : String(err)
87
- };
88
- }
60
+ const details = this.client.getBoolDetails(flagKey, mappedCtx);
61
+ return toResolutionDetails(details, defaultValue);
89
62
  }
90
63
  async resolveStringEvaluation(flagKey, defaultValue, context, _logger) {
91
64
  const mappedCtx = mapContext(context, this.targetingKeyMapping);
92
- try {
93
- const value = this.client.getString(flagKey, mappedCtx);
94
- if (value === void 0) {
95
- return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };
96
- }
97
- return { value, reason: StandardResolutionReasons.TARGETING_MATCH };
98
- } catch (err) {
99
- return {
100
- value: defaultValue,
101
- reason: StandardResolutionReasons.ERROR,
102
- errorCode: toErrorCode(err),
103
- errorMessage: err instanceof Error ? err.message : String(err)
104
- };
105
- }
65
+ const details = this.client.getStringDetails(flagKey, mappedCtx);
66
+ return toResolutionDetails(details, defaultValue);
106
67
  }
107
68
  async resolveNumberEvaluation(flagKey, defaultValue, context, _logger) {
108
69
  const mappedCtx = mapContext(context, this.targetingKeyMapping);
109
- try {
110
- const value = this.client.getNumber(flagKey, mappedCtx);
111
- if (value === void 0) {
112
- return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };
113
- }
114
- return { value, reason: StandardResolutionReasons.TARGETING_MATCH };
115
- } catch (err) {
116
- return {
117
- value: defaultValue,
118
- reason: StandardResolutionReasons.ERROR,
119
- errorCode: toErrorCode(err),
120
- errorMessage: err instanceof Error ? err.message : String(err)
121
- };
122
- }
70
+ const details = this.client.getNumberDetails(flagKey, mappedCtx);
71
+ return toResolutionDetails(details, defaultValue);
123
72
  }
124
73
  async resolveObjectEvaluation(flagKey, defaultValue, context, _logger) {
125
74
  const mappedCtx = mapContext(context, this.targetingKeyMapping);
126
- try {
127
- const listVal = this.client.getStringList(flagKey, mappedCtx);
128
- if (listVal !== void 0) {
129
- return {
130
- value: listVal,
131
- reason: StandardResolutionReasons.TARGETING_MATCH
132
- };
133
- }
134
- const jsonVal = this.client.getJSON(flagKey, mappedCtx);
135
- if (jsonVal !== void 0) {
136
- return { value: jsonVal, reason: StandardResolutionReasons.TARGETING_MATCH };
137
- }
138
- return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };
139
- } catch (err) {
140
- return {
141
- value: defaultValue,
142
- reason: StandardResolutionReasons.ERROR,
143
- errorCode: toErrorCode(err),
144
- errorMessage: err instanceof Error ? err.message : String(err)
145
- };
75
+ const listDetails = this.client.getStringListDetails(flagKey, mappedCtx);
76
+ if (listDetails.reason === "STATIC" || listDetails.reason === "TARGETING_MATCH" || listDetails.reason === "SPLIT") {
77
+ return toResolutionDetails(
78
+ listDetails,
79
+ defaultValue
80
+ );
146
81
  }
82
+ const jsonDetails = this.client.getJSONDetails(flagKey, mappedCtx);
83
+ return toResolutionDetails(jsonDetails, defaultValue);
147
84
  }
148
85
  /**
149
86
  * Escape hatch: access the underlying @quonfig/node client for native-only features
@@ -153,6 +90,54 @@ var QuonfigProvider = class {
153
90
  return this.client;
154
91
  }
155
92
  };
93
+ function toResolutionDetails(details, defaultValue) {
94
+ switch (details.reason) {
95
+ case "STATIC":
96
+ return { value: details.value, reason: StandardResolutionReasons.STATIC };
97
+ case "TARGETING_MATCH":
98
+ return {
99
+ value: details.value,
100
+ reason: StandardResolutionReasons.TARGETING_MATCH
101
+ };
102
+ case "SPLIT":
103
+ return { value: details.value, reason: StandardResolutionReasons.SPLIT };
104
+ case "DEFAULT":
105
+ return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };
106
+ case "ERROR":
107
+ return {
108
+ value: defaultValue,
109
+ reason: StandardResolutionReasons.ERROR,
110
+ errorCode: toOFErrorCode(details.errorCode)
111
+ };
112
+ }
113
+ }
114
+ function toOFErrorCode(code) {
115
+ switch (code) {
116
+ case "FLAG_NOT_FOUND":
117
+ return ErrorCode.FLAG_NOT_FOUND;
118
+ case "TYPE_MISMATCH":
119
+ return ErrorCode.TYPE_MISMATCH;
120
+ case "GENERAL":
121
+ default:
122
+ return ErrorCode.GENERAL;
123
+ }
124
+ }
125
+
126
+ // src/errors.ts
127
+ import { ErrorCode as ErrorCode2 } from "@openfeature/server-sdk";
128
+ function toErrorCode(err) {
129
+ const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
130
+ if (msg.includes("not found") || msg.includes("flag not found") || msg.includes("no value found") || msg.includes("value found for key")) {
131
+ return ErrorCode2.FLAG_NOT_FOUND;
132
+ }
133
+ if (msg.includes("type mismatch")) {
134
+ return ErrorCode2.TYPE_MISMATCH;
135
+ }
136
+ if (msg.includes("not initialized") || msg.includes("provider not ready")) {
137
+ return ErrorCode2.PROVIDER_NOT_READY;
138
+ }
139
+ return ErrorCode2.GENERAL;
140
+ }
156
141
  export {
157
142
  QuonfigProvider,
158
143
  mapContext,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider.ts","../src/context.ts","../src/errors.ts"],"sourcesContent":["import {\n EvaluationContext,\n JsonValue,\n Logger,\n OpenFeatureEventEmitter,\n Provider,\n ProviderEvents,\n ResolutionDetails,\n StandardResolutionReasons,\n} from \"@openfeature/server-sdk\";\nimport { Quonfig, QuonfigOptions } from \"@quonfig/node\";\nimport { mapContext } from \"./context.js\";\nimport { toErrorCode } from \"./errors.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\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 async initialize(_context?: EvaluationContext): Promise<void> {\n await this.client.init();\n }\n\n async shutdown(): Promise<void> {\n 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 try {\n const value = this.client.getBool(flagKey, mappedCtx);\n if (value === undefined) {\n return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };\n }\n return { value, reason: StandardResolutionReasons.TARGETING_MATCH };\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n errorMessage: err instanceof Error ? err.message : String(err),\n };\n }\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 try {\n const value = this.client.getString(flagKey, mappedCtx);\n if (value === undefined) {\n return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };\n }\n return { value, reason: StandardResolutionReasons.TARGETING_MATCH };\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n errorMessage: err instanceof Error ? err.message : String(err),\n };\n }\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 try {\n const value = this.client.getNumber(flagKey, mappedCtx);\n if (value === undefined) {\n return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };\n }\n return { value, reason: StandardResolutionReasons.TARGETING_MATCH };\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n errorMessage: err instanceof Error ? err.message : String(err),\n };\n }\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 {\n // Try string_list first (returns string[])\n const listVal = this.client.getStringList(flagKey, mappedCtx);\n if (listVal !== undefined) {\n return {\n value: listVal as unknown as T,\n reason: StandardResolutionReasons.TARGETING_MATCH,\n };\n }\n // Fall back to JSON\n const jsonVal = this.client.getJSON(flagKey, mappedCtx);\n if (jsonVal !== undefined) {\n return { value: jsonVal as T, reason: StandardResolutionReasons.TARGETING_MATCH };\n }\n return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n errorMessage: err instanceof Error ? err.message : String(err),\n };\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","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 =\n 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,EAIE;AAAA,EAEA;AAAA,EAEA;AAAA,OACK;AACP,SAAS,eAA+B;;;ACEjC,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;;;AChDA,SAAS,iBAAiB;AAQnB,SAAS,YAAY,KAAyB;AACnD,QAAM,MACJ,eAAe,QAAQ,IAAI,QAAQ,YAAY,IAAI,OAAO,GAAG,EAAE,YAAY;AAE7E,MACE,IAAI,SAAS,WAAW,KACxB,IAAI,SAAS,gBAAgB,KAC7B,IAAI,SAAS,gBAAgB,KAC7B,IAAI,SAAS,qBAAqB,GAClC;AACA,WAAO,UAAU;AAAA,EACnB;AACA,MAAI,IAAI,SAAS,eAAe,GAAG;AACjC,WAAO,UAAU;AAAA,EACnB;AACA,MAAI,IAAI,SAAS,iBAAiB,KAAK,IAAI,SAAS,oBAAoB,GAAG;AACzE,WAAO,UAAU;AAAA,EACnB;AACA,SAAO,UAAU;AACnB;;;AFUO,IAAM,kBAAN,MAA0C;AAAA,EAQ/C,YAAY,SAAiC;AAP7C,SAAS,WAAW,EAAE,MAAM,UAAU;AACtC,SAAS,SAAS,IAAI,wBAAwB;AAC9C,SAAS,QAAQ,CAAC;AAMhB,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,EAEA,MAAM,WAAW,UAA6C;AAC5D,UAAM,KAAK,OAAO,KAAK;AAAA,EACzB;AAAA,EAEA,MAAM,WAA0B;AAC9B,SAAK,OAAO,MAAM;AAAA,EACpB;AAAA,EAEA,MAAM,yBACJ,SACA,cACA,SACA,SACqC;AACrC,UAAM,YAAY,WAAW,SAAS,KAAK,mBAAmB;AAC9D,QAAI;AACF,YAAM,QAAQ,KAAK,OAAO,QAAQ,SAAS,SAAS;AACpD,UAAI,UAAU,QAAW;AACvB,eAAO,EAAE,OAAO,cAAc,QAAQ,0BAA0B,QAAQ;AAAA,MAC1E;AACA,aAAO,EAAE,OAAO,QAAQ,0BAA0B,gBAAgB;AAAA,IACpE,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ,0BAA0B;AAAA,QAClC,WAAW,YAAY,GAAG;AAAA,QAC1B,cAAc,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MAC/D;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,wBACJ,SACA,cACA,SACA,SACoC;AACpC,UAAM,YAAY,WAAW,SAAS,KAAK,mBAAmB;AAC9D,QAAI;AACF,YAAM,QAAQ,KAAK,OAAO,UAAU,SAAS,SAAS;AACtD,UAAI,UAAU,QAAW;AACvB,eAAO,EAAE,OAAO,cAAc,QAAQ,0BAA0B,QAAQ;AAAA,MAC1E;AACA,aAAO,EAAE,OAAO,QAAQ,0BAA0B,gBAAgB;AAAA,IACpE,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ,0BAA0B;AAAA,QAClC,WAAW,YAAY,GAAG;AAAA,QAC1B,cAAc,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MAC/D;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,wBACJ,SACA,cACA,SACA,SACoC;AACpC,UAAM,YAAY,WAAW,SAAS,KAAK,mBAAmB;AAC9D,QAAI;AACF,YAAM,QAAQ,KAAK,OAAO,UAAU,SAAS,SAAS;AACtD,UAAI,UAAU,QAAW;AACvB,eAAO,EAAE,OAAO,cAAc,QAAQ,0BAA0B,QAAQ;AAAA,MAC1E;AACA,aAAO,EAAE,OAAO,QAAQ,0BAA0B,gBAAgB;AAAA,IACpE,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ,0BAA0B;AAAA,QAClC,WAAW,YAAY,GAAG;AAAA,QAC1B,cAAc,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MAC/D;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,wBACJ,SACA,cACA,SACA,SAC+B;AAC/B,UAAM,YAAY,WAAW,SAAS,KAAK,mBAAmB;AAC9D,QAAI;AAEF,YAAM,UAAU,KAAK,OAAO,cAAc,SAAS,SAAS;AAC5D,UAAI,YAAY,QAAW;AACzB,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ,0BAA0B;AAAA,QACpC;AAAA,MACF;AAEA,YAAM,UAAU,KAAK,OAAO,QAAQ,SAAS,SAAS;AACtD,UAAI,YAAY,QAAW;AACzB,eAAO,EAAE,OAAO,SAAc,QAAQ,0BAA0B,gBAAgB;AAAA,MAClF;AACA,aAAO,EAAE,OAAO,cAAc,QAAQ,0BAA0B,QAAQ;AAAA,IAC1E,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ,0BAA0B;AAAA,QAClC,WAAW,YAAY,GAAG;AAAA,QAC1B,cAAc,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MAC/D;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AACF;","names":["dotIdx"]}
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\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 async initialize(_context?: EvaluationContext): Promise<void> {\n await this.client.init();\n }\n\n async shutdown(): Promise<void> {\n 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 / GENERAL)\n */\nfunction toResolutionDetails<T>(\n details: EvaluationDetails<T>,\n defaultValue: T,\n): ResolutionDetails<T> {\n switch (details.reason) {\n case \"STATIC\":\n return { value: details.value as T, reason: StandardResolutionReasons.STATIC };\n case \"TARGETING_MATCH\":\n return {\n value: details.value as T,\n reason: StandardResolutionReasons.TARGETING_MATCH,\n };\n case \"SPLIT\":\n return { value: details.value as T, reason: StandardResolutionReasons.SPLIT };\n case \"DEFAULT\":\n return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };\n case \"ERROR\":\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toOFErrorCode(details.errorCode),\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 \"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 =\n 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quonfig/openfeature-node",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "OpenFeature provider for Quonfig -- Node.js",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -22,11 +22,11 @@
22
22
  "prepublishOnly": "npm run build"
23
23
  },
24
24
  "peerDependencies": {
25
- "@quonfig/node": ">=0.0.6",
25
+ "@quonfig/node": ">=0.0.18",
26
26
  "@openfeature/server-sdk": ">=1.0.0"
27
27
  },
28
28
  "devDependencies": {
29
- "@quonfig/node": "^0.0.6",
29
+ "@quonfig/node": "^0.0.18",
30
30
  "@openfeature/server-sdk": "^1.7.0",
31
31
  "@types/node": "^20.11.0",
32
32
  "tsup": "^8.0.0",