@quonfig/openfeature-web 1.1.0 → 2.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 CHANGED
@@ -113,6 +113,33 @@ To use a different property for `targetingKey`:
113
113
  new QuonfigWebProvider({ sdkKey: "...", targetingKeyMapping: "account.id" });
114
114
  ```
115
115
 
116
+ ### Changing the context
117
+
118
+ Call `OpenFeature.setContext()` when the user changes (sign-in, sign-out, switching accounts). The
119
+ provider passes the new context to the client at once and fetches its values. Until that fetch
120
+ lands, reads return the cached copy for the new context (reason `STALE`) if this browser has one,
121
+ and your defaults otherwise. They never return the previous user's values, even when the fetch
122
+ fails.
123
+
124
+ ## Typed reads
125
+
126
+ Each OpenFeature getter reads only the config types that match it. Any other read returns your
127
+ default with reason `ERROR` and error code `TYPE_MISMATCH`:
128
+
129
+ | Getter | Reads |
130
+ | ----------------- | ------------------------------------------ |
131
+ | `getBooleanValue` | `bool` |
132
+ | `getStringValue` | `string`; `duration` as an ISO 8601 string |
133
+ | `getNumberValue` | `int` and `double` |
134
+ | `getObjectValue` | `json`; `string_list` as an array |
135
+
136
+ Strings are never coerced: a stored `"true"` is not a boolean and a stored `"3"` is not a number.
137
+
138
+ A stored value that cannot be parsed as its declared type (an `int` holding `"twelve"` or `3.5`, a
139
+ `duration` holding `"30s"`) returns your default with reason `ERROR` and error code `PARSE_ERROR`
140
+ when you read it with the getter for that type (`getNumberValue` on the `int`, `getStringValue` on
141
+ the `duration`). Read with the getter for another type, it is `TYPE_MISMATCH` as above.
142
+
116
143
  ## What you lose vs. the native SDK
117
144
 
118
145
  The OpenFeature interface covers boolean, string, number, and object types. Some Quonfig-native
@@ -120,7 +147,9 @@ features require `provider.getClient()` (the escape hatch):
120
147
 
121
148
  1. **Log levels** (`shouldLog`, `logger`) — native SDK only
122
149
  2. **`string_list` configs** — access via `getObjectValue` and cast to `string[]`
123
- 3. **`duration` configs** — `getStringValue` returns an ISO 8601 string; parse client-side
150
+ 3. **`duration` configs** — `getStringValue` returns an ISO 8601 string; parse client-side, or read
151
+ it natively with `provider.getClient().getDuration(key, defaultValue)` (`{ seconds, ms }`).
152
+ `getNumberValue` on a duration returns your default with `TYPE_MISMATCH`.
124
153
  4. **`bytes` configs** — not accessible via OpenFeature
125
154
  5. **Bulk config access** (`extract()`, the `configs` getter, `get()`) — native SDK only
126
155
  6. **Context keys** must use dot-notation (`"user.email"`), not nested objects
package/dist/index.d.mts CHANGED
@@ -43,7 +43,14 @@ declare class QuonfigWebProvider implements Provider {
43
43
  private unsubscribe;
44
44
  constructor(options: QuonfigWebProviderOptions);
45
45
  initialize(context?: EvaluationContext): Promise<void>;
46
- onContextChanged(_oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void>;
46
+ /** Called by OpenFeature on `OpenFeature.setContext()`; switches the client to the new context. */
47
+ onContextChange(_oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void>;
48
+ /**
49
+ * @deprecated Use `onContextChange`, the hook OpenFeature calls. Before 2.0.0
50
+ * the provider implemented only this name, so `OpenFeature.setContext()` never
51
+ * reached the client. Kept so direct callers keep working.
52
+ */
53
+ onContextChanged(oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void>;
47
54
  shutdown(): Promise<void>;
48
55
  resolveBooleanEvaluation(flagKey: string, defaultValue: boolean, _context?: EvaluationContext): ResolutionDetails<boolean>;
49
56
  resolveStringEvaluation(flagKey: string, defaultValue: string, _context?: EvaluationContext): ResolutionDetails<string>;
package/dist/index.d.ts CHANGED
@@ -43,7 +43,14 @@ declare class QuonfigWebProvider implements Provider {
43
43
  private unsubscribe;
44
44
  constructor(options: QuonfigWebProviderOptions);
45
45
  initialize(context?: EvaluationContext): Promise<void>;
46
- onContextChanged(_oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void>;
46
+ /** Called by OpenFeature on `OpenFeature.setContext()`; switches the client to the new context. */
47
+ onContextChange(_oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void>;
48
+ /**
49
+ * @deprecated Use `onContextChange`, the hook OpenFeature calls. Before 2.0.0
50
+ * the provider implemented only this name, so `OpenFeature.setContext()` never
51
+ * reached the client. Kept so direct callers keep working.
52
+ */
53
+ onContextChanged(oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void>;
47
54
  shutdown(): Promise<void>;
48
55
  resolveBooleanEvaluation(flagKey: string, defaultValue: boolean, _context?: EvaluationContext): ResolutionDetails<boolean>;
49
56
  resolveStringEvaluation(flagKey: string, defaultValue: string, _context?: EvaluationContext): ResolutionDetails<string>;
package/dist/index.js CHANGED
@@ -109,10 +109,19 @@ var QuonfigWebProvider = class {
109
109
  this.reported = "READY";
110
110
  if (this.currentStatus() !== "READY") setTimeout(() => this.syncStatus(), 0);
111
111
  }
112
- async onContextChanged(_oldCtx, newCtx) {
112
+ /** Called by OpenFeature on `OpenFeature.setContext()`; switches the client to the new context. */
113
+ async onContextChange(_oldCtx, newCtx) {
113
114
  const nativeCtx = mapContext(newCtx, this.targetingKeyMapping);
114
115
  await this.client.updateContext(nativeCtx);
115
116
  }
117
+ /**
118
+ * @deprecated Use `onContextChange`, the hook OpenFeature calls. Before 2.0.0
119
+ * the provider implemented only this name, so `OpenFeature.setContext()` never
120
+ * reached the client. Kept so direct callers keep working.
121
+ */
122
+ async onContextChanged(oldCtx, newCtx) {
123
+ return this.onContextChange(oldCtx, newCtx);
124
+ }
116
125
  async shutdown() {
117
126
  this.unsubscribe?.();
118
127
  this.unsubscribe = void 0;
@@ -176,10 +185,14 @@ var QuonfigWebProvider = class {
176
185
  }
177
186
  if (details.reason === "ERROR") {
178
187
  const noConfig = !this.client.loaded;
188
+ let errorCode = noConfig ? import_web_sdk2.ErrorCode.PROVIDER_NOT_READY : toOFErrorCode(details.errorCode);
189
+ if (errorCode === import_web_sdk2.ErrorCode.TYPE_MISMATCH && readsAs(this.client.configs[flagKey]?.type, expectedType)) {
190
+ errorCode = import_web_sdk2.ErrorCode.PARSE_ERROR;
191
+ }
179
192
  return {
180
193
  value: defaultValue,
181
194
  reason: import_web_sdk2.StandardResolutionReasons.ERROR,
182
- errorCode: noConfig ? import_web_sdk2.ErrorCode.PROVIDER_NOT_READY : toOFErrorCode(details.errorCode),
195
+ errorCode,
183
196
  ...details.errorMessage ? { errorMessage: details.errorMessage } : {},
184
197
  variant: details.variant,
185
198
  flagMetadata: details.flagMetadata
@@ -274,6 +287,24 @@ function toOFReason(reason) {
274
287
  return import_web_sdk2.StandardResolutionReasons.ERROR;
275
288
  }
276
289
  }
290
+ function readsAs(declaredType, expectedType) {
291
+ switch (declaredType) {
292
+ case "bool":
293
+ return expectedType === "boolean";
294
+ case "string":
295
+ case "log_level":
296
+ case "duration":
297
+ return expectedType === "string";
298
+ case "int":
299
+ case "double":
300
+ return expectedType === "number";
301
+ case "string_list":
302
+ case "json":
303
+ return expectedType === "object";
304
+ default:
305
+ return false;
306
+ }
307
+ }
277
308
  function toOFErrorCode(code) {
278
309
  switch (code) {
279
310
  case "FLAG_NOT_FOUND":
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/context.ts","../src/errors.ts"],"sourcesContent":["export { QuonfigWebProvider } from \"./provider\";\nexport type { QuonfigWebProviderOptions } from \"./provider\";\nexport { mapContext } from \"./context\";\nexport { toErrorCode } from \"./errors\";\n","import {\n ErrorCode,\n OpenFeatureEventEmitter,\n Provider,\n ProviderEvents,\n ResolutionDetails,\n StandardResolutionReasons,\n} from \"@openfeature/web-sdk\";\nimport type { EvaluationContext, JsonValue, ResolutionReason } from \"@openfeature/web-sdk\";\nimport { Quonfig } from \"@quonfig/javascript\";\nimport type { EvaluationDetails, OnInitFailure } from \"@quonfig/javascript\";\n\nimport { mapContext } from \"./context\";\nimport { toErrorCode } from \"./errors\";\n\nexport interface QuonfigWebProviderOptions {\n sdkKey: string;\n /** Which Quonfig context property the OpenFeature targetingKey maps to. Default: \"user.id\" */\n targetingKeyMapping?: string;\n /** Override the Quonfig API base URL. */\n apiUrl?: string;\n /** Request timeout in ms. */\n timeout?: number;\n /**\n * What `initialize()` does when no Quonfig config is available by\n * `initTimeout`. Passed to `@quonfig/javascript`'s `init()`; default `\"fail\"`.\n * A cached last-known-good copy counts as config in every mode.\n *\n * - `\"fail\"`: `initialize()` rejects, so the provider status is ERROR. The\n * client keeps retrying and the provider emits PROVIDER_READY on heal.\n * - `\"wait\"`: `initialize()` stays pending (status NOT_READY) until config arrives.\n * - `\"continue\"`: `initialize()` resolves, then the provider emits\n * PROVIDER_STALE (serving a cached copy) or PROVIDER_ERROR (serving code\n * defaults), and PROVIDER_READY on heal.\n */\n onInitFailure?: OnInitFailure;\n /** Overall init deadline in ms before `onInitFailure` applies (SDK default 5000). */\n initTimeout?: number;\n}\n\ntype ReportedStatus = \"READY\" | \"STALE\" | \"ERROR\";\n\nexport class QuonfigWebProvider implements Provider {\n readonly metadata = { name: \"quonfig-web\" } as const;\n readonly runsOn = \"client\" as const;\n hooks = [];\n readonly events = new OpenFeatureEventEmitter();\n\n private client: Quonfig;\n private readonly targetingKeyMapping: string;\n private readonly sdkKey: string;\n private readonly apiUrl: string | undefined;\n private readonly timeout: number | undefined;\n private readonly onInitFailure: OnInitFailure | undefined;\n private readonly initTimeout: number | undefined;\n // The status OpenFeature last saw from this provider; undefined until\n // initialize() settles (OpenFeature owns the status before that).\n private reported: ReportedStatus | undefined;\n private unsubscribe: (() => void) | undefined;\n\n constructor(options: QuonfigWebProviderOptions) {\n this.sdkKey = options.sdkKey;\n this.targetingKeyMapping = options.targetingKeyMapping ?? \"user.id\";\n this.apiUrl = options.apiUrl;\n this.timeout = options.timeout;\n this.onInitFailure = options.onInitFailure;\n this.initTimeout = options.initTimeout;\n this.client = new Quonfig();\n }\n\n async initialize(context?: EvaluationContext): Promise<void> {\n const nativeCtx = context ? mapContext(context, this.targetingKeyMapping) : { \"\": {} };\n\n this.reported = undefined;\n // The SDK notifies subscribers when it heals (or falls back to a cached\n // copy); syncStatus turns that into the matching provider event.\n this.unsubscribe ??= this.client.subscribe(() => this.syncStatus());\n\n try {\n await this.client.init({\n sdkKey: this.sdkKey,\n context: nativeCtx,\n ...(this.apiUrl !== undefined && { apiUrl: this.apiUrl }),\n ...(this.timeout !== undefined && { timeout: this.timeout }),\n ...(this.onInitFailure !== undefined && { onInitFailure: this.onInitFailure }),\n ...(this.initTimeout !== undefined && { initTimeout: this.initTimeout }),\n });\n } catch (err) {\n // onInitFailure \"fail\": OpenFeature marks the provider ERROR from this\n // rejection. The client stays alive and keeps retrying in the\n // background; syncStatus emits PROVIDER_READY when it heals.\n this.reported = \"ERROR\";\n // Catch a heal that landed before OpenFeature applied ERROR.\n setTimeout(() => this.syncStatus(), 0);\n throw err;\n }\n\n // OpenFeature marks the provider READY once this resolves. Under\n // \"continue\" (or a cached copy) the client is not live yet, so report\n // STALE or ERROR right after OpenFeature has applied READY.\n this.reported = \"READY\";\n if (this.currentStatus() !== \"READY\") setTimeout(() => this.syncStatus(), 0);\n }\n\n async onContextChanged(_oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void> {\n const nativeCtx = mapContext(newCtx, this.targetingKeyMapping);\n await this.client.updateContext(nativeCtx);\n }\n\n async shutdown(): Promise<void> {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n await this.client.close();\n }\n\n resolveBooleanEvaluation(\n flagKey: string,\n defaultValue: boolean,\n _context?: EvaluationContext\n ): ResolutionDetails<boolean> {\n return this._resolve(flagKey, defaultValue, \"boolean\");\n }\n\n resolveStringEvaluation(\n flagKey: string,\n defaultValue: string,\n _context?: EvaluationContext\n ): ResolutionDetails<string> {\n return this._resolve(flagKey, defaultValue, \"string\");\n }\n\n resolveNumberEvaluation(\n flagKey: string,\n defaultValue: number,\n _context?: EvaluationContext\n ): ResolutionDetails<number> {\n return this._resolve(flagKey, defaultValue, \"number\");\n }\n\n resolveObjectEvaluation<T extends JsonValue = JsonValue>(\n flagKey: string,\n defaultValue: T,\n _context?: EvaluationContext\n ): ResolutionDetails<T> {\n return this._resolve(flagKey, defaultValue, \"object\");\n }\n\n /** Escape hatch: access the underlying Quonfig client directly. */\n getClient(): Quonfig {\n return this.client;\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n /** What the client is serving: live config, a cached copy, or nothing (defaults). */\n private currentStatus(): ReportedStatus {\n if (this.client.stale) return \"STALE\";\n return this.client.isReady() ? \"READY\" : \"ERROR\";\n }\n\n /** Emit the provider event for a status change (design doc B.5). */\n private syncStatus(): void {\n if (this.reported === undefined) return;\n const next = this.currentStatus();\n if (next === this.reported) return;\n this.reported = next;\n if (next === \"READY\") {\n this.events.emit(ProviderEvents.Ready);\n } else if (next === \"STALE\") {\n this.events.emit(ProviderEvents.Stale, {\n message: \"Quonfig: serving a cached copy of config; delivery not reachable yet\",\n });\n } else {\n this.events.emit(ProviderEvents.Error, {\n message: \"Quonfig: no config loaded; serving code defaults until delivery is reachable\",\n errorCode: ErrorCode.PROVIDER_NOT_READY,\n });\n }\n }\n\n private _resolve<T>(\n flagKey: string,\n defaultValue: T,\n expectedType: \"boolean\" | \"string\" | \"number\" | \"object\"\n ): ResolutionDetails<T> {\n let details: EvaluationDetails;\n try {\n details = this.client.getDetails(flagKey);\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n variant: \"default\",\n flagMetadata: {},\n };\n }\n\n // Errors from the SDK side (FLAG_NOT_FOUND, GENERAL) — pass through. With\n // no config at all (init failed or \"continue\" on defaults) the outage code\n // is PROVIDER_NOT_READY.\n if (details.reason === \"ERROR\") {\n const noConfig = !this.client.loaded;\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: noConfig ? ErrorCode.PROVIDER_NOT_READY : toOFErrorCode(details.errorCode),\n ...(details.errorMessage ? { errorMessage: details.errorMessage } : {}),\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n // Coerce the resolved value to the expected OF shape. A null result\n // signals provider-level TYPE_MISMATCH (the SDK has no requested-type\n // hint, so this happens here, not inside getDetails).\n const coerced = this._coerce<T>(details.value, expectedType, defaultValue);\n if (coerced === null) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: ErrorCode.TYPE_MISMATCH,\n // OF spec convention: variant='default' on the error path; preserve\n // flagMetadata so consumers still see configId/configType.\n variant: \"default\",\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n return {\n value: coerced,\n reason: toOFReason(details.reason),\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n /**\n * Coerce a raw ConfigValue to the expected OF type.\n * Returns null if the type does not match (signals TYPE_MISMATCH).\n */\n private _coerce<T>(\n raw: unknown,\n expectedType: \"boolean\" | \"string\" | \"number\" | \"object\",\n defaultValue: T\n ): T | null {\n switch (expectedType) {\n case \"boolean\":\n if (typeof raw === \"boolean\") return raw as unknown as T;\n return null;\n\n case \"string\":\n if (typeof raw === \"string\") return raw as unknown as T;\n // Duration objects: return ISO 8601 string representation\n if (raw !== null && typeof raw === \"object\" && \"seconds\" in raw && \"ms\" in raw) {\n return this._durationToISO(raw as { seconds: number; ms: number }) as unknown as T;\n }\n return null;\n\n case \"number\":\n if (typeof raw === \"number\") return raw as unknown as T;\n return null;\n\n case \"object\":\n // Arrays (string_list) and plain objects both satisfy \"object\"\n if (Array.isArray(raw)) return raw as unknown as T;\n if (raw !== null && typeof raw === \"object\") return raw as unknown as T;\n return null;\n\n default:\n return null;\n }\n }\n\n /**\n * Convert a Quonfig Duration to its canonical ISO 8601 string in the Quonfig\n * duration grammar (e.g. \"P1DT6H2M1.5S\"): days kept as D, zero components\n * omitted, \"PT0S\" for zero. Works from the integer ms count, so the output\n * carries no float residue.\n */\n private _durationToISO(duration: { seconds: number; ms: number }): string {\n let rest = Math.round(duration.ms);\n const days = Math.floor(rest / 86400000);\n rest -= days * 86400000;\n const hours = Math.floor(rest / 3600000);\n rest -= hours * 3600000;\n const minutes = Math.floor(rest / 60000);\n rest -= minutes * 60000;\n const secs = Math.floor(rest / 1000);\n const millis = rest - secs * 1000;\n\n let time = \"\";\n if (hours > 0) time += `${hours}H`;\n if (minutes > 0) time += `${minutes}M`;\n if (secs > 0 || millis > 0) {\n const fraction = millis > 0 ? `.${String(millis).padStart(3, \"0\").replace(/0+$/, \"\")}` : \"\";\n time += `${secs}${fraction}S`;\n }\n\n if (days === 0 && time === \"\") return \"PT0S\";\n return `P${days > 0 ? `${days}D` : \"\"}${time ? `T${time}` : \"\"}`;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Map sdk-javascript's EvaluationReason onto OpenFeature's StandardResolutionReasons.\n * SPLIT is provider-defined; OF allows arbitrary strings, so the literal value works.\n */\nfunction toOFReason(reason: EvaluationDetails[\"reason\"]): ResolutionReason {\n switch (reason) {\n case \"STATIC\":\n return StandardResolutionReasons.STATIC;\n case \"TARGETING_MATCH\":\n return StandardResolutionReasons.TARGETING_MATCH;\n case \"SPLIT\":\n return StandardResolutionReasons.SPLIT;\n case \"DEFAULT\":\n return StandardResolutionReasons.DEFAULT;\n case \"STALE\":\n return StandardResolutionReasons.STALE;\n case \"ERROR\":\n default:\n return StandardResolutionReasons.ERROR;\n }\n}\n\n/** Translate sdk-javascript's EvaluationErrorCode onto the OF ErrorCode enum. */\nfunction toOFErrorCode(code: EvaluationDetails[\"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/web-sdk\";\nimport type { Contexts } from \"@quonfig/javascript\";\n\n/**\n * Map an OpenFeature EvaluationContext to Quonfig's nested Contexts format.\n *\n * Rules:\n * - `targetingKey` maps to the property specified by `targetingKeyMapping` (default: \"user.id\")\n * - Keys with a dot are split on the FIRST dot: namespace = left side, key = right side\n * - Keys without a dot go into the default (\"\") namespace\n */\nexport function mapContext(\n ofContext: EvaluationContext,\n targetingKeyMapping = \"user.id\"\n): Contexts {\n const result: Record<string, Record<string, unknown>> = {};\n\n for (const [key, value] of Object.entries(ofContext)) {\n if (value === undefined) continue;\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] = value;\n continue;\n }\n\n const dotIdx = key.indexOf(\".\");\n if (dotIdx === -1) {\n result[\"\"] ??= {};\n result[\"\"][key] = value;\n } else {\n const ns = key.slice(0, dotIdx);\n const prop = key.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = value;\n }\n }\n\n return result as Contexts;\n}\n","import { ErrorCode } from \"@openfeature/web-sdk\";\n\n/**\n * Map a native SDK error (or unknown throw) to an OpenFeature ErrorCode.\n */\nexport function toErrorCode(err: unknown): ErrorCode {\n const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();\n\n if (msg.includes(\"flag not found\") || msg.includes(\"not found\")) {\n return ErrorCode.FLAG_NOT_FOUND;\n }\n if (msg.includes(\"type mismatch\") || msg.includes(\"type_mismatch\")) {\n return ErrorCode.TYPE_MISMATCH;\n }\n if (\n msg.includes(\"not initialized\") ||\n msg.includes(\"provider_not_ready\") ||\n msg.includes(\"call init()\") ||\n msg.includes(\"not ready\")\n ) {\n return ErrorCode.PROVIDER_NOT_READY;\n }\n return ErrorCode.GENERAL;\n}\n"],"mappings":"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","names":["import_web_sdk","dotIdx"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/context.ts","../src/errors.ts"],"sourcesContent":["export { QuonfigWebProvider } from \"./provider\";\nexport type { QuonfigWebProviderOptions } from \"./provider\";\nexport { mapContext } from \"./context\";\nexport { toErrorCode } from \"./errors\";\n","import {\n ErrorCode,\n OpenFeatureEventEmitter,\n Provider,\n ProviderEvents,\n ResolutionDetails,\n StandardResolutionReasons,\n} from \"@openfeature/web-sdk\";\nimport type { EvaluationContext, JsonValue, ResolutionReason } from \"@openfeature/web-sdk\";\nimport { Quonfig } from \"@quonfig/javascript\";\nimport type { EvaluationDetails, OnInitFailure } from \"@quonfig/javascript\";\n\nimport { mapContext } from \"./context\";\nimport { toErrorCode } from \"./errors\";\n\nexport interface QuonfigWebProviderOptions {\n sdkKey: string;\n /** Which Quonfig context property the OpenFeature targetingKey maps to. Default: \"user.id\" */\n targetingKeyMapping?: string;\n /** Override the Quonfig API base URL. */\n apiUrl?: string;\n /** Request timeout in ms. */\n timeout?: number;\n /**\n * What `initialize()` does when no Quonfig config is available by\n * `initTimeout`. Passed to `@quonfig/javascript`'s `init()`; default `\"fail\"`.\n * A cached last-known-good copy counts as config in every mode.\n *\n * - `\"fail\"`: `initialize()` rejects, so the provider status is ERROR. The\n * client keeps retrying and the provider emits PROVIDER_READY on heal.\n * - `\"wait\"`: `initialize()` stays pending (status NOT_READY) until config arrives.\n * - `\"continue\"`: `initialize()` resolves, then the provider emits\n * PROVIDER_STALE (serving a cached copy) or PROVIDER_ERROR (serving code\n * defaults), and PROVIDER_READY on heal.\n */\n onInitFailure?: OnInitFailure;\n /** Overall init deadline in ms before `onInitFailure` applies (SDK default 5000). */\n initTimeout?: number;\n}\n\ntype ReportedStatus = \"READY\" | \"STALE\" | \"ERROR\";\n\nexport class QuonfigWebProvider implements Provider {\n readonly metadata = { name: \"quonfig-web\" } as const;\n readonly runsOn = \"client\" as const;\n hooks = [];\n readonly events = new OpenFeatureEventEmitter();\n\n private client: Quonfig;\n private readonly targetingKeyMapping: string;\n private readonly sdkKey: string;\n private readonly apiUrl: string | undefined;\n private readonly timeout: number | undefined;\n private readonly onInitFailure: OnInitFailure | undefined;\n private readonly initTimeout: number | undefined;\n // The status OpenFeature last saw from this provider; undefined until\n // initialize() settles (OpenFeature owns the status before that).\n private reported: ReportedStatus | undefined;\n private unsubscribe: (() => void) | undefined;\n\n constructor(options: QuonfigWebProviderOptions) {\n this.sdkKey = options.sdkKey;\n this.targetingKeyMapping = options.targetingKeyMapping ?? \"user.id\";\n this.apiUrl = options.apiUrl;\n this.timeout = options.timeout;\n this.onInitFailure = options.onInitFailure;\n this.initTimeout = options.initTimeout;\n this.client = new Quonfig();\n }\n\n async initialize(context?: EvaluationContext): Promise<void> {\n const nativeCtx = context ? mapContext(context, this.targetingKeyMapping) : { \"\": {} };\n\n this.reported = undefined;\n // The SDK notifies subscribers when it heals (or falls back to a cached\n // copy); syncStatus turns that into the matching provider event.\n this.unsubscribe ??= this.client.subscribe(() => this.syncStatus());\n\n try {\n await this.client.init({\n sdkKey: this.sdkKey,\n context: nativeCtx,\n ...(this.apiUrl !== undefined && { apiUrl: this.apiUrl }),\n ...(this.timeout !== undefined && { timeout: this.timeout }),\n ...(this.onInitFailure !== undefined && { onInitFailure: this.onInitFailure }),\n ...(this.initTimeout !== undefined && { initTimeout: this.initTimeout }),\n });\n } catch (err) {\n // onInitFailure \"fail\": OpenFeature marks the provider ERROR from this\n // rejection. The client stays alive and keeps retrying in the\n // background; syncStatus emits PROVIDER_READY when it heals.\n this.reported = \"ERROR\";\n // Catch a heal that landed before OpenFeature applied ERROR.\n setTimeout(() => this.syncStatus(), 0);\n throw err;\n }\n\n // OpenFeature marks the provider READY once this resolves. Under\n // \"continue\" (or a cached copy) the client is not live yet, so report\n // STALE or ERROR right after OpenFeature has applied READY.\n this.reported = \"READY\";\n if (this.currentStatus() !== \"READY\") setTimeout(() => this.syncStatus(), 0);\n }\n\n /** Called by OpenFeature on `OpenFeature.setContext()`; switches the client to the new context. */\n async onContextChange(_oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void> {\n const nativeCtx = mapContext(newCtx, this.targetingKeyMapping);\n await this.client.updateContext(nativeCtx);\n }\n\n /**\n * @deprecated Use `onContextChange`, the hook OpenFeature calls. Before 2.0.0\n * the provider implemented only this name, so `OpenFeature.setContext()` never\n * reached the client. Kept so direct callers keep working.\n */\n async onContextChanged(oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void> {\n return this.onContextChange(oldCtx, newCtx);\n }\n\n async shutdown(): Promise<void> {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n await this.client.close();\n }\n\n resolveBooleanEvaluation(\n flagKey: string,\n defaultValue: boolean,\n _context?: EvaluationContext\n ): ResolutionDetails<boolean> {\n return this._resolve(flagKey, defaultValue, \"boolean\");\n }\n\n resolveStringEvaluation(\n flagKey: string,\n defaultValue: string,\n _context?: EvaluationContext\n ): ResolutionDetails<string> {\n return this._resolve(flagKey, defaultValue, \"string\");\n }\n\n resolveNumberEvaluation(\n flagKey: string,\n defaultValue: number,\n _context?: EvaluationContext\n ): ResolutionDetails<number> {\n return this._resolve(flagKey, defaultValue, \"number\");\n }\n\n resolveObjectEvaluation<T extends JsonValue = JsonValue>(\n flagKey: string,\n defaultValue: T,\n _context?: EvaluationContext\n ): ResolutionDetails<T> {\n return this._resolve(flagKey, defaultValue, \"object\");\n }\n\n /** Escape hatch: access the underlying Quonfig client directly. */\n getClient(): Quonfig {\n return this.client;\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n /** What the client is serving: live config, a cached copy, or nothing (defaults). */\n private currentStatus(): ReportedStatus {\n if (this.client.stale) return \"STALE\";\n return this.client.isReady() ? \"READY\" : \"ERROR\";\n }\n\n /** Emit the provider event for a status change (design doc B.5). */\n private syncStatus(): void {\n if (this.reported === undefined) return;\n const next = this.currentStatus();\n if (next === this.reported) return;\n this.reported = next;\n if (next === \"READY\") {\n this.events.emit(ProviderEvents.Ready);\n } else if (next === \"STALE\") {\n this.events.emit(ProviderEvents.Stale, {\n message: \"Quonfig: serving a cached copy of config; delivery not reachable yet\",\n });\n } else {\n this.events.emit(ProviderEvents.Error, {\n message: \"Quonfig: no config loaded; serving code defaults until delivery is reachable\",\n errorCode: ErrorCode.PROVIDER_NOT_READY,\n });\n }\n }\n\n private _resolve<T>(\n flagKey: string,\n defaultValue: T,\n expectedType: \"boolean\" | \"string\" | \"number\" | \"object\"\n ): ResolutionDetails<T> {\n let details: EvaluationDetails;\n try {\n details = this.client.getDetails(flagKey);\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n variant: \"default\",\n flagMetadata: {},\n };\n }\n\n // Errors from the SDK side (FLAG_NOT_FOUND, GENERAL) — pass through. With\n // no config at all (init failed or \"continue\" on defaults) the outage code\n // is PROVIDER_NOT_READY.\n if (details.reason === \"ERROR\") {\n const noConfig = !this.client.loaded;\n let errorCode = noConfig ? ErrorCode.PROVIDER_NOT_READY : toOFErrorCode(details.errorCode);\n // The SDK reports a stored value that failed to decode as its declared\n // type as TYPE_MISMATCH, without knowing what type the caller asked\n // for. Read with the method for that declared type it is a data\n // problem: PARSE_ERROR (qfg-9xsl). Read with the method for another\n // type it stays TYPE_MISMATCH.\n if (\n errorCode === ErrorCode.TYPE_MISMATCH &&\n readsAs(this.client.configs[flagKey]?.type, expectedType)\n ) {\n errorCode = ErrorCode.PARSE_ERROR;\n }\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode,\n ...(details.errorMessage ? { errorMessage: details.errorMessage } : {}),\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n // Coerce the resolved value to the expected OF shape. A null result\n // signals provider-level TYPE_MISMATCH (the SDK has no requested-type\n // hint, so this happens here, not inside getDetails).\n const coerced = this._coerce<T>(details.value, expectedType, defaultValue);\n if (coerced === null) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: ErrorCode.TYPE_MISMATCH,\n // OF spec convention: variant='default' on the error path; preserve\n // flagMetadata so consumers still see configId/configType.\n variant: \"default\",\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n return {\n value: coerced,\n reason: toOFReason(details.reason),\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n /**\n * Coerce a raw ConfigValue to the expected OF type.\n * Returns null if the type does not match (signals TYPE_MISMATCH).\n */\n private _coerce<T>(\n raw: unknown,\n expectedType: \"boolean\" | \"string\" | \"number\" | \"object\",\n defaultValue: T\n ): T | null {\n switch (expectedType) {\n case \"boolean\":\n if (typeof raw === \"boolean\") return raw as unknown as T;\n return null;\n\n case \"string\":\n if (typeof raw === \"string\") return raw as unknown as T;\n // Duration objects: return ISO 8601 string representation\n if (raw !== null && typeof raw === \"object\" && \"seconds\" in raw && \"ms\" in raw) {\n return this._durationToISO(raw as { seconds: number; ms: number }) as unknown as T;\n }\n return null;\n\n case \"number\":\n if (typeof raw === \"number\") return raw as unknown as T;\n return null;\n\n case \"object\":\n // Arrays (string_list) and plain objects both satisfy \"object\"\n if (Array.isArray(raw)) return raw as unknown as T;\n if (raw !== null && typeof raw === \"object\") return raw as unknown as T;\n return null;\n\n default:\n return null;\n }\n }\n\n /**\n * Convert a Quonfig Duration to its canonical ISO 8601 string in the Quonfig\n * duration grammar (e.g. \"P1DT6H2M1.5S\"): days kept as D, zero components\n * omitted, \"PT0S\" for zero. Works from the integer ms count, so the output\n * carries no float residue.\n */\n private _durationToISO(duration: { seconds: number; ms: number }): string {\n let rest = Math.round(duration.ms);\n const days = Math.floor(rest / 86400000);\n rest -= days * 86400000;\n const hours = Math.floor(rest / 3600000);\n rest -= hours * 3600000;\n const minutes = Math.floor(rest / 60000);\n rest -= minutes * 60000;\n const secs = Math.floor(rest / 1000);\n const millis = rest - secs * 1000;\n\n let time = \"\";\n if (hours > 0) time += `${hours}H`;\n if (minutes > 0) time += `${minutes}M`;\n if (secs > 0 || millis > 0) {\n const fraction = millis > 0 ? `.${String(millis).padStart(3, \"0\").replace(/0+$/, \"\")}` : \"\";\n time += `${secs}${fraction}S`;\n }\n\n if (days === 0 && time === \"\") return \"PT0S\";\n return `P${days > 0 ? `${days}D` : \"\"}${time ? `T${time}` : \"\"}`;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Map sdk-javascript's EvaluationReason onto OpenFeature's StandardResolutionReasons.\n * SPLIT is provider-defined; OF allows arbitrary strings, so the literal value works.\n */\nfunction toOFReason(reason: EvaluationDetails[\"reason\"]): ResolutionReason {\n switch (reason) {\n case \"STATIC\":\n return StandardResolutionReasons.STATIC;\n case \"TARGETING_MATCH\":\n return StandardResolutionReasons.TARGETING_MATCH;\n case \"SPLIT\":\n return StandardResolutionReasons.SPLIT;\n case \"DEFAULT\":\n return StandardResolutionReasons.DEFAULT;\n case \"STALE\":\n return StandardResolutionReasons.STALE;\n case \"ERROR\":\n default:\n return StandardResolutionReasons.ERROR;\n }\n}\n\n/**\n * True when a config of Quonfig value type `declaredType` is read with the OF\n * method for `expectedType`. OpenFeature has one number method (int and\n * double), reads durations as ISO 8601 strings, and reads string_list and\n * json through the object method.\n */\nfunction readsAs(\n declaredType: string | undefined,\n expectedType: \"boolean\" | \"string\" | \"number\" | \"object\"\n): boolean {\n switch (declaredType) {\n case \"bool\":\n return expectedType === \"boolean\";\n case \"string\":\n case \"log_level\":\n case \"duration\":\n return expectedType === \"string\";\n case \"int\":\n case \"double\":\n return expectedType === \"number\";\n case \"string_list\":\n case \"json\":\n return expectedType === \"object\";\n default:\n return false;\n }\n}\n\n/** Translate sdk-javascript's EvaluationErrorCode onto the OF ErrorCode enum. */\nfunction toOFErrorCode(code: EvaluationDetails[\"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/web-sdk\";\nimport type { Contexts } from \"@quonfig/javascript\";\n\n/**\n * Map an OpenFeature EvaluationContext to Quonfig's nested Contexts format.\n *\n * Rules:\n * - `targetingKey` maps to the property specified by `targetingKeyMapping` (default: \"user.id\")\n * - Keys with a dot are split on the FIRST dot: namespace = left side, key = right side\n * - Keys without a dot go into the default (\"\") namespace\n */\nexport function mapContext(\n ofContext: EvaluationContext,\n targetingKeyMapping = \"user.id\"\n): Contexts {\n const result: Record<string, Record<string, unknown>> = {};\n\n for (const [key, value] of Object.entries(ofContext)) {\n if (value === undefined) continue;\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] = value;\n continue;\n }\n\n const dotIdx = key.indexOf(\".\");\n if (dotIdx === -1) {\n result[\"\"] ??= {};\n result[\"\"][key] = value;\n } else {\n const ns = key.slice(0, dotIdx);\n const prop = key.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = value;\n }\n }\n\n return result as Contexts;\n}\n","import { ErrorCode } from \"@openfeature/web-sdk\";\n\n/**\n * Map a native SDK error (or unknown throw) to an OpenFeature ErrorCode.\n */\nexport function toErrorCode(err: unknown): ErrorCode {\n const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();\n\n if (msg.includes(\"flag not found\") || msg.includes(\"not found\")) {\n return ErrorCode.FLAG_NOT_FOUND;\n }\n if (msg.includes(\"type mismatch\") || msg.includes(\"type_mismatch\")) {\n return ErrorCode.TYPE_MISMATCH;\n }\n if (\n msg.includes(\"not initialized\") ||\n msg.includes(\"provider_not_ready\") ||\n msg.includes(\"call init()\") ||\n msg.includes(\"not ready\")\n ) {\n return ErrorCode.PROVIDER_NOT_READY;\n }\n return ErrorCode.GENERAL;\n}\n"],"mappings":"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","names":["import_web_sdk","dotIdx"]}
package/dist/index.mjs CHANGED
@@ -86,10 +86,19 @@ var QuonfigWebProvider = class {
86
86
  this.reported = "READY";
87
87
  if (this.currentStatus() !== "READY") setTimeout(() => this.syncStatus(), 0);
88
88
  }
