@lunora/flags 1.0.0-alpha.40 → 1.0.0-alpha.42
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 +5 -1
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/createFlags-DqB9pDga.mjs +1 -0
- package/dist/providers/flagship.d.mts +12 -3
- package/dist/providers/flagship.d.ts +12 -3
- package/dist/providers/flagship.mjs +1 -1
- package/package.json +2 -2
- package/dist/packem_shared/createFlags-BuOXqhnI.mjs +0 -1
package/README.md
CHANGED
|
@@ -79,7 +79,11 @@ export const listPosts = query(async (ctx) => {
|
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
`ctx.flags` never throws: a missing flag, type mismatch, or provider error resolves
|
|
82
|
-
with the `defaultValue`.
|
|
82
|
+
with the `defaultValue`. It does not do so silently — a provider that fails to BIND
|
|
83
|
+
(a missing binding, an unset token, a throwing `initialize`) is logged through
|
|
84
|
+
`defineFlags({ logger })`, or on `console.error` when none is configured, since
|
|
85
|
+
otherwise a misconfigured deployment serves every flag at its checked-in default
|
|
86
|
+
with nothing to notice. The default `targetingKey` (from `identify`) is merged under
|
|
83
87
|
any per-call context, and identical evaluations within one request are memoized.
|
|
84
88
|
|
|
85
89
|
## Exports
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineFlags as o,isFlagsDefinition as r}from"./packem_shared/defineFlags-BZhoR_h7.mjs";import{createFlags as f}from"./packem_shared/createFlags-
|
|
1
|
+
import{defineFlags as o,isFlagsDefinition as r}from"./packem_shared/defineFlags-BZhoR_h7.mjs";import{createFlags as f}from"./packem_shared/createFlags-DqB9pDga.mjs";export{f as createFlags,o as defineFlags,r as isFlagsDefinition};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as v}from"@lunora/errors";import{OpenFeature as m,ErrorCode as F}from"@openfeature/server-sdk";const D=/["\\\u0000-\u001F\uD800-\uDFFF]/,N=e=>D.test(e)?JSON.stringify(e):`"${e}"`,y=e=>{if(e===void 0)return"null";if(typeof e=="bigint")throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");if(typeof e=="number"){if(Number.isNaN(e))return"nan";if(e===1/0)return"inf";if(e===-1/0)return"-inf";if(Object.is(e,-0))return"-0"}if(typeof e=="string")return N(e);if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e)){let s="[";for(let l=0;l<e.length;l++)l>0&&(s+=","),s+=y(e[l]);return s+"]"}const i=Object.getPrototypeOf(e);if(i!==null&&i!==Object.prototype){const s=e.constructor?.name??"value";throw new TypeError(`stableStringify: cannot use a ${s} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`)}const c=e,a=Object.keys(c).sort();let n="{",u=!0;for(const s of a){const l=c[s];l!==void 0&&(u?u=!1:n+=",",n+=N(s),n+=":",n+=y(l))}return n+"}"},j="lunora";let h=new WeakMap,d=0;const $={},A=e=>Object.keys(e).length===0?$:e,C=(e,i)=>{const c=`@lunora/flags: could not bind the OpenFeature provider — every flag read falls back to its caller-supplied default until this is fixed: ${i instanceof Error?i.message:String(i)}`;if(e){e.error(c);return}console.error(c)},M=(e,i,c)=>{const a=A(i);let n=h.get(e);n===void 0&&(n=new WeakMap,h.set(e,n));let u=n.get(a);if(u===void 0&&(d+=1,u={domain:d===1?j:`${j}-${String(d)}`},n.set(a,u)),u.client!==void 0)return u.client;const s=u,{domain:l}=s,{hooks:g,logger:o}=e,r=(async()=>{await m.setProviderAndWait(l,c());const t=m.getClient(l);return o&&t.setLogger(o),g&&g.length>0&&t.addHooks(...g),t})();return s.client=r,r.catch(t=>{s.client===r&&(s.client=void 0),C(o,t)}),r},P=async()=>{h=new WeakMap,d=0,await m.clearProviders()},T=(e,i,c,a)=>{const n=y([e,i,c]);return Object.keys(a).length===0?`${n}:{}`:`${n}:${y(a)}`},W=(e,i,c,a,n)=>{switch(i){case"boolean":return e.getBooleanDetails(c,a,n);case"number":return e.getNumberDetails(c,a,n);case"object":return e.getObjectDetails(c,a,n);case"string":return e.getStringDetails(c,a,n);default:throw new v("INTERNAL",`createFlags: unknown flag type "${i}"`)}},R=(e,i,c={})=>{const{provider:a,targetingKey:n}=c,u=()=>a?.()??e.provider(i);let s;try{s=typeof n=="function"?n():n}catch{s=void 0}const l=new Map,g=(o,r,t,f)=>{const E=s===void 0?{...f}:{targetingKey:s,...f},S=b=>({errorCode:F.GENERAL,errorMessage:b instanceof Error?b.message:String(b),flagKey:r,flagMetadata:{},reason:"ERROR",value:t}),O=()=>M(e,i,u).then(b=>W(b,o,r,t,E)).catch(S);let p;try{p=T(o,r,t,E)}catch{return O()}const k=l.get(p);if(k)return k;const w=O();return l.set(p,w),w};return{boolean:(o,r,t)=>g("boolean",o,r,t).then(f=>f.value),details:{boolean:(o,r,t)=>g("boolean",o,r,t),number:(o,r,t)=>g("number",o,r,t),object:(o,r,t)=>g("object",o,r,t),string:(o,r,t)=>g("string",o,r,t)},number:(o,r,t)=>g("number",o,r,t).then(f=>f.value),object:(o,r,t)=>g("object",o,r,t).then(f=>f.value),string:(o,r,t)=>g("string",o,r,t).then(f=>f.value)}};export{R as createFlags,P as resetFlags};
|
|
@@ -27,8 +27,16 @@ interface FlagshipHttpOptions {
|
|
|
27
27
|
accountId?: string;
|
|
28
28
|
/** Flagship app id; the SDK builds the evaluation URL (mutually exclusive with `endpoint`). */
|
|
29
29
|
appId?: string;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Bearer token added as an `Authorization: Bearer` header to every request.
|
|
32
|
+
* Either the literal token, or a thunk resolved against the Worker `env` at
|
|
33
|
+
* construction (`(env) => env.FLAGSHIP_TOKEN`) so the secret never has to be
|
|
34
|
+
* inlined in source. A thunk resolving to anything but a non-empty string
|
|
35
|
+
* throws from the bind (`createFlags` logs it and evaluations fall back to
|
|
36
|
+
* their caller-supplied defaults): `Bearer undefined` would otherwise make
|
|
37
|
+
* every evaluation fall back with nothing said.
|
|
38
|
+
*/
|
|
39
|
+
authToken?: ((env: Record<string, unknown>) => unknown) | string;
|
|
32
40
|
/** Base URL override (only used with `appId`). */
|
|
33
41
|
baseUrl?: string;
|
|
34
42
|
cacheMaxSize?: number;
|
|
@@ -54,8 +62,9 @@ type FlagshipProviderOptions = FlagshipBindingOptions | FlagshipHttpOptions;
|
|
|
54
62
|
* // Binding mode (recommended) — reads env.FLAGS at request time:
|
|
55
63
|
* flagshipProvider({ binding: "FLAGS" })
|
|
56
64
|
*
|
|
57
|
-
* // HTTP mode:
|
|
65
|
+
* // HTTP mode — the token is a literal, or a thunk read off the Worker env:
|
|
58
66
|
* flagshipProvider({ appId: "app-abc", accountId: "acct", authToken: "tok" })
|
|
67
|
+
* flagshipProvider({ appId: "app-abc", accountId: "acct", authToken: (env) => env.FLAGSHIP_TOKEN })
|
|
59
68
|
* ```
|
|
60
69
|
*/
|
|
61
70
|
declare const flagshipProvider: (options: FlagshipProviderOptions) => FlagsProviderFactory;
|
|
@@ -27,8 +27,16 @@ interface FlagshipHttpOptions {
|
|
|
27
27
|
accountId?: string;
|
|
28
28
|
/** Flagship app id; the SDK builds the evaluation URL (mutually exclusive with `endpoint`). */
|
|
29
29
|
appId?: string;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Bearer token added as an `Authorization: Bearer` header to every request.
|
|
32
|
+
* Either the literal token, or a thunk resolved against the Worker `env` at
|
|
33
|
+
* construction (`(env) => env.FLAGSHIP_TOKEN`) so the secret never has to be
|
|
34
|
+
* inlined in source. A thunk resolving to anything but a non-empty string
|
|
35
|
+
* throws from the bind (`createFlags` logs it and evaluations fall back to
|
|
36
|
+
* their caller-supplied defaults): `Bearer undefined` would otherwise make
|
|
37
|
+
* every evaluation fall back with nothing said.
|
|
38
|
+
*/
|
|
39
|
+
authToken?: ((env: Record<string, unknown>) => unknown) | string;
|
|
32
40
|
/** Base URL override (only used with `appId`). */
|
|
33
41
|
baseUrl?: string;
|
|
34
42
|
cacheMaxSize?: number;
|
|
@@ -54,8 +62,9 @@ type FlagshipProviderOptions = FlagshipBindingOptions | FlagshipHttpOptions;
|
|
|
54
62
|
* // Binding mode (recommended) — reads env.FLAGS at request time:
|
|
55
63
|
* flagshipProvider({ binding: "FLAGS" })
|
|
56
64
|
*
|
|
57
|
-
* // HTTP mode:
|
|
65
|
+
* // HTTP mode — the token is a literal, or a thunk read off the Worker env:
|
|
58
66
|
* flagshipProvider({ appId: "app-abc", accountId: "acct", authToken: "tok" })
|
|
67
|
+
* flagshipProvider({ appId: "app-abc", accountId: "acct", authToken: (env) => env.FLAGSHIP_TOKEN })
|
|
59
68
|
* ```
|
|
60
69
|
*/
|
|
61
70
|
declare const flagshipProvider: (options: FlagshipProviderOptions) => FlagsProviderFactory;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{FlagshipServerProvider as
|
|
1
|
+
import{FlagshipServerProvider as d}from"@cloudflare/flagship/server";import{LunoraError as o}from"@lunora/errors";const p=e=>"binding"in e&&typeof e.binding=="string",h=e=>{if(p(e)){const{binding:n,...i}=e;return u=>{const t=u[n];if(t==null)throw new o("INTERNAL",`flagshipProvider: no binding "${n}" found on env. Add a \`flagship\` binding to wrangler.jsonc, e.g. { "flagship": [{ "binding": "${n}", "app_id": "your-app-id" }] }.`);return new d({binding:t,...i})}}const{appId:a,endpoint:s}=e;if(a===void 0&&s===void 0)throw new o("INTERNAL",'flagshipProvider: HTTP mode requires either `appId` (the SDK builds the evaluation URL) or `endpoint` (a full evaluation URL). Pass exactly one, or use binding mode: `flagshipProvider({ binding: "FLAGS" })`.');if(a!==void 0&&s!==void 0)throw new o("INTERNAL","flagshipProvider: `appId` and `endpoint` are mutually exclusive in HTTP mode — pass exactly one.");const{authToken:r,...l}=e;return n=>{if(typeof r!="function")return new d({...l,...r===void 0?{}:{authToken:r}});const i=r(n);if(typeof i!="string"||i.length===0)throw new o("INTERNAL","flagshipProvider: `authToken` resolved to an empty or non-string value — check that the env var the thunk reads is set. An unset secret would send `Bearer undefined`, and every evaluation would silently fall back to its default.");return new d({...l,authToken:i})}};export{h as flagshipProvider};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/flags",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.42",
|
|
4
4
|
"description": "OpenFeature-based feature flags for Lunora — ctx.flags, useFlag, and a first-class Cloudflare Flagship provider with any OpenFeature provider pluggable",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
63
|
+
"@lunora/errors": "1.0.0-alpha.30",
|
|
64
64
|
"@openfeature/server-sdk": "^1.23.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as v}from"@lunora/errors";import{OpenFeature as m,ErrorCode as D}from"@openfeature/server-sdk";const F=/["\\\u0000-\u001F\uD800-\uDFFF]/,N=e=>F.test(e)?JSON.stringify(e):`"${e}"`,d=e=>{if(e===void 0)return"null";if(typeof e=="bigint")throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");if(typeof e=="number"){if(Number.isNaN(e))return"nan";if(e===1/0)return"inf";if(e===-1/0)return"-inf";if(Object.is(e,-0))return"-0"}if(typeof e=="string")return N(e);if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e)){let i="[";for(let a=0;a<e.length;a++)a>0&&(i+=","),i+=d(e[a]);return i+"]"}const g=Object.getPrototypeOf(e);if(g!==null&&g!==Object.prototype){const i=e.constructor?.name??"value";throw new TypeError(`stableStringify: cannot use a ${i} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`)}const c=e,s=Object.keys(c).sort();let r="{",f=!0;for(const i of s){const a=c[i];a!==void 0&&(f?f=!1:r+=",",r+=N(i),r+=":",r+=d(a))}return r+"}"},j="lunora";let h=new WeakMap,y=0;const A={},C=e=>Object.keys(e).length===0?A:e,M=(e,g,c)=>{const s=C(g);let r=h.get(e);r===void 0&&(r=new WeakMap,h.set(e,r));let f=r.get(s);if(f===void 0&&(y+=1,f={domain:y===1?j:`${j}-${String(y)}`},r.set(s,f)),f.client!==void 0)return f.client;const i=f,{domain:a}=i,{hooks:b,logger:o}=e,t=(async()=>{await m.setProviderAndWait(a,c());const n=m.getClient(a);return o&&n.setLogger(o),b&&b.length>0&&n.addHooks(...b),n})();return i.client=t,t.catch(()=>{i.client===t&&(i.client=void 0)}),t},L=async()=>{h=new WeakMap,y=0,await m.clearProviders()},$=(e,g,c,s)=>{const r=d([e,g,c]);return Object.keys(s).length===0?`${r}:{}`:`${r}:${d(s)}`},T=(e,g,c,s,r)=>{switch(g){case"boolean":return e.getBooleanDetails(c,s,r);case"number":return e.getNumberDetails(c,s,r);case"object":return e.getObjectDetails(c,s,r);case"string":return e.getStringDetails(c,s,r);default:throw new v("INTERNAL",`createFlags: unknown flag type "${g}"`)}},P=(e,g,c={})=>{const{provider:s,targetingKey:r}=c,f=()=>s?.()??e.provider(g);let i;try{i=typeof r=="function"?r():r}catch{i=void 0}const a=new Map,b=(o,t,n,l)=>{const E=i===void 0?{...l}:{targetingKey:i,...l},S=u=>({errorCode:D.GENERAL,errorMessage:u instanceof Error?u.message:String(u),flagKey:t,flagMetadata:{},reason:"ERROR",value:n}),O=()=>M(e,g,f).then(u=>T(u,o,t,n,E)).catch(S);let p;try{p=$(o,t,n,E)}catch{return O()}const w=a.get(p);if(w)return w;const k=O();return a.set(p,k),k};return{boolean:(o,t,n)=>b("boolean",o,t,n).then(l=>l.value),details:{boolean:(o,t,n)=>b("boolean",o,t,n),number:(o,t,n)=>b("number",o,t,n),object:(o,t,n)=>b("object",o,t,n),string:(o,t,n)=>b("string",o,t,n)},number:(o,t,n)=>b("number",o,t,n).then(l=>l.value),object:(o,t,n)=>b("object",o,t,n).then(l=>l.value),string:(o,t,n)=>b("string",o,t,n).then(l=>l.value)}};export{P as createFlags,L as resetFlags};
|