@quonfig/openfeature-web 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/index.d.mts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +50 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +51 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -72,9 +72,27 @@ const provider = new QuonfigWebProvider({
|
|
|
72
72
|
targetingKeyMapping: "user.id", // default; maps OpenFeature targetingKey
|
|
73
73
|
apiUrl: "https://custom.api.com", // optional — override API base URL
|
|
74
74
|
timeout: 5000, // optional — request timeout in ms
|
|
75
|
+
onInitFailure: "fail", // optional — "fail" (default) | "wait" | "continue"
|
|
76
|
+
initTimeout: 5000, // optional — ms to keep retrying before onInitFailure applies
|
|
75
77
|
});
|
|
76
78
|
```
|
|
77
79
|
|
|
80
|
+
### Init failure and provider status
|
|
81
|
+
|
|
82
|
+
When no config can be loaded by `initTimeout`, `onInitFailure` decides what `initialize()` does. A
|
|
83
|
+
cached last-known-good copy in localStorage counts as config in every mode.
|
|
84
|
+
|
|
85
|
+
| `onInitFailure` | `initialize()` | Provider status | When delivery comes back |
|
|
86
|
+
| ---------------------- | ------------------------------- | ----------------- | ------------------------ |
|
|
87
|
+
| `"fail"` (default) | Rejects (`QuonfigInitError`) | ERROR | `PROVIDER_READY` |
|
|
88
|
+
| `"wait"` | Stays pending until config | NOT_READY | `initialize()` resolves |
|
|
89
|
+
| `"continue"` + cache | Resolves | STALE | `PROVIDER_READY` |
|
|
90
|
+
| `"continue"`, no cache | Resolves, then `PROVIDER_ERROR` | ERROR (non-fatal) | `PROVIDER_READY` |
|
|
91
|
+
|
|
92
|
+
The client keeps retrying in the background in every mode. Evaluations served from the cached copy
|
|
93
|
+
report reason `STALE`; with no config at all they return your default with error code
|
|
94
|
+
`PROVIDER_NOT_READY`.
|
|
95
|
+
|
|
78
96
|
## Context mapping
|
|
79
97
|
|
|
80
98
|
OpenFeature uses a flat context; Quonfig uses a namespace-nested context. The provider maps between
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Provider, OpenFeatureEventEmitter, EvaluationContext, ResolutionDetails, JsonValue, ErrorCode } from '@openfeature/web-sdk';
|
|
2
|
-
import { Quonfig, Contexts } from '@quonfig/javascript';
|
|
2
|
+
import { OnInitFailure, Quonfig, Contexts } from '@quonfig/javascript';
|
|
3
3
|
|
|
4
4
|
interface QuonfigWebProviderOptions {
|
|
5
5
|
sdkKey: string;
|
|
@@ -9,6 +9,21 @@ interface QuonfigWebProviderOptions {
|
|
|
9
9
|
apiUrl?: string;
|
|
10
10
|
/** Request timeout in ms. */
|
|
11
11
|
timeout?: number;
|
|
12
|
+
/**
|
|
13
|
+
* What `initialize()` does when no Quonfig config is available by
|
|
14
|
+
* `initTimeout`. Passed to `@quonfig/javascript`'s `init()`; default `"fail"`.
|
|
15
|
+
* A cached last-known-good copy counts as config in every mode.
|
|
16
|
+
*
|
|
17
|
+
* - `"fail"`: `initialize()` rejects, so the provider status is ERROR. The
|
|
18
|
+
* client keeps retrying and the provider emits PROVIDER_READY on heal.
|
|
19
|
+
* - `"wait"`: `initialize()` stays pending (status NOT_READY) until config arrives.
|
|
20
|
+
* - `"continue"`: `initialize()` resolves, then the provider emits
|
|
21
|
+
* PROVIDER_STALE (serving a cached copy) or PROVIDER_ERROR (serving code
|
|
22
|
+
* defaults), and PROVIDER_READY on heal.
|
|
23
|
+
*/
|
|
24
|
+
onInitFailure?: OnInitFailure;
|
|
25
|
+
/** Overall init deadline in ms before `onInitFailure` applies (SDK default 5000). */
|
|
26
|
+
initTimeout?: number;
|
|
12
27
|
}
|
|
13
28
|
declare class QuonfigWebProvider implements Provider {
|
|
14
29
|
readonly metadata: {
|
|
@@ -22,6 +37,10 @@ declare class QuonfigWebProvider implements Provider {
|
|
|
22
37
|
private readonly sdkKey;
|
|
23
38
|
private readonly apiUrl;
|
|
24
39
|
private readonly timeout;
|
|
40
|
+
private readonly onInitFailure;
|
|
41
|
+
private readonly initTimeout;
|
|
42
|
+
private reported;
|
|
43
|
+
private unsubscribe;
|
|
25
44
|
constructor(options: QuonfigWebProviderOptions);
|
|
26
45
|
initialize(context?: EvaluationContext): Promise<void>;
|
|
27
46
|
onContextChanged(_oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void>;
|
|
@@ -32,6 +51,10 @@ declare class QuonfigWebProvider implements Provider {
|
|
|
32
51
|
resolveObjectEvaluation<T extends JsonValue = JsonValue>(flagKey: string, defaultValue: T, _context?: EvaluationContext): ResolutionDetails<T>;
|
|
33
52
|
/** Escape hatch: access the underlying Quonfig client directly. */
|
|
34
53
|
getClient(): Quonfig;
|
|
54
|
+
/** What the client is serving: live config, a cached copy, or nothing (defaults). */
|
|
55
|
+
private currentStatus;
|
|
56
|
+
/** Emit the provider event for a status change (design doc B.5). */
|
|
57
|
+
private syncStatus;
|
|
35
58
|
private _resolve;
|
|
36
59
|
/**
|
|
37
60
|
* Coerce a raw ConfigValue to the expected OF type.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Provider, OpenFeatureEventEmitter, EvaluationContext, ResolutionDetails, JsonValue, ErrorCode } from '@openfeature/web-sdk';
|
|
2
|
-
import { Quonfig, Contexts } from '@quonfig/javascript';
|
|
2
|
+
import { OnInitFailure, Quonfig, Contexts } from '@quonfig/javascript';
|
|
3
3
|
|
|
4
4
|
interface QuonfigWebProviderOptions {
|
|
5
5
|
sdkKey: string;
|
|
@@ -9,6 +9,21 @@ interface QuonfigWebProviderOptions {
|
|
|
9
9
|
apiUrl?: string;
|
|
10
10
|
/** Request timeout in ms. */
|
|
11
11
|
timeout?: number;
|
|
12
|
+
/**
|
|
13
|
+
* What `initialize()` does when no Quonfig config is available by
|
|
14
|
+
* `initTimeout`. Passed to `@quonfig/javascript`'s `init()`; default `"fail"`.
|
|
15
|
+
* A cached last-known-good copy counts as config in every mode.
|
|
16
|
+
*
|
|
17
|
+
* - `"fail"`: `initialize()` rejects, so the provider status is ERROR. The
|
|
18
|
+
* client keeps retrying and the provider emits PROVIDER_READY on heal.
|
|
19
|
+
* - `"wait"`: `initialize()` stays pending (status NOT_READY) until config arrives.
|
|
20
|
+
* - `"continue"`: `initialize()` resolves, then the provider emits
|
|
21
|
+
* PROVIDER_STALE (serving a cached copy) or PROVIDER_ERROR (serving code
|
|
22
|
+
* defaults), and PROVIDER_READY on heal.
|
|
23
|
+
*/
|
|
24
|
+
onInitFailure?: OnInitFailure;
|
|
25
|
+
/** Overall init deadline in ms before `onInitFailure` applies (SDK default 5000). */
|
|
26
|
+
initTimeout?: number;
|
|
12
27
|
}
|
|
13
28
|
declare class QuonfigWebProvider implements Provider {
|
|
14
29
|
readonly metadata: {
|
|
@@ -22,6 +37,10 @@ declare class QuonfigWebProvider implements Provider {
|
|
|
22
37
|
private readonly sdkKey;
|
|
23
38
|
private readonly apiUrl;
|
|
24
39
|
private readonly timeout;
|
|
40
|
+
private readonly onInitFailure;
|
|
41
|
+
private readonly initTimeout;
|
|
42
|
+
private reported;
|
|
43
|
+
private unsubscribe;
|
|
25
44
|
constructor(options: QuonfigWebProviderOptions);
|
|
26
45
|
initialize(context?: EvaluationContext): Promise<void>;
|
|
27
46
|
onContextChanged(_oldCtx: EvaluationContext, newCtx: EvaluationContext): Promise<void>;
|
|
@@ -32,6 +51,10 @@ declare class QuonfigWebProvider implements Provider {
|
|
|
32
51
|
resolveObjectEvaluation<T extends JsonValue = JsonValue>(flagKey: string, defaultValue: T, _context?: EvaluationContext): ResolutionDetails<T>;
|
|
33
52
|
/** Escape hatch: access the underlying Quonfig client directly. */
|
|
34
53
|
getClient(): Quonfig;
|
|
54
|
+
/** What the client is serving: live config, a cached copy, or nothing (defaults). */
|
|
55
|
+
private currentStatus;
|
|
56
|
+
/** Emit the provider event for a status change (design doc B.5). */
|
|
57
|
+
private syncStatus;
|
|
35
58
|
private _resolve;
|
|
36
59
|
/**
|
|
37
60
|
* Coerce a raw ConfigValue to the expected OF type.
|
package/dist/index.js
CHANGED
|
@@ -84,22 +84,38 @@ var QuonfigWebProvider = class {
|
|
|
84
84
|
this.targetingKeyMapping = options.targetingKeyMapping ?? "user.id";
|
|
85
85
|
this.apiUrl = options.apiUrl;
|
|
86
86
|
this.timeout = options.timeout;
|
|
87
|
+
this.onInitFailure = options.onInitFailure;
|
|
88
|
+
this.initTimeout = options.initTimeout;
|
|
87
89
|
this.client = new import_javascript.Quonfig();
|
|
88
90
|
}
|
|
89
91
|
async initialize(context) {
|
|
90
92
|
const nativeCtx = context ? mapContext(context, this.targetingKeyMapping) : { "": {} };
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
this.reported = void 0;
|
|
94
|
+
this.unsubscribe ?? (this.unsubscribe = this.client.subscribe(() => this.syncStatus()));
|
|
95
|
+
try {
|
|
96
|
+
await this.client.init({
|
|
97
|
+
sdkKey: this.sdkKey,
|
|
98
|
+
context: nativeCtx,
|
|
99
|
+
...this.apiUrl !== void 0 && { apiUrl: this.apiUrl },
|
|
100
|
+
...this.timeout !== void 0 && { timeout: this.timeout },
|
|
101
|
+
...this.onInitFailure !== void 0 && { onInitFailure: this.onInitFailure },
|
|
102
|
+
...this.initTimeout !== void 0 && { initTimeout: this.initTimeout }
|
|
103
|
+
});
|
|
104
|
+
} catch (err) {
|
|
105
|
+
this.reported = "ERROR";
|
|
106
|
+
setTimeout(() => this.syncStatus(), 0);
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
this.reported = "READY";
|
|
110
|
+
if (this.currentStatus() !== "READY") setTimeout(() => this.syncStatus(), 0);
|
|
97
111
|
}
|
|
98
112
|
async onContextChanged(_oldCtx, newCtx) {
|
|
99
113
|
const nativeCtx = mapContext(newCtx, this.targetingKeyMapping);
|
|
100
114
|
await this.client.updateContext(nativeCtx);
|
|
101
115
|
}
|
|
102
116
|
async shutdown() {
|
|
117
|
+
this.unsubscribe?.();
|
|
118
|
+
this.unsubscribe = void 0;
|
|
103
119
|
await this.client.close();
|
|
104
120
|
}
|
|
105
121
|
resolveBooleanEvaluation(flagKey, defaultValue, _context) {
|
|
@@ -121,6 +137,30 @@ var QuonfigWebProvider = class {
|
|
|
121
137
|
// ---------------------------------------------------------------------------
|
|
122
138
|
// Private helpers
|
|
123
139
|
// ---------------------------------------------------------------------------
|
|
140
|
+
/** What the client is serving: live config, a cached copy, or nothing (defaults). */
|
|
141
|
+
currentStatus() {
|
|
142
|
+
if (this.client.stale) return "STALE";
|
|
143
|
+
return this.client.isReady() ? "READY" : "ERROR";
|
|
144
|
+
}
|
|
145
|
+
/** Emit the provider event for a status change (design doc B.5). */
|
|
146
|
+
syncStatus() {
|
|
147
|
+
if (this.reported === void 0) return;
|
|
148
|
+
const next = this.currentStatus();
|
|
149
|
+
if (next === this.reported) return;
|
|
150
|
+
this.reported = next;
|
|
151
|
+
if (next === "READY") {
|
|
152
|
+
this.events.emit(import_web_sdk2.ProviderEvents.Ready);
|
|
153
|
+
} else if (next === "STALE") {
|
|
154
|
+
this.events.emit(import_web_sdk2.ProviderEvents.Stale, {
|
|
155
|
+
message: "Quonfig: serving a cached copy of config; delivery not reachable yet"
|
|
156
|
+
});
|
|
157
|
+
} else {
|
|
158
|
+
this.events.emit(import_web_sdk2.ProviderEvents.Error, {
|
|
159
|
+
message: "Quonfig: no config loaded; serving code defaults until delivery is reachable",
|
|
160
|
+
errorCode: import_web_sdk2.ErrorCode.PROVIDER_NOT_READY
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
124
164
|
_resolve(flagKey, defaultValue, expectedType) {
|
|
125
165
|
let details;
|
|
126
166
|
try {
|
|
@@ -135,10 +175,11 @@ var QuonfigWebProvider = class {
|
|
|
135
175
|
};
|
|
136
176
|
}
|
|
137
177
|
if (details.reason === "ERROR") {
|
|
178
|
+
const noConfig = !this.client.loaded;
|
|
138
179
|
return {
|
|
139
180
|
value: defaultValue,
|
|
140
181
|
reason: import_web_sdk2.StandardResolutionReasons.ERROR,
|
|
141
|
-
errorCode: toOFErrorCode(details.errorCode),
|
|
182
|
+
errorCode: noConfig ? import_web_sdk2.ErrorCode.PROVIDER_NOT_READY : toOFErrorCode(details.errorCode),
|
|
142
183
|
...details.errorMessage ? { errorMessage: details.errorMessage } : {},
|
|
143
184
|
variant: details.variant,
|
|
144
185
|
flagMetadata: details.flagMetadata
|
|
@@ -226,6 +267,8 @@ function toOFReason(reason) {
|
|
|
226
267
|
return import_web_sdk2.StandardResolutionReasons.SPLIT;
|
|
227
268
|
case "DEFAULT":
|
|
228
269
|
return import_web_sdk2.StandardResolutionReasons.DEFAULT;
|
|
270
|
+
case "STALE":
|
|
271
|
+
return import_web_sdk2.StandardResolutionReasons.STALE;
|
|
229
272
|
case "ERROR":
|
|
230
273
|
default:
|
|
231
274
|
return import_web_sdk2.StandardResolutionReasons.ERROR;
|
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 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 } 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\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\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.client = new Quonfig();\n }\n\n async initialize(context?: EvaluationContext): Promise<void> {\n const nativeCtx = context ? mapContext(context, this.targetingKeyMapping) : { \"\": {} };\n\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 });\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 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 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.\n if (details.reason === \"ERROR\") {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: 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 \"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 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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ErrorCode as ErrorCode2,
|
|
4
4
|
OpenFeatureEventEmitter,
|
|
5
|
+
ProviderEvents,
|
|
5
6
|
StandardResolutionReasons
|
|
6
7
|
} from "@openfeature/web-sdk";
|
|
7
8
|
import { Quonfig } from "@quonfig/javascript";
|
|
@@ -60,22 +61,38 @@ var QuonfigWebProvider = class {
|
|
|
60
61
|
this.targetingKeyMapping = options.targetingKeyMapping ?? "user.id";
|
|
61
62
|
this.apiUrl = options.apiUrl;
|
|
62
63
|
this.timeout = options.timeout;
|
|
64
|
+
this.onInitFailure = options.onInitFailure;
|
|
65
|
+
this.initTimeout = options.initTimeout;
|
|
63
66
|
this.client = new Quonfig();
|
|
64
67
|
}
|
|
65
68
|
async initialize(context) {
|
|
66
69
|
const nativeCtx = context ? mapContext(context, this.targetingKeyMapping) : { "": {} };
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
this.reported = void 0;
|
|
71
|
+
this.unsubscribe ?? (this.unsubscribe = this.client.subscribe(() => this.syncStatus()));
|
|
72
|
+
try {
|
|
73
|
+
await this.client.init({
|
|
74
|
+
sdkKey: this.sdkKey,
|
|
75
|
+
context: nativeCtx,
|
|
76
|
+
...this.apiUrl !== void 0 && { apiUrl: this.apiUrl },
|
|
77
|
+
...this.timeout !== void 0 && { timeout: this.timeout },
|
|
78
|
+
...this.onInitFailure !== void 0 && { onInitFailure: this.onInitFailure },
|
|
79
|
+
...this.initTimeout !== void 0 && { initTimeout: this.initTimeout }
|
|
80
|
+
});
|
|
81
|
+
} catch (err) {
|
|
82
|
+
this.reported = "ERROR";
|
|
83
|
+
setTimeout(() => this.syncStatus(), 0);
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
this.reported = "READY";
|
|
87
|
+
if (this.currentStatus() !== "READY") setTimeout(() => this.syncStatus(), 0);
|
|
73
88
|
}
|
|
74
89
|
async onContextChanged(_oldCtx, newCtx) {
|
|
75
90
|
const nativeCtx = mapContext(newCtx, this.targetingKeyMapping);
|
|
76
91
|
await this.client.updateContext(nativeCtx);
|
|
77
92
|
}
|
|
78
93
|
async shutdown() {
|
|
94
|
+
this.unsubscribe?.();
|
|
95
|
+
this.unsubscribe = void 0;
|
|
79
96
|
await this.client.close();
|
|
80
97
|
}
|
|
81
98
|
resolveBooleanEvaluation(flagKey, defaultValue, _context) {
|
|
@@ -97,6 +114,30 @@ var QuonfigWebProvider = class {
|
|
|
97
114
|
// ---------------------------------------------------------------------------
|
|
98
115
|
// Private helpers
|
|
99
116
|
// ---------------------------------------------------------------------------
|
|
117
|
+
/** What the client is serving: live config, a cached copy, or nothing (defaults). */
|
|
118
|
+
currentStatus() {
|
|
119
|
+
if (this.client.stale) return "STALE";
|
|
120
|
+
return this.client.isReady() ? "READY" : "ERROR";
|
|
121
|
+
}
|
|
122
|
+
/** Emit the provider event for a status change (design doc B.5). */
|
|
123
|
+
syncStatus() {
|
|
124
|
+
if (this.reported === void 0) return;
|
|
125
|
+
const next = this.currentStatus();
|
|
126
|
+
if (next === this.reported) return;
|
|
127
|
+
this.reported = next;
|
|
128
|
+
if (next === "READY") {
|
|
129
|
+
this.events.emit(ProviderEvents.Ready);
|
|
130
|
+
} else if (next === "STALE") {
|
|
131
|
+
this.events.emit(ProviderEvents.Stale, {
|
|
132
|
+
message: "Quonfig: serving a cached copy of config; delivery not reachable yet"
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
this.events.emit(ProviderEvents.Error, {
|
|
136
|
+
message: "Quonfig: no config loaded; serving code defaults until delivery is reachable",
|
|
137
|
+
errorCode: ErrorCode2.PROVIDER_NOT_READY
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
100
141
|
_resolve(flagKey, defaultValue, expectedType) {
|
|
101
142
|
let details;
|
|
102
143
|
try {
|
|
@@ -111,10 +152,11 @@ var QuonfigWebProvider = class {
|
|
|
111
152
|
};
|
|
112
153
|
}
|
|
113
154
|
if (details.reason === "ERROR") {
|
|
155
|
+
const noConfig = !this.client.loaded;
|
|
114
156
|
return {
|
|
115
157
|
value: defaultValue,
|
|
116
158
|
reason: StandardResolutionReasons.ERROR,
|
|
117
|
-
errorCode: toOFErrorCode(details.errorCode),
|
|
159
|
+
errorCode: noConfig ? ErrorCode2.PROVIDER_NOT_READY : toOFErrorCode(details.errorCode),
|
|
118
160
|
...details.errorMessage ? { errorMessage: details.errorMessage } : {},
|
|
119
161
|
variant: details.variant,
|
|
120
162
|
flagMetadata: details.flagMetadata
|
|
@@ -202,6 +244,8 @@ function toOFReason(reason) {
|
|
|
202
244
|
return StandardResolutionReasons.SPLIT;
|
|
203
245
|
case "DEFAULT":
|
|
204
246
|
return StandardResolutionReasons.DEFAULT;
|
|
247
|
+
case "STALE":
|
|
248
|
+
return StandardResolutionReasons.STALE;
|
|
205
249
|
case "ERROR":
|
|
206
250
|
default:
|
|
207
251
|
return StandardResolutionReasons.ERROR;
|
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 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 } 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\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\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.client = new Quonfig();\n }\n\n async initialize(context?: EvaluationContext): Promise<void> {\n const nativeCtx = context ? mapContext(context, this.targetingKeyMapping) : { \"\": {} };\n\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 });\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 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 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.\n if (details.reason === \"ERROR\") {\n return {\n value: defaultValue,\n reason: StandardResolutionReasons.ERROR,\n errorCode: 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 \"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 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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quonfig/openfeature-web",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "OpenFeature provider for Quonfig — Web/Browser (also works with @openfeature/react-sdk)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"version": "prettier --write CHANGELOG.md README.md && git add CHANGELOG.md README.md"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@quonfig/javascript": ">=1.
|
|
29
|
+
"@quonfig/javascript": ">=1.8.0",
|
|
30
30
|
"@openfeature/web-sdk": ">=1.0.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@quonfig/javascript": "^1.
|
|
33
|
+
"@quonfig/javascript": "^1.8.0",
|
|
34
34
|
"@openfeature/web-sdk": "^1.0.0",
|
|
35
35
|
"@types/node": "^26.6.4",
|
|
36
36
|
"prettier": "^3.0.0",
|