89
- async onContextChanged(_oldCtx, newCtx) {
89
+ /** Called by OpenFeature on `OpenFeature.setContext()`; switches the client to the new context. */
90
+ async onContextChange(_oldCtx, newCtx) {
90
91
  const nativeCtx = mapContext(newCtx, this.targetingKeyMapping);
91
92
  await this.client.updateContext(nativeCtx);
92
93
  }
94
+ /**
95
+ * @deprecated Use `onContextChange`, the hook OpenFeature calls. Before 2.0.0
96
+ * the provider implemented only this name, so `OpenFeature.setContext()` never
97
+ * reached the client. Kept so direct callers keep working.
98
+ */
99
+ async onContextChanged(oldCtx, newCtx) {
100
+ return this.onContextChange(oldCtx, newCtx);
101
+ }
93
102
  async shutdown() {
94
103
  this.unsubscribe?.();
95
104
  this.unsubscribe = void 0;
@@ -153,10 +162,14 @@ var QuonfigWebProvider = class {
153
162
  }
154
163
  if (details.reason === "ERROR") {
155
164
  const noConfig = !this.client.loaded;
165
+ let errorCode = noConfig ? ErrorCode2.PROVIDER_NOT_READY : toOFErrorCode(details.errorCode);
166
+ if (errorCode === ErrorCode2.TYPE_MISMATCH && readsAs(this.client.configs[flagKey]?.type, expectedType)) {
167
+ errorCode = ErrorCode2.PARSE_ERROR;
168
+ }
156
169
  return {
157
170
  value: defaultValue,
158
171
  reason: StandardResolutionReasons.ERROR,
159
- errorCode: noConfig ? ErrorCode2.PROVIDER_NOT_READY : toOFErrorCode(details.errorCode),
172
+ errorCode,
160
173
  ...details.errorMessage ? { errorMessage: details.errorMessage } : {},
161
174
  variant: details.variant,
162
175
  flagMetadata: details.flagMetadata
@@ -251,6 +264,24 @@ function toOFReason(reason) {
251
264
  return StandardResolutionReasons.ERROR;
252
265
  }
253
266
  }
267
+ function readsAs(declaredType, expectedType) {
268
+ switch (declaredType) {
269
+ case "bool":
270
+ return expectedType === "boolean";
271
+ case "string":
272
+ case "log_level":
273
+ case "duration":
274
+ return expectedType === "string";
275
+ case "int":
276
+ case "double":
277
+ return expectedType === "number";
278
+ case "string_list":
279
+ case "json":
280
+ return expectedType === "object";
281
+ default:
282
+ return false;
283
+ }
284
+ }
254
285
  function toOFErrorCode(code) {
255
286
  switch (code) {
256
287
  case "FLAG_NOT_FOUND":
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider.ts","../src/context.ts","../src/errors.ts"],"sourcesContent":["import {\n ErrorCode,\n OpenFeatureEventEmitter,\n Provider,\n ProviderEvents,\n ResolutionDetails,\n StandardResolutionReasons,\n} from \"@openfeature/web-sdk\";\nimport type { EvaluationContext, JsonValue, ResolutionReason } from \"@openfeature/web-sdk\";\nimport { Quonfig } from \"@quonfig/javascript\";\nimport type { EvaluationDetails, OnInitFailure } from \"@quonfig/javascript\";\n\nimport { mapContext } from \"./context\";\nimport { toErrorCode } from \"./errors\";\n\nexport interface QuonfigWebProviderOptions {\n sdkKey: string;\n /** Which Quonfig context property the OpenFeature targetingKey maps to. Default: \"user.id\" */\n targetingKeyMapping?: string;\n /** Override the Quonfig API base URL. */\n apiUrl?: string;\n /** Request timeout in ms. */\n timeout?: number;\n /**\n * What `initialize()` does when no Quonfig config is available by\n * `initTimeout`. Passed to `@quonfig/javascript`'s `init()`; default `\"fail\"`.\n * A cached last-known-good copy counts as config in every mode.\n *\n * - `\"fail\"`: `initialize()` rejects, so the provider status is ERROR. The\n * client keeps retrying and the provider emits PROVIDER_READY on heal.\n * - `\"wait\"`: `initialize()` stays pending (status NOT_READY) until config arrives.\n * - `\"continue\"`: `initialize()` resolves, then the provider emits\n * PROVIDER_STALE (serving a cached copy) or PROVIDER_ERROR (serving code\n * defaults), and PROVIDER_READY on heal.\n */\n onInitFailure?: OnInitFailure;\n /** Overall init deadline in ms before `onInitFailure` applies (SDK default 5000). */\n initTimeout?: number;\n}\n\ntype ReportedStatus = \"READY\" | \"STALE\" | \"ERROR\";\n\nexport class QuonfigWebProvider implements Provider {\n readonly metadata = { name: \"quonfig-web\" } as const;\n readonly runsOn = \"client\" as const;\n hooks = [];\n readonly events = new OpenFeatureEventEmitter();\n\n private client: Quonfig;\n private readonly targetingKeyMapping: string;\n private readonly sdkKey: string;\n private readonly apiUrl: string | undefined;\n private readonly timeout: number | undefined;\n private readonly onInitFailure: OnInitFailure | undefined;\n private readonly initTimeout: number | undefined;\n // The status OpenFeature last saw from this provider; undefined until\n // initialize() settles (OpenFeature owns the status before that).\n private reported: ReportedStatus | undefined;\n private unsubscribe: (() => void) | undefined;\n\n constructor(options: QuonfigWebProviderOptions) {\n this.sdkKey = options.sdkKey;\n this.targetingKeyMapping = options.targetingKeyMapping ?? \"user.id\";\n this.apiUrl = options.apiUrl;\n this.timeout = options.timeout;\n this.onInitFailure = options.onInitFailure;\n this.initTimeout = options.initTimeout;\n this.client = new Quonfig();\n }\n\n async initialize(context?: EvaluationContext): Promise<void> {\n const nativeCtx = context ? mapContext(context, this.targetingKeyMapping) : { \"\": {} };\n\n this.reported = undefined;\n // The SDK notifies subscribers when it heals (or falls back to a cached\n // copy); syncStatus turns that into the matching provider event.\n this.unsubscribe ??= this.client.subscribe(() => this.syncStatus());\n\n try {\n await this.client.init({\n sdkKey: this.sdkKey,\n context: nativeCtx,\n ...(this.apiUrl !== undefined && { apiUrl: this.apiUrl }),\n ...(this.timeout !== undefined && { timeout: this.timeout }),\n ...(this.onInitFailure !== undefined && { onInitFailure: this.onInitFailure }),\n ...(this.initTimeout !== undefined && { initTimeout: this.initTimeout }),\n });\n } catch (err) {\n // onInitFailure \"fail\": OpenFeature marks the provider ERROR from this\n // rejection. The client stays alive and keeps retrying in the\n // background; syncStatus emits PROVIDER_READY when it heals.\n this.reported = \"ERROR\";\n // Catch a heal that landed before OpenFeature applied ERROR.\n setTimeout(() => this.syncStatus(), 0);\n throw err;\n }\n\n // OpenFeature marks the provider READY once this resolves. Under\n // \"continue\" (or a cached copy) the client is not live yet, so report\n // STALE or ERROR right after OpenFeature has applied READY.\n this.reported = \"READY\";\n if (this.currentStatus() !== \"READY\") setTimeout(() => this.syncStatus(), 0);\n }\n\n async onContextChanged(_oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void> {\n const nativeCtx = mapContext(newCtx, this.targetingKeyMapping);\n await this.client.updateContext(nativeCtx);\n }\n\n async shutdown(): Promise<void> {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n await this.client.close();\n }\n\n resolveBooleanEvaluation(\n flagKey: string,\n defaultValue: boolean,\n _context?: EvaluationContext\n ): ResolutionDetails<boolean> {\n return this._resolve(flagKey, defaultValue, \"boolean\");\n }\n\n resolveStringEvaluation(\n flagKey: string,\n defaultValue: string,\n _context?: EvaluationContext\n ): ResolutionDetails<string> {\n return this._resolve(flagKey, defaultValue, \"string\");\n }\n\n resolveNumberEvaluation(\n flagKey: string,\n defaultValue: number,\n _context?: EvaluationContext\n ): ResolutionDetails<number> {\n return this._resolve(flagKey, defaultValue, \"number\");\n }\n\n resolveObjectEvaluation<T extends JsonValue = JsonValue>(\n flagKey: string,\n defaultValue: T,\n _context?: EvaluationContext\n ): ResolutionDetails<T> {\n return this._resolve(flagKey, defaultValue, \"object\");\n }\n\n /** Escape hatch: access the underlying Quonfig client directly. */\n getClient(): Quonfig {\n return this.client;\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n /** What the client is serving: live config, a cached copy, or nothing (defaults). */\n private currentStatus(): ReportedStatus {\n if (this.client.stale) return \"STALE\";\n return this.client.isReady() ? \"READY\" : \"ERROR\";\n }\n\n /** Emit the provider event for a status change (design doc B.5). */\n private syncStatus(): void {\n if (this.reported === undefined) return;\n const next = this.currentStatus();\n if (next === this.reported) return;\n this.reported = next;\n if (next === \"READY\") {\n this.events.emit(ProviderEvents.Ready);\n } else if (next === \"STALE\") {\n this.events.emit(ProviderEvents.Stale, {\n message: \"Quonfig: serving a cached copy of config; delivery not reachable yet\",\n });\n } else {\n this.events.emit(ProviderEvents.Error, {\n message: \"Quonfig: no config loaded; serving code defaults until delivery is reachable\",\n errorCode: ErrorCode.PROVIDER_NOT_READY,\n });\n }\n }\n\n private _resolve<T>(\n flagKey: string,\n defaultValue: T,\n expectedType: \"boolean\" | \"string\" | \"number\" | \"object\"\n ): ResolutionDetails<T> {\n let details: EvaluationDetails;\n try {\n details = this.client.getDetails(flagKey);\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n variant: \"default\",\n flagMetadata: {},\n };\n }\n\n // Errors from the SDK side (FLAG_NOT_FOUND, GENERAL) — pass through. With\n // no config at all (init failed or \"continue\" on defaults) the outage code\n // is PROVIDER_NOT_READY.\n if (details.reason === \"ERROR\") {\n const noConfig = !this.client.loaded;\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: noConfig ? ErrorCode.PROVIDER_NOT_READY : toOFErrorCode(details.errorCode),\n ...(details.errorMessage ? { errorMessage: details.errorMessage } : {}),\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n // Coerce the resolved value to the expected OF shape. A null result\n // signals provider-level TYPE_MISMATCH (the SDK has no requested-type\n // hint, so this happens here, not inside getDetails).\n const coerced = this._coerce<T>(details.value, expectedType, defaultValue);\n if (coerced === null) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: ErrorCode.TYPE_MISMATCH,\n // OF spec convention: variant='default' on the error path; preserve\n // flagMetadata so consumers still see configId/configType.\n variant: \"default\",\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n return {\n value: coerced,\n reason: toOFReason(details.reason),\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n /**\n * Coerce a raw ConfigValue to the expected OF type.\n * Returns null if the type does not match (signals TYPE_MISMATCH).\n */\n private _coerce<T>(\n raw: unknown,\n expectedType: \"boolean\" | \"string\" | \"number\" | \"object\",\n defaultValue: T\n ): T | null {\n switch (expectedType) {\n case \"boolean\":\n if (typeof raw === \"boolean\") return raw as unknown as T;\n return null;\n\n case \"string\":\n if (typeof raw === \"string\") return raw as unknown as T;\n // Duration objects: return ISO 8601 string representation\n if (raw !== null && typeof raw === \"object\" && \"seconds\" in raw && \"ms\" in raw) {\n return this._durationToISO(raw as { seconds: number; ms: number }) as unknown as T;\n }\n return null;\n\n case \"number\":\n if (typeof raw === \"number\") return raw as unknown as T;\n return null;\n\n case \"object\":\n // Arrays (string_list) and plain objects both satisfy \"object\"\n if (Array.isArray(raw)) return raw as unknown as T;\n if (raw !== null && typeof raw === \"object\") return raw as unknown as T;\n return null;\n\n default:\n return null;\n }\n }\n\n /**\n * Convert a Quonfig Duration to its canonical ISO 8601 string in the Quonfig\n * duration grammar (e.g. \"P1DT6H2M1.5S\"): days kept as D, zero components\n * omitted, \"PT0S\" for zero. Works from the integer ms count, so the output\n * carries no float residue.\n */\n private _durationToISO(duration: { seconds: number; ms: number }): string {\n let rest = Math.round(duration.ms);\n const days = Math.floor(rest / 86400000);\n rest -= days * 86400000;\n const hours = Math.floor(rest / 3600000);\n rest -= hours * 3600000;\n const minutes = Math.floor(rest / 60000);\n rest -= minutes * 60000;\n const secs = Math.floor(rest / 1000);\n const millis = rest - secs * 1000;\n\n let time = \"\";\n if (hours > 0) time += `${hours}H`;\n if (minutes > 0) time += `${minutes}M`;\n if (secs > 0 || millis > 0) {\n const fraction = millis > 0 ? `.${String(millis).padStart(3, \"0\").replace(/0+$/, \"\")}` : \"\";\n time += `${secs}${fraction}S`;\n }\n\n if (days === 0 && time === \"\") return \"PT0S\";\n return `P${days > 0 ? `${days}D` : \"\"}${time ? `T${time}` : \"\"}`;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Map sdk-javascript's EvaluationReason onto OpenFeature's StandardResolutionReasons.\n * SPLIT is provider-defined; OF allows arbitrary strings, so the literal value works.\n */\nfunction toOFReason(reason: EvaluationDetails[\"reason\"]): ResolutionReason {\n switch (reason) {\n case \"STATIC\":\n return StandardResolutionReasons.STATIC;\n case \"TARGETING_MATCH\":\n return StandardResolutionReasons.TARGETING_MATCH;\n case \"SPLIT\":\n return StandardResolutionReasons.SPLIT;\n case \"DEFAULT\":\n return StandardResolutionReasons.DEFAULT;\n case \"STALE\":\n return StandardResolutionReasons.STALE;\n case \"ERROR\":\n default:\n return StandardResolutionReasons.ERROR;\n }\n}\n\n/** Translate sdk-javascript's EvaluationErrorCode onto the OF ErrorCode enum. */\nfunction toOFErrorCode(code: EvaluationDetails[\"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/web-sdk\";\nimport type { Contexts } from \"@quonfig/javascript\";\n\n/**\n * Map an OpenFeature EvaluationContext to Quonfig's nested Contexts format.\n *\n * Rules:\n * - `targetingKey` maps to the property specified by `targetingKeyMapping` (default: \"user.id\")\n * - Keys with a dot are split on the FIRST dot: namespace = left side, key = right side\n * - Keys without a dot go into the default (\"\") namespace\n */\nexport function mapContext(\n ofContext: EvaluationContext,\n targetingKeyMapping = \"user.id\"\n): Contexts {\n const result: Record<string, Record<string, unknown>> = {};\n\n for (const [key, value] of Object.entries(ofContext)) {\n if (value === undefined) continue;\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] = value;\n continue;\n }\n\n const dotIdx = key.indexOf(\".\");\n if (dotIdx === -1) {\n result[\"\"] ??= {};\n result[\"\"][key] = value;\n } else {\n const ns = key.slice(0, dotIdx);\n const prop = key.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = value;\n }\n }\n\n return result as Contexts;\n}\n","import { ErrorCode } from \"@openfeature/web-sdk\";\n\n/**\n * Map a native SDK error (or unknown throw) to an OpenFeature ErrorCode.\n */\nexport function toErrorCode(err: unknown): ErrorCode {\n const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();\n\n if (msg.includes(\"flag not found\") || msg.includes(\"not found\")) {\n return ErrorCode.FLAG_NOT_FOUND;\n }\n if (msg.includes(\"type mismatch\") || msg.includes(\"type_mismatch\")) {\n return ErrorCode.TYPE_MISMATCH;\n }\n if (\n msg.includes(\"not initialized\") ||\n msg.includes(\"provider_not_ready\") ||\n msg.includes(\"call init()\") ||\n msg.includes(\"not ready\")\n ) {\n return ErrorCode.PROVIDER_NOT_READY;\n }\n return ErrorCode.GENERAL;\n}\n"],"mappings":"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","names":["ErrorCode","dotIdx","ErrorCode"]}
1
+ {"version":3,"sources":["../src/provider.ts","../src/context.ts","../src/errors.ts"],"sourcesContent":["import {\n ErrorCode,\n OpenFeatureEventEmitter,\n Provider,\n ProviderEvents,\n ResolutionDetails,\n StandardResolutionReasons,\n} from \"@openfeature/web-sdk\";\nimport type { EvaluationContext, JsonValue, ResolutionReason } from \"@openfeature/web-sdk\";\nimport { Quonfig } from \"@quonfig/javascript\";\nimport type { EvaluationDetails, OnInitFailure } from \"@quonfig/javascript\";\n\nimport { mapContext } from \"./context\";\nimport { toErrorCode } from \"./errors\";\n\nexport interface QuonfigWebProviderOptions {\n sdkKey: string;\n /** Which Quonfig context property the OpenFeature targetingKey maps to. Default: \"user.id\" */\n targetingKeyMapping?: string;\n /** Override the Quonfig API base URL. */\n apiUrl?: string;\n /** Request timeout in ms. */\n timeout?: number;\n /**\n * What `initialize()` does when no Quonfig config is available by\n * `initTimeout`. Passed to `@quonfig/javascript`'s `init()`; default `\"fail\"`.\n * A cached last-known-good copy counts as config in every mode.\n *\n * - `\"fail\"`: `initialize()` rejects, so the provider status is ERROR. The\n * client keeps retrying and the provider emits PROVIDER_READY on heal.\n * - `\"wait\"`: `initialize()` stays pending (status NOT_READY) until config arrives.\n * - `\"continue\"`: `initialize()` resolves, then the provider emits\n * PROVIDER_STALE (serving a cached copy) or PROVIDER_ERROR (serving code\n * defaults), and PROVIDER_READY on heal.\n */\n onInitFailure?: OnInitFailure;\n /** Overall init deadline in ms before `onInitFailure` applies (SDK default 5000). */\n initTimeout?: number;\n}\n\ntype ReportedStatus = \"READY\" | \"STALE\" | \"ERROR\";\n\nexport class QuonfigWebProvider implements Provider {\n readonly metadata = { name: \"quonfig-web\" } as const;\n readonly runsOn = \"client\" as const;\n hooks = [];\n readonly events = new OpenFeatureEventEmitter();\n\n private client: Quonfig;\n private readonly targetingKeyMapping: string;\n private readonly sdkKey: string;\n private readonly apiUrl: string | undefined;\n private readonly timeout: number | undefined;\n private readonly onInitFailure: OnInitFailure | undefined;\n private readonly initTimeout: number | undefined;\n // The status OpenFeature last saw from this provider; undefined until\n // initialize() settles (OpenFeature owns the status before that).\n private reported: ReportedStatus | undefined;\n private unsubscribe: (() => void) | undefined;\n\n constructor(options: QuonfigWebProviderOptions) {\n this.sdkKey = options.sdkKey;\n this.targetingKeyMapping = options.targetingKeyMapping ?? \"user.id\";\n this.apiUrl = options.apiUrl;\n this.timeout = options.timeout;\n this.onInitFailure = options.onInitFailure;\n this.initTimeout = options.initTimeout;\n this.client = new Quonfig();\n }\n\n async initialize(context?: EvaluationContext): Promise<void> {\n const nativeCtx = context ? mapContext(context, this.targetingKeyMapping) : { \"\": {} };\n\n this.reported = undefined;\n // The SDK notifies subscribers when it heals (or falls back to a cached\n // copy); syncStatus turns that into the matching provider event.\n this.unsubscribe ??= this.client.subscribe(() => this.syncStatus());\n\n try {\n await this.client.init({\n sdkKey: this.sdkKey,\n context: nativeCtx,\n ...(this.apiUrl !== undefined && { apiUrl: this.apiUrl }),\n ...(this.timeout !== undefined && { timeout: this.timeout }),\n ...(this.onInitFailure !== undefined && { onInitFailure: this.onInitFailure }),\n ...(this.initTimeout !== undefined && { initTimeout: this.initTimeout }),\n });\n } catch (err) {\n // onInitFailure \"fail\": OpenFeature marks the provider ERROR from this\n // rejection. The client stays alive and keeps retrying in the\n // background; syncStatus emits PROVIDER_READY when it heals.\n this.reported = \"ERROR\";\n // Catch a heal that landed before OpenFeature applied ERROR.\n setTimeout(() => this.syncStatus(), 0);\n throw err;\n }\n\n // OpenFeature marks the provider READY once this resolves. Under\n // \"continue\" (or a cached copy) the client is not live yet, so report\n // STALE or ERROR right after OpenFeature has applied READY.\n this.reported = \"READY\";\n if (this.currentStatus() !== \"READY\") setTimeout(() => this.syncStatus(), 0);\n }\n\n /** Called by OpenFeature on `OpenFeature.setContext()`; switches the client to the new context. */\n async onContextChange(_oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void> {\n const nativeCtx = mapContext(newCtx, this.targetingKeyMapping);\n await this.client.updateContext(nativeCtx);\n }\n\n /**\n * @deprecated Use `onContextChange`, the hook OpenFeature calls. Before 2.0.0\n * the provider implemented only this name, so `OpenFeature.setContext()` never\n * reached the client. Kept so direct callers keep working.\n */\n async onContextChanged(oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void> {\n return this.onContextChange(oldCtx, newCtx);\n }\n\n async shutdown(): Promise<void> {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n await this.client.close();\n }\n\n resolveBooleanEvaluation(\n flagKey: string,\n defaultValue: boolean,\n _context?: EvaluationContext\n ): ResolutionDetails<boolean> {\n return this._resolve(flagKey, defaultValue, \"boolean\");\n }\n\n resolveStringEvaluation(\n flagKey: string,\n defaultValue: string,\n _context?: EvaluationContext\n ): ResolutionDetails<string> {\n return this._resolve(flagKey, defaultValue, \"string\");\n }\n\n resolveNumberEvaluation(\n flagKey: string,\n defaultValue: number,\n _context?: EvaluationContext\n ): ResolutionDetails<number> {\n return this._resolve(flagKey, defaultValue, \"number\");\n }\n\n resolveObjectEvaluation<T extends JsonValue = JsonValue>(\n flagKey: string,\n defaultValue: T,\n _context?: EvaluationContext\n ): ResolutionDetails<T> {\n return this._resolve(flagKey, defaultValue, \"object\");\n }\n\n /** Escape hatch: access the underlying Quonfig client directly. */\n getClient(): Quonfig {\n return this.client;\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n /** What the client is serving: live config, a cached copy, or nothing (defaults). */\n private currentStatus(): ReportedStatus {\n if (this.client.stale) return \"STALE\";\n return this.client.isReady() ? \"READY\" : \"ERROR\";\n }\n\n /** Emit the provider event for a status change (design doc B.5). */\n private syncStatus(): void {\n if (this.reported === undefined) return;\n const next = this.currentStatus();\n if (next === this.reported) return;\n this.reported = next;\n if (next === \"READY\") {\n this.events.emit(ProviderEvents.Ready);\n } else if (next === \"STALE\") {\n this.events.emit(ProviderEvents.Stale, {\n message: \"Quonfig: serving a cached copy of config; delivery not reachable yet\",\n });\n } else {\n this.events.emit(ProviderEvents.Error, {\n message: \"Quonfig: no config loaded; serving code defaults until delivery is reachable\",\n errorCode: ErrorCode.PROVIDER_NOT_READY,\n });\n }\n }\n\n private _resolve<T>(\n flagKey: string,\n defaultValue: T,\n expectedType: \"boolean\" | \"string\" | \"number\" | \"object\"\n ): ResolutionDetails<T> {\n let details: EvaluationDetails;\n try {\n details = this.client.getDetails(flagKey);\n } catch (err) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: toErrorCode(err),\n variant: \"default\",\n flagMetadata: {},\n };\n }\n\n // Errors from the SDK side (FLAG_NOT_FOUND, GENERAL) — pass through. With\n // no config at all (init failed or \"continue\" on defaults) the outage code\n // is PROVIDER_NOT_READY.\n if (details.reason === \"ERROR\") {\n const noConfig = !this.client.loaded;\n let errorCode = noConfig ? ErrorCode.PROVIDER_NOT_READY : toOFErrorCode(details.errorCode);\n // The SDK reports a stored value that failed to decode as its declared\n // type as TYPE_MISMATCH, without knowing what type the caller asked\n // for. Read with the method for that declared type it is a data\n // problem: PARSE_ERROR (qfg-9xsl). Read with the method for another\n // type it stays TYPE_MISMATCH.\n if (\n errorCode === ErrorCode.TYPE_MISMATCH &&\n readsAs(this.client.configs[flagKey]?.type, expectedType)\n ) {\n errorCode = ErrorCode.PARSE_ERROR;\n }\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode,\n ...(details.errorMessage ? { errorMessage: details.errorMessage } : {}),\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n // Coerce the resolved value to the expected OF shape. A null result\n // signals provider-level TYPE_MISMATCH (the SDK has no requested-type\n // hint, so this happens here, not inside getDetails).\n const coerced = this._coerce<T>(details.value, expectedType, defaultValue);\n if (coerced === null) {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: ErrorCode.TYPE_MISMATCH,\n // OF spec convention: variant='default' on the error path; preserve\n // flagMetadata so consumers still see configId/configType.\n variant: \"default\",\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n return {\n value: coerced,\n reason: toOFReason(details.reason),\n variant: details.variant,\n flagMetadata: details.flagMetadata as ResolutionDetails<T>[\"flagMetadata\"],\n };\n }\n\n /**\n * Coerce a raw ConfigValue to the expected OF type.\n * Returns null if the type does not match (signals TYPE_MISMATCH).\n */\n private _coerce<T>(\n raw: unknown,\n expectedType: \"boolean\" | \"string\" | \"number\" | \"object\",\n defaultValue: T\n ): T | null {\n switch (expectedType) {\n case \"boolean\":\n if (typeof raw === \"boolean\") return raw as unknown as T;\n return null;\n\n case \"string\":\n if (typeof raw === \"string\") return raw as unknown as T;\n // Duration objects: return ISO 8601 string representation\n if (raw !== null && typeof raw === \"object\" && \"seconds\" in raw && \"ms\" in raw) {\n return this._durationToISO(raw as { seconds: number; ms: number }) as unknown as T;\n }\n return null;\n\n case \"number\":\n if (typeof raw === \"number\") return raw as unknown as T;\n return null;\n\n case \"object\":\n // Arrays (string_list) and plain objects both satisfy \"object\"\n if (Array.isArray(raw)) return raw as unknown as T;\n if (raw !== null && typeof raw === \"object\") return raw as unknown as T;\n return null;\n\n default:\n return null;\n }\n }\n\n /**\n * Convert a Quonfig Duration to its canonical ISO 8601 string in the Quonfig\n * duration grammar (e.g. \"P1DT6H2M1.5S\"): days kept as D, zero components\n * omitted, \"PT0S\" for zero. Works from the integer ms count, so the output\n * carries no float residue.\n */\n private _durationToISO(duration: { seconds: number; ms: number }): string {\n let rest = Math.round(duration.ms);\n const days = Math.floor(rest / 86400000);\n rest -= days * 86400000;\n const hours = Math.floor(rest / 3600000);\n rest -= hours * 3600000;\n const minutes = Math.floor(rest / 60000);\n rest -= minutes * 60000;\n const secs = Math.floor(rest / 1000);\n const millis = rest - secs * 1000;\n\n let time = \"\";\n if (hours > 0) time += `${hours}H`;\n if (minutes > 0) time += `${minutes}M`;\n if (secs > 0 || millis > 0) {\n const fraction = millis > 0 ? `.${String(millis).padStart(3, \"0\").replace(/0+$/, \"\")}` : \"\";\n time += `${secs}${fraction}S`;\n }\n\n if (days === 0 && time === \"\") return \"PT0S\";\n return `P${days > 0 ? `${days}D` : \"\"}${time ? `T${time}` : \"\"}`;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Map sdk-javascript's EvaluationReason onto OpenFeature's StandardResolutionReasons.\n * SPLIT is provider-defined; OF allows arbitrary strings, so the literal value works.\n */\nfunction toOFReason(reason: EvaluationDetails[\"reason\"]): ResolutionReason {\n switch (reason) {\n case \"STATIC\":\n return StandardResolutionReasons.STATIC;\n case \"TARGETING_MATCH\":\n return StandardResolutionReasons.TARGETING_MATCH;\n case \"SPLIT\":\n return StandardResolutionReasons.SPLIT;\n case \"DEFAULT\":\n return StandardResolutionReasons.DEFAULT;\n case \"STALE\":\n return StandardResolutionReasons.STALE;\n case \"ERROR\":\n default:\n return StandardResolutionReasons.ERROR;\n }\n}\n\n/**\n * True when a config of Quonfig value type `declaredType` is read with the OF\n * method for `expectedType`. OpenFeature has one number method (int and\n * double), reads durations as ISO 8601 strings, and reads string_list and\n * json through the object method.\n */\nfunction readsAs(\n declaredType: string | undefined,\n expectedType: \"boolean\" | \"string\" | \"number\" | \"object\"\n): boolean {\n switch (declaredType) {\n case \"bool\":\n return expectedType === \"boolean\";\n case \"string\":\n case \"log_level\":\n case \"duration\":\n return expectedType === \"string\";\n case \"int\":\n case \"double\":\n return expectedType === \"number\";\n case \"string_list\":\n case \"json\":\n return expectedType === \"object\";\n default:\n return false;\n }\n}\n\n/** Translate sdk-javascript's EvaluationErrorCode onto the OF ErrorCode enum. */\nfunction toOFErrorCode(code: EvaluationDetails[\"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/web-sdk\";\nimport type { Contexts } from \"@quonfig/javascript\";\n\n/**\n * Map an OpenFeature EvaluationContext to Quonfig's nested Contexts format.\n *\n * Rules:\n * - `targetingKey` maps to the property specified by `targetingKeyMapping` (default: \"user.id\")\n * - Keys with a dot are split on the FIRST dot: namespace = left side, key = right side\n * - Keys without a dot go into the default (\"\") namespace\n */\nexport function mapContext(\n ofContext: EvaluationContext,\n targetingKeyMapping = \"user.id\"\n): Contexts {\n const result: Record<string, Record<string, unknown>> = {};\n\n for (const [key, value] of Object.entries(ofContext)) {\n if (value === undefined) continue;\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] = value;\n continue;\n }\n\n const dotIdx = key.indexOf(\".\");\n if (dotIdx === -1) {\n result[\"\"] ??= {};\n result[\"\"][key] = value;\n } else {\n const ns = key.slice(0, dotIdx);\n const prop = key.slice(dotIdx + 1);\n result[ns] ??= {};\n result[ns][prop] = value;\n }\n }\n\n return result as Contexts;\n}\n","import { ErrorCode } from \"@openfeature/web-sdk\";\n\n/**\n * Map a native SDK error (or unknown throw) to an OpenFeature ErrorCode.\n */\nexport function toErrorCode(err: unknown): ErrorCode {\n const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();\n\n if (msg.includes(\"flag not found\") || msg.includes(\"not found\")) {\n return ErrorCode.FLAG_NOT_FOUND;\n }\n if (msg.includes(\"type mismatch\") || msg.includes(\"type_mismatch\")) {\n return ErrorCode.TYPE_MISMATCH;\n }\n if (\n msg.includes(\"not initialized\") ||\n msg.includes(\"provider_not_ready\") ||\n msg.includes(\"call init()\") ||\n msg.includes(\"not ready\")\n ) {\n return ErrorCode.PROVIDER_NOT_READY;\n }\n return ErrorCode.GENERAL;\n}\n"],"mappings":";AAAA;AAAA,EACE,aAAAA;AAAA,EACA;AAAA,EAEA;AAAA,EAEA;AAAA,OACK;AAEP,SAAS,eAAe;;;ACEjB,SAAS,WACd,WACA,sBAAsB,WACZ;AACV,QAAM,SAAkD,CAAC;AAEzD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,QAAI,UAAU,OAAW;AAEzB,QAAI,QAAQ,gBAAgB;AAC1B,YAAMC,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,kCAAe,CAAC;AAChB,aAAO,EAAE,EAAE,IAAI,IAAI;AACnB;AAAA,IACF;AAEA,UAAM,SAAS,IAAI,QAAQ,GAAG;AAC9B,QAAI,WAAW,IAAI;AACjB,kCAAe,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,kCAAe,CAAC;AAChB,aAAO,EAAE,EAAE,IAAI,IAAI;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;;;AC1CA,SAAS,iBAAiB;AAKnB,SAAS,YAAY,KAAyB;AACnD,QAAM,MAAM,eAAe,QAAQ,IAAI,QAAQ,YAAY,IAAI,OAAO,GAAG,EAAE,YAAY;AAEvF,MAAI,IAAI,SAAS,gBAAgB,KAAK,IAAI,SAAS,WAAW,GAAG;AAC/D,WAAO,UAAU;AAAA,EACnB;AACA,MAAI,IAAI,SAAS,eAAe,KAAK,IAAI,SAAS,eAAe,GAAG;AAClE,WAAO,UAAU;AAAA,EACnB;AACA,MACE,IAAI,SAAS,iBAAiB,KAC9B,IAAI,SAAS,oBAAoB,KACjC,IAAI,SAAS,aAAa,KAC1B,IAAI,SAAS,WAAW,GACxB;AACA,WAAO,UAAU;AAAA,EACnB;AACA,SAAO,UAAU;AACnB;;;AFmBO,IAAM,qBAAN,MAA6C;AAAA,EAkBlD,YAAY,SAAoC;AAjBhD,SAAS,WAAW,EAAE,MAAM,cAAc;AAC1C,SAAS,SAAS;AAClB,iBAAQ,CAAC;AACT,SAAS,SAAS,IAAI,wBAAwB;AAe5C,SAAK,SAAS,QAAQ;AACtB,SAAK,sBAAsB,QAAQ,uBAAuB;AAC1D,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ;AACvB,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,cAAc,QAAQ;AAC3B,SAAK,SAAS,IAAI,QAAQ;AAAA,EAC5B;AAAA,EAEA,MAAM,WAAW,SAA4C;AAC3D,UAAM,YAAY,UAAU,WAAW,SAAS,KAAK,mBAAmB,IAAI,EAAE,IAAI,CAAC,EAAE;AAErF,SAAK,WAAW;AAGhB,SAAK,gBAAL,KAAK,cAAgB,KAAK,OAAO,UAAU,MAAM,KAAK,WAAW,CAAC;AAElE,QAAI;AACF,YAAM,KAAK,OAAO,KAAK;AAAA,QACrB,QAAQ,KAAK;AAAA,QACb,SAAS;AAAA,QACT,GAAI,KAAK,WAAW,UAAa,EAAE,QAAQ,KAAK,OAAO;AAAA,QACvD,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC1D,GAAI,KAAK,kBAAkB,UAAa,EAAE,eAAe,KAAK,cAAc;AAAA,QAC5E,GAAI,KAAK,gBAAgB,UAAa,EAAE,aAAa,KAAK,YAAY;AAAA,MACxE,CAAC;AAAA,IACH,SAAS,KAAK;AAIZ,WAAK,WAAW;AAEhB,iBAAW,MAAM,KAAK,WAAW,GAAG,CAAC;AACrC,YAAM;AAAA,IACR;AAKA,SAAK,WAAW;AAChB,QAAI,KAAK,cAAc,MAAM,QAAS,YAAW,MAAM,KAAK,WAAW,GAAG,CAAC;AAAA,EAC7E;AAAA;AAAA,EAGA,MAAM,gBAAgB,SAA4B,QAA0C;AAC1F,UAAM,YAAY,WAAW,QAAQ,KAAK,mBAAmB;AAC7D,UAAM,KAAK,OAAO,cAAc,SAAS;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,iBAAiB,QAA2B,QAA0C;AAC1F,WAAO,KAAK,gBAAgB,QAAQ,MAAM;AAAA,EAC5C;AAAA,EAEA,MAAM,WAA0B;AAC9B,SAAK,cAAc;AACnB,SAAK,cAAc;AACnB,UAAM,KAAK,OAAO,MAAM;AAAA,EAC1B;AAAA,EAEA,yBACE,SACA,cACA,UAC4B;AAC5B,WAAO,KAAK,SAAS,SAAS,cAAc,SAAS;AAAA,EACvD;AAAA,EAEA,wBACE,SACA,cACA,UAC2B;AAC3B,WAAO,KAAK,SAAS,SAAS,cAAc,QAAQ;AAAA,EACtD;AAAA,EAEA,wBACE,SACA,cACA,UAC2B;AAC3B,WAAO,KAAK,SAAS,SAAS,cAAc,QAAQ;AAAA,EACtD;AAAA,EAEA,wBACE,SACA,cACA,UACsB;AACtB,WAAO,KAAK,SAAS,SAAS,cAAc,QAAQ;AAAA,EACtD;AAAA;AAAA,EAGA,YAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,gBAAgC;AACtC,QAAI,KAAK,OAAO,MAAO,QAAO;AAC9B,WAAO,KAAK,OAAO,QAAQ,IAAI,UAAU;AAAA,EAC3C;AAAA;AAAA,EAGQ,aAAmB;AACzB,QAAI,KAAK,aAAa,OAAW;AACjC,UAAM,OAAO,KAAK,cAAc;AAChC,QAAI,SAAS,KAAK,SAAU;AAC5B,SAAK,WAAW;AAChB,QAAI,SAAS,SAAS;AACpB,WAAK,OAAO,KAAK,eAAe,KAAK;AAAA,IACvC,WAAW,SAAS,SAAS;AAC3B,WAAK,OAAO,KAAK,eAAe,OAAO;AAAA,QACrC,SAAS;AAAA,MACX,CAAC;AAAA,IACH,OAAO;AACL,WAAK,OAAO,KAAK,eAAe,OAAO;AAAA,QACrC,SAAS;AAAA,QACT,WAAWC,WAAU;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,SACN,SACA,cACA,cACsB;AACtB,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,OAAO,WAAW,OAAO;AAAA,IAC1C,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ,0BAA0B;AAAA,QAClC,WAAW,YAAY,GAAG;AAAA,QAC1B,SAAS;AAAA,QACT,cAAc,CAAC;AAAA,MACjB;AAAA,IACF;AAKA,QAAI,QAAQ,WAAW,SAAS;AAC9B,YAAM,WAAW,CAAC,KAAK,OAAO;AAC9B,UAAI,YAAY,WAAWA,WAAU,qBAAqB,cAAc,QAAQ,SAAS;AAMzF,UACE,cAAcA,WAAU,iBACxB,QAAQ,KAAK,OAAO,QAAQ,OAAO,GAAG,MAAM,YAAY,GACxD;AACA,oBAAYA,WAAU;AAAA,MACxB;AACA,aAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ,0BAA0B;AAAA,QAClC;AAAA,QACA,GAAI,QAAQ,eAAe,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;AAAA,QACrE,SAAS,QAAQ;AAAA,QACjB,cAAc,QAAQ;AAAA,MACxB;AAAA,IACF;AAKA,UAAM,UAAU,KAAK,QAAW,QAAQ,OAAO,cAAc,YAAY;AACzE,QAAI,YAAY,MAAM;AACpB,aAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ,0BAA0B;AAAA,QAClC,WAAWA,WAAU;AAAA;AAAA;AAAA,QAGrB,SAAS;AAAA,QACT,cAAc,QAAQ;AAAA,MACxB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,QAAQ,WAAW,QAAQ,MAAM;AAAA,MACjC,SAAS,QAAQ;AAAA,MACjB,cAAc,QAAQ;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,QACN,KACA,cACA,cACU;AACV,YAAQ,cAAc;AAAA,MACpB,KAAK;AACH,YAAI,OAAO,QAAQ,UAAW,QAAO;AACrC,eAAO;AAAA,MAET,KAAK;AACH,YAAI,OAAO,QAAQ,SAAU,QAAO;AAEpC,YAAI,QAAQ,QAAQ,OAAO,QAAQ,YAAY,aAAa,OAAO,QAAQ,KAAK;AAC9E,iBAAO,KAAK,eAAe,GAAsC;AAAA,QACnE;AACA,eAAO;AAAA,MAET,KAAK;AACH,YAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,eAAO;AAAA,MAET,KAAK;AAEH,YAAI,MAAM,QAAQ,GAAG,EAAG,QAAO;AAC/B,YAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,eAAO;AAAA,MAET;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,eAAe,UAAmD;AACxE,QAAI,OAAO,KAAK,MAAM,SAAS,EAAE;AACjC,UAAM,OAAO,KAAK,MAAM,OAAO,KAAQ;AACvC,YAAQ,OAAO;AACf,UAAM,QAAQ,KAAK,MAAM,OAAO,IAAO;AACvC,YAAQ,QAAQ;AAChB,UAAM,UAAU,KAAK,MAAM,OAAO,GAAK;AACvC,YAAQ,UAAU;AAClB,UAAM,OAAO,KAAK,MAAM,OAAO,GAAI;AACnC,UAAM,SAAS,OAAO,OAAO;AAE7B,QAAI,OAAO;AACX,QAAI,QAAQ,EAAG,SAAQ,GAAG,KAAK;AAC/B,QAAI,UAAU,EAAG,SAAQ,GAAG,OAAO;AACnC,QAAI,OAAO,KAAK,SAAS,GAAG;AAC1B,YAAM,WAAW,SAAS,IAAI,IAAI,OAAO,MAAM,EAAE,SAAS,GAAG,GAAG,EAAE,QAAQ,OAAO,EAAE,CAAC,KAAK;AACzF,cAAQ,GAAG,IAAI,GAAG,QAAQ;AAAA,IAC5B;AAEA,QAAI,SAAS,KAAK,SAAS,GAAI,QAAO;AACtC,WAAO,IAAI,OAAO,IAAI,GAAG,IAAI,MAAM,EAAE,GAAG,OAAO,IAAI,IAAI,KAAK,EAAE;AAAA,EAChE;AACF;AAUA,SAAS,WAAW,QAAuD;AACzE,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,0BAA0B;AAAA,IACnC,KAAK;AACH,aAAO,0BAA0B;AAAA,IACnC,KAAK;AACH,aAAO,0BAA0B;AAAA,IACnC,KAAK;AACH,aAAO,0BAA0B;AAAA,IACnC,KAAK;AACH,aAAO,0BAA0B;AAAA,IACnC,KAAK;AAAA,IACL;AACE,aAAO,0BAA0B;AAAA,EACrC;AACF;AAQA,SAAS,QACP,cACA,cACS;AACT,UAAQ,cAAc;AAAA,IACpB,KAAK;AACH,aAAO,iBAAiB;AAAA,IAC1B,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,iBAAiB;AAAA,IAC1B,KAAK;AAAA,IACL,KAAK;AACH,aAAO,iBAAiB;AAAA,IAC1B,KAAK;AAAA,IACL,KAAK;AACH,aAAO,iBAAiB;AAAA,IAC1B;AACE,aAAO;AAAA,EACX;AACF;AAGA,SAAS,cAAc,MAAiD;AACtE,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAOA,WAAU;AAAA,IACnB,KAAK;AACH,aAAOA,WAAU;AAAA,IACnB,KAAK;AAAA,IACL;AACE,aAAOA,WAAU;AAAA,EACrB;AACF;","names":["ErrorCode","dotIdx","ErrorCode"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quonfig/openfeature-web",
3
- "version": "1.1.0",
3
+ "version": "2.1.0",
4
4
  "description": "OpenFeature provider for Quonfig — Web/Browser (also works with @openfeature/react-sdk)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -26,11 +26,11 @@
26
26
  "version": "prettier --write CHANGELOG.md README.md && git add CHANGELOG.md README.md"
27
27
  },
28
28
  "peerDependencies": {
29
- "@quonfig/javascript": ">=1.8.0",
29
+ "@quonfig/javascript": ">=2.0.0",
30
30
  "@openfeature/web-sdk": ">=1.0.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@quonfig/javascript": "^1.8.0",
33
+ "@quonfig/javascript": "^2.0.0",
34
34
  "@openfeature/web-sdk": "^1.0.0",
35
35
  "@types/node": "^26.6.4",
36
36
  "prettier": "^3.0.0",