@quonfig/openfeature-web 1.1.0 → 2.0.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,30 @@ 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. A
137
+ stored value that does not match its declared type (an `int` holding `"twelve"` or `3.5`) also
138
+ returns your default with `TYPE_MISMATCH`.
139
+
116
140
  ## What you lose vs. the native SDK
117
141
 
118
142
  The OpenFeature interface covers boolean, string, number, and object types. Some Quonfig-native
@@ -120,7 +144,9 @@ features require `provider.getClient()` (the escape hatch):
120
144
 
121
145
  1. **Log levels** (`shouldLog`, `logger`) — native SDK only
122
146
  2. **`string_list` configs** — access via `getObjectValue` and cast to `string[]`
123
- 3. **`duration` configs** — `getStringValue` returns an ISO 8601 string; parse client-side
147
+ 3. **`duration` configs** — `getStringValue` returns an ISO 8601 string; parse client-side, or read
148
+ it natively with `provider.getClient().getDuration(key, defaultValue)` (`{ seconds, ms }`).
149
+ `getNumberValue` on a duration returns your default with `TYPE_MISMATCH`.
124
150
  4. **`bytes` configs** — not accessible via OpenFeature
125
151
  5. **Bulk config access** (`extract()`, the `configs` getter, `get()`) — native SDK only
126
152
  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;
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 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"]}
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;
@@ -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 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quonfig/openfeature-web",
3
- "version": "1.1.0",
3
+ "version": "2.0.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",