@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 +27 -1
- package/dist/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/index.mjs.map
CHANGED
|
@@ -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": "
|
|
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": ">=
|
|
29
|
+
"@quonfig/javascript": ">=2.0.0",
|
|
30
30
|
"@openfeature/web-sdk": ">=1.0.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@quonfig/javascript": "^
|
|
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",
|