@nebulr-group/bridge-auth-core 0.1.2 → 0.4.0-beta.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/dist/billing/bridge-subscription.d.ts +71 -0
- package/dist/billing/bridge-subscription.d.ts.map +1 -0
- package/dist/billing/bridge-subscription.js +209 -0
- package/dist/billing/bridge-subscription.js.map +1 -0
- package/dist/billing/entitlements-store.d.ts +61 -0
- package/dist/billing/entitlements-store.d.ts.map +1 -0
- package/dist/billing/entitlements-store.js +127 -0
- package/dist/billing/entitlements-store.js.map +1 -0
- package/dist/billing/fetch-billing-state.d.ts +9 -0
- package/dist/billing/fetch-billing-state.d.ts.map +1 -0
- package/dist/billing/fetch-billing-state.js +22 -0
- package/dist/billing/fetch-billing-state.js.map +1 -0
- package/dist/billing/lock-signal.d.ts +6 -0
- package/dist/billing/lock-signal.d.ts.map +1 -0
- package/dist/billing/lock-signal.js +8 -0
- package/dist/billing/lock-signal.js.map +1 -0
- package/dist/billing/quota-store.d.ts +86 -0
- package/dist/billing/quota-store.d.ts.map +1 -0
- package/dist/billing/quota-store.js +180 -0
- package/dist/billing/quota-store.js.map +1 -0
- package/dist/billing/types.d.ts +64 -0
- package/dist/billing/types.d.ts.map +1 -0
- package/dist/billing/types.js +49 -0
- package/dist/billing/types.js.map +1 -0
- package/dist/billing/use-bridge.d.ts +131 -0
- package/dist/billing/use-bridge.d.ts.map +1 -0
- package/dist/billing/use-bridge.js +181 -0
- package/dist/billing/use-bridge.js.map +1 -0
- package/dist/bridge-auth.d.ts +36 -1
- package/dist/bridge-auth.d.ts.map +1 -1
- package/dist/bridge-auth.js +57 -3
- package/dist/bridge-auth.js.map +1 -1
- package/dist/direct-auth.d.ts +1 -0
- package/dist/direct-auth.d.ts.map +1 -1
- package/dist/direct-auth.js +7 -0
- package/dist/direct-auth.js.map +1 -1
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +14 -0
- package/dist/errors.js.map +1 -1
- package/dist/flags/attribute-providers.d.ts +229 -0
- package/dist/flags/attribute-providers.d.ts.map +1 -0
- package/dist/flags/attribute-providers.js +375 -0
- package/dist/flags/attribute-providers.js.map +1 -0
- package/dist/flags/dev-attribute-provider.d.ts +67 -0
- package/dist/flags/dev-attribute-provider.d.ts.map +1 -0
- package/dist/flags/dev-attribute-provider.js +200 -0
- package/dist/flags/dev-attribute-provider.js.map +1 -0
- package/dist/flags/evaluator.d.ts +58 -0
- package/dist/flags/evaluator.d.ts.map +1 -0
- package/dist/flags/evaluator.js +160 -0
- package/dist/flags/evaluator.js.map +1 -0
- package/dist/flags/flag.d.ts +199 -0
- package/dist/flags/flag.d.ts.map +1 -0
- package/dist/flags/flag.js +337 -0
- package/dist/flags/flag.js.map +1 -0
- package/dist/flags/identity.d.ts +77 -0
- package/dist/flags/identity.d.ts.map +1 -0
- package/dist/flags/identity.js +134 -0
- package/dist/flags/identity.js.map +1 -0
- package/dist/flags/index.d.ts +20 -0
- package/dist/flags/index.d.ts.map +1 -0
- package/dist/flags/index.js +21 -0
- package/dist/flags/index.js.map +1 -0
- package/dist/flags/operators.d.ts +63 -0
- package/dist/flags/operators.d.ts.map +1 -0
- package/dist/flags/operators.js +299 -0
- package/dist/flags/operators.js.map +1 -0
- package/dist/flags/propagation.d.ts +16 -0
- package/dist/flags/propagation.d.ts.map +1 -0
- package/dist/flags/propagation.js +107 -0
- package/dist/flags/propagation.js.map +1 -0
- package/dist/flags/realtime.d.ts +328 -0
- package/dist/flags/realtime.d.ts.map +1 -0
- package/dist/flags/realtime.js +504 -0
- package/dist/flags/realtime.js.map +1 -0
- package/dist/flags/runtime-mode.d.ts +32 -0
- package/dist/flags/runtime-mode.d.ts.map +1 -0
- package/dist/flags/runtime-mode.js +84 -0
- package/dist/flags/runtime-mode.js.map +1 -0
- package/dist/flags/telemetry.d.ts +79 -0
- package/dist/flags/telemetry.d.ts.map +1 -0
- package/dist/flags/telemetry.js +286 -0
- package/dist/flags/telemetry.js.map +1 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +11 -1
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +14 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -1
- package/dist/index.js.map +1 -1
- package/dist/management-types.d.ts +38 -0
- package/dist/management-types.d.ts.map +1 -1
- package/dist/route-guard.d.ts.map +1 -1
- package/dist/route-guard.js +15 -5
- package/dist/route-guard.js.map +1 -1
- package/dist/token-manager.d.ts +3 -1
- package/dist/token-manager.d.ts.map +1 -1
- package/dist/token-manager.js +10 -6
- package/dist/token-manager.js.map +1 -1
- package/dist/types.d.ts +10 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/usage/index.d.ts +3 -0
- package/dist/usage/index.d.ts.map +1 -0
- package/dist/usage/index.js +4 -0
- package/dist/usage/index.js.map +1 -0
- package/dist/usage/storage/durable-storage.d.ts +47 -0
- package/dist/usage/storage/durable-storage.d.ts.map +1 -0
- package/dist/usage/storage/durable-storage.js +27 -0
- package/dist/usage/storage/durable-storage.js.map +1 -0
- package/dist/usage/storage/index.d.ts +7 -0
- package/dist/usage/storage/index.d.ts.map +1 -0
- package/dist/usage/storage/index.js +28 -0
- package/dist/usage/storage/index.js.map +1 -0
- package/dist/usage/storage/indexeddb-storage.d.ts +18 -0
- package/dist/usage/storage/indexeddb-storage.d.ts.map +1 -0
- package/dist/usage/storage/indexeddb-storage.js +159 -0
- package/dist/usage/storage/indexeddb-storage.js.map +1 -0
- package/dist/usage/storage/node-fs-storage.d.ts +29 -0
- package/dist/usage/storage/node-fs-storage.d.ts.map +1 -0
- package/dist/usage/storage/node-fs-storage.js +180 -0
- package/dist/usage/storage/node-fs-storage.js.map +1 -0
- package/dist/usage/storage/noop-storage.d.ts +17 -0
- package/dist/usage/storage/noop-storage.d.ts.map +1 -0
- package/dist/usage/storage/noop-storage.js +67 -0
- package/dist/usage/storage/noop-storage.js.map +1 -0
- package/dist/usage/usage-reporter.d.ts +64 -0
- package/dist/usage/usage-reporter.d.ts.map +1 -0
- package/dist/usage/usage-reporter.js +234 -0
- package/dist/usage/usage-reporter.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attribute-providers.d.ts","sourceRoot":"","sources":["../../src/flags/attribute-providers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
// AttributeProvider plugin model for Bridge Feature Flags (TBP-173).
|
|
2
|
+
//
|
|
3
|
+
// AttributeProvider is THE canonical mechanism for feeding attributes into
|
|
4
|
+
// flag evaluation. Every framework SDK wires the bridge-managed providers
|
|
5
|
+
// (`AuthAttributeProvider`, `BillingAttributeProvider`) automatically on
|
|
6
|
+
// bootstrap so `user.role`, `tenant.plan`, billing entitlements etc. flow in
|
|
7
|
+
// without app code. Apps push their own state into eval by implementing
|
|
8
|
+
// `AttributeProvider` and `register()`ing the instance on the registry — that
|
|
9
|
+
// is the canonical pattern for any attribute backed by app state (cohort,
|
|
10
|
+
// route segment, current workspace, anything reactive).
|
|
11
|
+
//
|
|
12
|
+
// Per-call attributes (via `setContext` or the `<FeatureFlag context>` /
|
|
13
|
+
// `useFlag(key, default, context)` prop) are the *override* path — for
|
|
14
|
+
// transient, one-off, or per-instance values, and to override a provider
|
|
15
|
+
// value at a specific call site. Collision rule (locked decision #20):
|
|
16
|
+
// dev-supplied attributes WIN over provider-supplied attributes on key
|
|
17
|
+
// overlap. The admin UI surfaces the collision so it's debuggable (TBP-178).
|
|
18
|
+
/**
|
|
19
|
+
* Registry attached to a BridgeFlags instance. Framework SDKs (bridge-svelte's
|
|
20
|
+
* Bridge.init) wire this up during bootstrap.
|
|
21
|
+
*/
|
|
22
|
+
export class AttributeProviderRegistry {
|
|
23
|
+
providers = [];
|
|
24
|
+
/** Register a provider. Idempotent on `name` — re-registering with the same name replaces. */
|
|
25
|
+
register(provider) {
|
|
26
|
+
if (!provider.name) {
|
|
27
|
+
throw new Error('AttributeProvider must have a non-empty name');
|
|
28
|
+
}
|
|
29
|
+
this.providers = this.providers.filter((p) => p.name !== provider.name);
|
|
30
|
+
this.providers.push(provider);
|
|
31
|
+
}
|
|
32
|
+
/** Remove a provider by name. */
|
|
33
|
+
unregister(name) {
|
|
34
|
+
this.providers = this.providers.filter((p) => p.name !== name);
|
|
35
|
+
}
|
|
36
|
+
/** Number of providers currently registered. */
|
|
37
|
+
size() {
|
|
38
|
+
return this.providers.length;
|
|
39
|
+
}
|
|
40
|
+
/** List provider names. */
|
|
41
|
+
names() {
|
|
42
|
+
return this.providers.map((p) => p.name);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Collect attributes from every registered provider, merged in registration
|
|
46
|
+
* order. Later providers' keys overwrite earlier ones; this is INTERNAL
|
|
47
|
+
* merge order — dev-supplied attributes always win at the call boundary
|
|
48
|
+
* (handled in `applyTo`).
|
|
49
|
+
*
|
|
50
|
+
* Provider failures are isolated — one provider throwing doesn't block the
|
|
51
|
+
* rest. The failing provider contributes an empty attribute set for that call.
|
|
52
|
+
*/
|
|
53
|
+
async collect() {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const provider of this.providers) {
|
|
56
|
+
try {
|
|
57
|
+
const attrs = await provider.provide();
|
|
58
|
+
Object.assign(out, attrs ?? {});
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Silently skip a failing provider for this eval.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Sync variant of `collect()` — used by `BridgeFlags.flag()` on the hot eval
|
|
68
|
+
* path (FF 2.0 evaluates inline on every flag check, so the merge must be
|
|
69
|
+
* sync). Async providers are SKIPPED for this eval; they should self-cache
|
|
70
|
+
* their result and return sync values once warmed.
|
|
71
|
+
*
|
|
72
|
+
* Same isolation guarantee as `collect()`: one throwing provider doesn't
|
|
73
|
+
* block the rest.
|
|
74
|
+
*/
|
|
75
|
+
collectSync() {
|
|
76
|
+
const out = {};
|
|
77
|
+
for (const provider of this.providers) {
|
|
78
|
+
try {
|
|
79
|
+
const attrs = provider.provide();
|
|
80
|
+
if (attrs instanceof Promise) {
|
|
81
|
+
// Async providers can't participate in the sync hot path. They run
|
|
82
|
+
// through `collect()`/`applyTo()` on demand instead.
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
Object.assign(out, attrs ?? {});
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Silently skip a failing provider for this eval.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Apply provider attributes to a BridgeFlags instance's global context.
|
|
95
|
+
* Should be called on a sensible cadence by the framework SDK — typically:
|
|
96
|
+
* - once at bootstrap
|
|
97
|
+
* - when auth state changes (logout/login, role change)
|
|
98
|
+
* - on a periodic refresh if attributes drift
|
|
99
|
+
*
|
|
100
|
+
* Dev-supplied attributes are preserved on collision (locked decision #20).
|
|
101
|
+
*/
|
|
102
|
+
async applyTo(bridge) {
|
|
103
|
+
const providerAttrs = await this.collect();
|
|
104
|
+
const current = bridge.getContext().attributes;
|
|
105
|
+
// Provider attrs first, dev-supplied attrs second (dev wins on collision)
|
|
106
|
+
bridge.setContext({
|
|
107
|
+
identity: bridge.getContext().identity,
|
|
108
|
+
attributes: { ...providerAttrs, ...current },
|
|
109
|
+
}, false);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Canonical auth-derived attribute source. Reads decoded JWT claims through
|
|
114
|
+
* a sync callback the framework SDK provides, and flattens them into the
|
|
115
|
+
* eval namespace:
|
|
116
|
+
* - `user.id` ← `sub`
|
|
117
|
+
* - `user.role` ← `role`
|
|
118
|
+
* - `user.email` ← `email`
|
|
119
|
+
* - `tenant.id` ← `tid`
|
|
120
|
+
* - `tenant.plan` ← `plan`
|
|
121
|
+
* - `privileges` ← `privileges` (array or string)
|
|
122
|
+
*
|
|
123
|
+
* Construct with no config for the empty stub (every workspace gets the
|
|
124
|
+
* provider in its registry by default; unconfigured ones contribute nothing).
|
|
125
|
+
*/
|
|
126
|
+
export class AuthAttributeProvider {
|
|
127
|
+
name = 'bridge:auth';
|
|
128
|
+
namespace = 'bridge';
|
|
129
|
+
config;
|
|
130
|
+
constructor(config) {
|
|
131
|
+
this.config = config ?? { getClaims: () => undefined };
|
|
132
|
+
}
|
|
133
|
+
provide() {
|
|
134
|
+
let claims;
|
|
135
|
+
try {
|
|
136
|
+
claims = this.config.getClaims();
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
if (!claims || typeof claims !== 'object')
|
|
142
|
+
return {};
|
|
143
|
+
const out = {};
|
|
144
|
+
if (typeof claims.sub === 'string' && claims.sub.length > 0) {
|
|
145
|
+
out['user.id'] = claims.sub;
|
|
146
|
+
}
|
|
147
|
+
if (typeof claims.role === 'string') {
|
|
148
|
+
out['user.role'] = claims.role;
|
|
149
|
+
}
|
|
150
|
+
if (typeof claims.email === 'string') {
|
|
151
|
+
out['user.email'] = claims.email;
|
|
152
|
+
}
|
|
153
|
+
if (typeof claims.tid === 'string') {
|
|
154
|
+
out['tenant.id'] = claims.tid;
|
|
155
|
+
}
|
|
156
|
+
if (typeof claims.plan === 'string') {
|
|
157
|
+
out['tenant.plan'] = claims.plan;
|
|
158
|
+
}
|
|
159
|
+
if (Array.isArray(claims.privileges)) {
|
|
160
|
+
out['privileges'] = claims.privileges;
|
|
161
|
+
}
|
|
162
|
+
else if (typeof claims.privileges === 'string') {
|
|
163
|
+
out['privileges'] = claims.privileges;
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const BILLING_NS_PREFIX = 'bridge:billing.';
|
|
169
|
+
export class BillingAttributeProvider {
|
|
170
|
+
name = 'bridge:billing';
|
|
171
|
+
namespace = 'bridge';
|
|
172
|
+
config;
|
|
173
|
+
stores = {};
|
|
174
|
+
warnedOnce = false;
|
|
175
|
+
/**
|
|
176
|
+
* Construct with an explicit config that supplies the billing snapshot.
|
|
177
|
+
* No config → treated as an empty provider (returns `{}` on every call)
|
|
178
|
+
* so accidentally instantiating without wiring doesn't crash.
|
|
179
|
+
*/
|
|
180
|
+
constructor(config) {
|
|
181
|
+
this.config = config ?? { getBillingSnapshot: () => undefined };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Billing 2.0 US-13 — wire the three reactive stores from `useBridge()` so
|
|
185
|
+
* `provide()` can flatten live billing state synchronously on every flag
|
|
186
|
+
* eval. Idempotent: re-binding replaces the previous store references. Any
|
|
187
|
+
* subset of stores may be supplied — missing stores simply contribute no
|
|
188
|
+
* keys to the flattened map (eg. unattached subscription → no `plan` key).
|
|
189
|
+
*
|
|
190
|
+
* Framework wrappers (bridge-svelte's `Bridge.init`) call this once after
|
|
191
|
+
* `useBridge()` has been bootstrapped, then `register()` the provider on
|
|
192
|
+
* the FF 2.0 AttributeProviderRegistry.
|
|
193
|
+
*/
|
|
194
|
+
bindStores(stores) {
|
|
195
|
+
this.stores = { ...stores };
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Flatten the live billing state into the `bridge:billing.*` namespace.
|
|
199
|
+
*
|
|
200
|
+
* Two sources are merged:
|
|
201
|
+
* 1. US-13 store path: reads the three `useBridge()` stores synchronously
|
|
202
|
+
* and emits canonical `plan`, `trial`, `subscription.status`,
|
|
203
|
+
* `quota.<metric>.*`, and `entitlement.<name>` keys.
|
|
204
|
+
* 2. Legacy `getBillingSnapshot` callback path (pre-US-13): preserved so
|
|
205
|
+
* standalone-FF apps that wired the provider before US-13 keep
|
|
206
|
+
* working. Store-derived keys win on collision (newer = canonical).
|
|
207
|
+
*
|
|
208
|
+
* The store path is sync, no HTTP, no awaits — safe in the FF 2.0 hot eval
|
|
209
|
+
* path. The legacy path may be async; when it is, the merged result is a
|
|
210
|
+
* Promise (consumers awaiting `provide()` already handle this).
|
|
211
|
+
*
|
|
212
|
+
* Failures in any branch degrade silently to whatever was successfully
|
|
213
|
+
* collected — never throws, never breaks the eval.
|
|
214
|
+
*/
|
|
215
|
+
provide() {
|
|
216
|
+
// US-13 — store-backed path. Synchronous, no HTTP. Reads the live state
|
|
217
|
+
// of the three Phase A/B stores (subscription, quotas, entitlements) and
|
|
218
|
+
// flattens into the `bridge:billing.*` namespace. Safe to call before any
|
|
219
|
+
// store is wired — missing stores simply contribute no keys.
|
|
220
|
+
const storeAttrs = this.flattenStores();
|
|
221
|
+
// Legacy path — the `getBillingSnapshot` callback config. Preserved so
|
|
222
|
+
// standalone-FF apps that wired the provider before US-13 keep working.
|
|
223
|
+
let result;
|
|
224
|
+
try {
|
|
225
|
+
result = this.config.getBillingSnapshot();
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
this.warnOnce(err);
|
|
229
|
+
return storeAttrs;
|
|
230
|
+
}
|
|
231
|
+
if (result instanceof Promise) {
|
|
232
|
+
// Async legacy snapshot — merge async; store attrs are returned now and
|
|
233
|
+
// also re-emitted under the resolved promise. Provider attrs from the
|
|
234
|
+
// store path win on collision (newer = US-13 canonical).
|
|
235
|
+
return result.then((snap) => ({ ...this.flatten(snap), ...storeAttrs }), (err) => {
|
|
236
|
+
this.warnOnce(err);
|
|
237
|
+
return storeAttrs;
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
// Sync legacy snapshot (or undefined) — merge sync. Store attrs win.
|
|
241
|
+
const legacyAttrs = this.flatten(result);
|
|
242
|
+
return { ...legacyAttrs, ...storeAttrs };
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* US-13 — read the three stores and emit the canonical `bridge:billing.*`
|
|
246
|
+
* flat map. Sync, defensive: every store ref is optional and every read is
|
|
247
|
+
* wrapped so a single misbehaving store can't break the eval path.
|
|
248
|
+
*
|
|
249
|
+
* Output key set per `BillingAttributeProvider`'s ticket contract:
|
|
250
|
+
* - `bridge:billing.plan` string (plan slug)
|
|
251
|
+
* - `bridge:billing.trial` boolean
|
|
252
|
+
* - `bridge:billing.subscription.status` string
|
|
253
|
+
* - `bridge:billing.quota.<metric>.used` number
|
|
254
|
+
* - `bridge:billing.quota.<metric>.limit` number
|
|
255
|
+
* - `bridge:billing.quota.<metric>.remaining` number
|
|
256
|
+
* - `bridge:billing.quota.<metric>.percent_used` number (0..1+)
|
|
257
|
+
* - `bridge:billing.entitlement.<name>` boolean (incl. `app_active`)
|
|
258
|
+
*
|
|
259
|
+
* Note: `bridge:billing.limit.<name>` (non-quota plan caps like seats) is
|
|
260
|
+
* reserved for a future story — no live source today, so it's left empty.
|
|
261
|
+
*/
|
|
262
|
+
flattenStores() {
|
|
263
|
+
const out = {};
|
|
264
|
+
// Subscription — plan slug, trial flag, status string.
|
|
265
|
+
try {
|
|
266
|
+
const sub = this.stores.subscription;
|
|
267
|
+
if (sub) {
|
|
268
|
+
const snap = sub.snapshot();
|
|
269
|
+
const state = snap.state;
|
|
270
|
+
if (state) {
|
|
271
|
+
if (state.plan && typeof state.plan.slug === 'string') {
|
|
272
|
+
out[`${BILLING_NS_PREFIX}plan`] = state.plan.slug;
|
|
273
|
+
}
|
|
274
|
+
if (typeof state.status === 'string') {
|
|
275
|
+
out[`${BILLING_NS_PREFIX}subscription.status`] = state.status;
|
|
276
|
+
out[`${BILLING_NS_PREFIX}trial`] = state.status === 'trial';
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
this.warnOnce(err);
|
|
283
|
+
}
|
|
284
|
+
// Quotas — per-metric used / limit / remaining / percent_used.
|
|
285
|
+
try {
|
|
286
|
+
const quotas = this.stores.quotas;
|
|
287
|
+
if (quotas) {
|
|
288
|
+
const all = quotas.getAll();
|
|
289
|
+
for (const [metric, snap] of all.entries()) {
|
|
290
|
+
if (!snap)
|
|
291
|
+
continue;
|
|
292
|
+
const base = `${BILLING_NS_PREFIX}quota.${metric}`;
|
|
293
|
+
if (typeof snap.used === 'number')
|
|
294
|
+
out[`${base}.used`] = snap.used;
|
|
295
|
+
if (typeof snap.limit === 'number')
|
|
296
|
+
out[`${base}.limit`] = snap.limit;
|
|
297
|
+
if (typeof snap.remaining === 'number') {
|
|
298
|
+
out[`${base}.remaining`] = snap.remaining;
|
|
299
|
+
}
|
|
300
|
+
if (typeof snap.percent_used === 'number') {
|
|
301
|
+
out[`${base}.percent_used`] = snap.percent_used;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
this.warnOnce(err);
|
|
308
|
+
}
|
|
309
|
+
// Entitlements — boolean per name (incl. `app_active`).
|
|
310
|
+
try {
|
|
311
|
+
const ent = this.stores.entitlements;
|
|
312
|
+
if (ent && ent.isHydrated()) {
|
|
313
|
+
const all = ent.all();
|
|
314
|
+
for (const [name, value] of Object.entries(all)) {
|
|
315
|
+
if (typeof value === 'boolean') {
|
|
316
|
+
out[`${BILLING_NS_PREFIX}entitlement.${name}`] = value;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
this.warnOnce(err);
|
|
323
|
+
}
|
|
324
|
+
return out;
|
|
325
|
+
}
|
|
326
|
+
flatten(snapshot) {
|
|
327
|
+
if (!snapshot || typeof snapshot !== 'object')
|
|
328
|
+
return {};
|
|
329
|
+
const out = {};
|
|
330
|
+
if (typeof snapshot.plan === 'string') {
|
|
331
|
+
out[`${BILLING_NS_PREFIX}plan`] = snapshot.plan;
|
|
332
|
+
}
|
|
333
|
+
if (typeof snapshot.trial === 'boolean') {
|
|
334
|
+
out[`${BILLING_NS_PREFIX}trial`] = snapshot.trial;
|
|
335
|
+
}
|
|
336
|
+
if (snapshot.quota && typeof snapshot.quota === 'object') {
|
|
337
|
+
for (const [resource, q] of Object.entries(snapshot.quota)) {
|
|
338
|
+
if (!q || typeof q !== 'object')
|
|
339
|
+
continue;
|
|
340
|
+
const base = `${BILLING_NS_PREFIX}quota.${resource}`;
|
|
341
|
+
const used = typeof q.used === 'number' ? q.used : undefined;
|
|
342
|
+
const limit = typeof q.limit === 'number' ? q.limit : undefined;
|
|
343
|
+
if (used !== undefined)
|
|
344
|
+
out[`${base}.used`] = used;
|
|
345
|
+
if (limit !== undefined)
|
|
346
|
+
out[`${base}.limit`] = limit;
|
|
347
|
+
if (used !== undefined && limit !== undefined && limit > 0) {
|
|
348
|
+
out[`${base}.percent_used`] = Math.floor((used / limit) * 100);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (snapshot.limit && typeof snapshot.limit === 'object') {
|
|
353
|
+
for (const [name, value] of Object.entries(snapshot.limit)) {
|
|
354
|
+
if (typeof value === 'number' || typeof value === 'string') {
|
|
355
|
+
out[`${BILLING_NS_PREFIX}limit.${name}`] = value;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return out;
|
|
360
|
+
}
|
|
361
|
+
warnOnce(err) {
|
|
362
|
+
if (this.warnedOnce)
|
|
363
|
+
return;
|
|
364
|
+
this.warnedOnce = true;
|
|
365
|
+
try {
|
|
366
|
+
// eslint-disable-next-line no-console
|
|
367
|
+
globalThis.console?.warn?.(`[BillingAttributeProvider] getBillingSnapshot threw; ` +
|
|
368
|
+
`returning empty attributes. Error: ${err?.message ?? String(err)}`);
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
// ignore console errors
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
//# sourceMappingURL=attribute-providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attribute-providers.js","sourceRoot":"","sources":["../../src/flags/attribute-providers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type AttributeGetter = () => unknown;
|
|
2
|
+
export type AttributeBulkGetter = () => Record<string, unknown> | undefined | null;
|
|
3
|
+
export interface SetOptions {
|
|
4
|
+
/**
|
|
5
|
+
* When false, this attribute's reads do NOT participate in
|
|
6
|
+
* `onAttributeObserved` telemetry. Default true.
|
|
7
|
+
*
|
|
8
|
+
* Useful for high-frequency keys (e.g. cursor position, scroll offset)
|
|
9
|
+
* where the dev needs the value in flag evals but doesn't need the
|
|
10
|
+
* telemetry overhead.
|
|
11
|
+
*/
|
|
12
|
+
observed?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Dev-managed AttributeProvider — the single backend for the `bridge.attributes`
|
|
16
|
+
* surface. One instance is created per SDK init and registered with the
|
|
17
|
+
* AttributeProviderRegistry. The order of registration determines whose
|
|
18
|
+
* keys win on collision via `collectSync` merge order (per-call attrs >
|
|
19
|
+
* setContext > providers — locked decision #20). Dev's provider is registered
|
|
20
|
+
* LAST so its keys beat the framework providers.
|
|
21
|
+
*/
|
|
22
|
+
export declare class DevAttributeProvider {
|
|
23
|
+
readonly name = "dev";
|
|
24
|
+
readonly namespace: 'custom';
|
|
25
|
+
private readonly _entries;
|
|
26
|
+
private readonly _bulkEntries;
|
|
27
|
+
private readonly _subscribers;
|
|
28
|
+
/** Static one-shot value. `opts.observed` defaults to true. */
|
|
29
|
+
set(key: string, value: unknown, opts?: SetOptions): void;
|
|
30
|
+
/** Live-bound: getter re-invoked on every flag eval. */
|
|
31
|
+
bind(key: string, getter: AttributeGetter, opts?: SetOptions): void;
|
|
32
|
+
/**
|
|
33
|
+
* Bulk getter returning `Record<string, unknown>`. Reserved keys inside
|
|
34
|
+
* the returned map are silently dropped (a `console.warn` fires once per
|
|
35
|
+
* reserved key per session to surface the typo).
|
|
36
|
+
*/
|
|
37
|
+
bindMany(getter: AttributeBulkGetter, opts?: SetOptions): void;
|
|
38
|
+
/** Remove a key (static or bound). Bulk getters are removed via `clearBulk()`. */
|
|
39
|
+
unset(key: string): void;
|
|
40
|
+
/** Remove every bulk getter. */
|
|
41
|
+
clearBulk(): void;
|
|
42
|
+
/** Remove every entry (test/teardown). */
|
|
43
|
+
clear(): void;
|
|
44
|
+
/**
|
|
45
|
+
* AttributeProvider contract — invoked synchronously on the FF 2.0 hot
|
|
46
|
+
* eval path. Returns the merged attribute map.
|
|
47
|
+
*
|
|
48
|
+
* Merge order within the dev provider: bulk getters first (so individual
|
|
49
|
+
* `set`/`bind` calls win over a bulk that happens to repeat a key).
|
|
50
|
+
* Getter exceptions are isolated — one throwing key doesn't break the rest.
|
|
51
|
+
*/
|
|
52
|
+
provide(): Record<string, unknown>;
|
|
53
|
+
/** Read-side convenience used by the public `bridge.attributes.get()`. */
|
|
54
|
+
get(): Record<string, unknown>;
|
|
55
|
+
/**
|
|
56
|
+
* Whether the key opts out of `onAttributeObserved` telemetry. Looked up
|
|
57
|
+
* by the framework SDK at observation time; bulk-supplied keys default to
|
|
58
|
+
* the bulk entry's `observed` flag (or true).
|
|
59
|
+
*/
|
|
60
|
+
isObserved(key: string): boolean;
|
|
61
|
+
/** Svelte store contract — subscribe to attribute-map snapshots. */
|
|
62
|
+
subscribe(fn: (map: Record<string, unknown>) => void): () => void;
|
|
63
|
+
private _notify;
|
|
64
|
+
}
|
|
65
|
+
/** Exposed for tests in other packages that want to validate the reserved set. */
|
|
66
|
+
export declare const __TEST_RESERVED_PREFIXES: readonly string[];
|
|
67
|
+
//# sourceMappingURL=dev-attribute-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-attribute-provider.d.ts","sourceRoot":"","sources":["../../src/flags/dev-attribute-provider.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC;AAC5C,MAAM,MAAM,mBAAmB,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;AAEnF,MAAM,WAAW,UAAU;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA4BD;;;;;;;GAOG;AACH,qBAAa,oBAAoB;IAC/B,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAY;IAIxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IAGjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqD;IAElF,+DAA+D;IAC/D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI;IAczD,wDAAwD;IACxD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI;IAcnE;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,mBAAmB,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI;IAS9D,kFAAkF;IAClF,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIxB,gCAAgC;IAChC,SAAS,IAAI,IAAI;IAMjB,0CAA0C;IAC1C,KAAK,IAAI,IAAI;IAMb;;;;;;;OAOG;IACH,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IA6BlC,0EAA0E;IAC1E,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAI9B;;;;OAIG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAWhC,oEAAoE;IACpE,SAAS,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IAYjE,OAAO,CAAC,OAAO;CAgBhB;AAED,kFAAkF;AAClF,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAAsB,CAAC"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Phase 5 (TBP-288/328/329/330) — dev-managed AttributeProvider.
|
|
2
|
+
//
|
|
3
|
+
// Backs the `bridge.attributes` surface in framework SDKs (bridge-svelte etc.).
|
|
4
|
+
// Three contribution modes — all coexist in one provider instance:
|
|
5
|
+
//
|
|
6
|
+
// set(key, value) → static one-shot value
|
|
7
|
+
// bind(key, getter) → live; getter is called on every flag eval
|
|
8
|
+
// bindMany(getter) → bulk; getter returns Record<string, unknown>
|
|
9
|
+
//
|
|
10
|
+
// Reserved namespace: keys starting with `bridge:` (and the legacy aliases
|
|
11
|
+
// `app:`, `tenant:`, `user:` reserved for bridge-managed attribute scopes)
|
|
12
|
+
// are REJECTED at write time with a clear console.warn — they don't end up
|
|
13
|
+
// in the provider map. Reads via get() return only the dev-contributed keys.
|
|
14
|
+
const RESERVED_PREFIXES = ['bridge:'];
|
|
15
|
+
function isReserved(key) {
|
|
16
|
+
for (const p of RESERVED_PREFIXES) {
|
|
17
|
+
if (key.startsWith(p))
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
function warnReserved(key, context) {
|
|
23
|
+
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
25
|
+
console.warn(`[bridge.attributes.${context}] key "${key}" is in the reserved \`bridge:\` namespace — write rejected. ` +
|
|
26
|
+
`Use a custom prefix (e.g. \`app.${key.split(':')[1] ?? key}\`) instead.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Dev-managed AttributeProvider — the single backend for the `bridge.attributes`
|
|
31
|
+
* surface. One instance is created per SDK init and registered with the
|
|
32
|
+
* AttributeProviderRegistry. The order of registration determines whose
|
|
33
|
+
* keys win on collision via `collectSync` merge order (per-call attrs >
|
|
34
|
+
* setContext > providers — locked decision #20). Dev's provider is registered
|
|
35
|
+
* LAST so its keys beat the framework providers.
|
|
36
|
+
*/
|
|
37
|
+
export class DevAttributeProvider {
|
|
38
|
+
name = 'dev';
|
|
39
|
+
namespace = 'custom';
|
|
40
|
+
// Single map keyed by attribute key for `static` + `bound` entries.
|
|
41
|
+
// Bulk entries are kept in a parallel list (no single key).
|
|
42
|
+
_entries = new Map();
|
|
43
|
+
_bulkEntries = [];
|
|
44
|
+
// Subscriptions for the Svelte store contract (TBP-328 AC: "subscribable").
|
|
45
|
+
_subscribers = new Set();
|
|
46
|
+
/** Static one-shot value. `opts.observed` defaults to true. */
|
|
47
|
+
set(key, value, opts) {
|
|
48
|
+
if (isReserved(key)) {
|
|
49
|
+
warnReserved(key, 'set');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this._entries.set(key, {
|
|
53
|
+
kind: 'static',
|
|
54
|
+
key,
|
|
55
|
+
value,
|
|
56
|
+
observed: opts?.observed ?? true,
|
|
57
|
+
});
|
|
58
|
+
this._notify();
|
|
59
|
+
}
|
|
60
|
+
/** Live-bound: getter re-invoked on every flag eval. */
|
|
61
|
+
bind(key, getter, opts) {
|
|
62
|
+
if (isReserved(key)) {
|
|
63
|
+
warnReserved(key, 'bind');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this._entries.set(key, {
|
|
67
|
+
kind: 'bound',
|
|
68
|
+
key,
|
|
69
|
+
getter,
|
|
70
|
+
observed: opts?.observed ?? true,
|
|
71
|
+
});
|
|
72
|
+
this._notify();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Bulk getter returning `Record<string, unknown>`. Reserved keys inside
|
|
76
|
+
* the returned map are silently dropped (a `console.warn` fires once per
|
|
77
|
+
* reserved key per session to surface the typo).
|
|
78
|
+
*/
|
|
79
|
+
bindMany(getter, opts) {
|
|
80
|
+
this._bulkEntries.push({
|
|
81
|
+
kind: 'bulk',
|
|
82
|
+
bulkGetter: getter,
|
|
83
|
+
observed: opts?.observed ?? true,
|
|
84
|
+
});
|
|
85
|
+
this._notify();
|
|
86
|
+
}
|
|
87
|
+
/** Remove a key (static or bound). Bulk getters are removed via `clearBulk()`. */
|
|
88
|
+
unset(key) {
|
|
89
|
+
if (this._entries.delete(key))
|
|
90
|
+
this._notify();
|
|
91
|
+
}
|
|
92
|
+
/** Remove every bulk getter. */
|
|
93
|
+
clearBulk() {
|
|
94
|
+
if (this._bulkEntries.length === 0)
|
|
95
|
+
return;
|
|
96
|
+
this._bulkEntries.length = 0;
|
|
97
|
+
this._notify();
|
|
98
|
+
}
|
|
99
|
+
/** Remove every entry (test/teardown). */
|
|
100
|
+
clear() {
|
|
101
|
+
this._entries.clear();
|
|
102
|
+
this._bulkEntries.length = 0;
|
|
103
|
+
this._notify();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* AttributeProvider contract — invoked synchronously on the FF 2.0 hot
|
|
107
|
+
* eval path. Returns the merged attribute map.
|
|
108
|
+
*
|
|
109
|
+
* Merge order within the dev provider: bulk getters first (so individual
|
|
110
|
+
* `set`/`bind` calls win over a bulk that happens to repeat a key).
|
|
111
|
+
* Getter exceptions are isolated — one throwing key doesn't break the rest.
|
|
112
|
+
*/
|
|
113
|
+
provide() {
|
|
114
|
+
const out = {};
|
|
115
|
+
for (const entry of this._bulkEntries) {
|
|
116
|
+
try {
|
|
117
|
+
const m = entry.bulkGetter();
|
|
118
|
+
if (m && typeof m === 'object') {
|
|
119
|
+
for (const [k, v] of Object.entries(m)) {
|
|
120
|
+
if (isReserved(k))
|
|
121
|
+
continue;
|
|
122
|
+
out[k] = v;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Skip a throwing bulk getter for this eval.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const entry of this._entries.values()) {
|
|
131
|
+
try {
|
|
132
|
+
if (entry.kind === 'static') {
|
|
133
|
+
out[entry.key] = entry.value;
|
|
134
|
+
}
|
|
135
|
+
else if (entry.kind === 'bound') {
|
|
136
|
+
out[entry.key] = entry.getter();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Skip a throwing getter; other keys still flow through.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
/** Read-side convenience used by the public `bridge.attributes.get()`. */
|
|
146
|
+
get() {
|
|
147
|
+
return this.provide();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Whether the key opts out of `onAttributeObserved` telemetry. Looked up
|
|
151
|
+
* by the framework SDK at observation time; bulk-supplied keys default to
|
|
152
|
+
* the bulk entry's `observed` flag (or true).
|
|
153
|
+
*/
|
|
154
|
+
isObserved(key) {
|
|
155
|
+
const direct = this._entries.get(key);
|
|
156
|
+
if (direct)
|
|
157
|
+
return direct.observed;
|
|
158
|
+
// Bulk entries don't track per-key observation; use the most-recently
|
|
159
|
+
// added bulk entry's flag if any, else default true.
|
|
160
|
+
if (this._bulkEntries.length > 0) {
|
|
161
|
+
return this._bulkEntries[this._bulkEntries.length - 1].observed;
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
/** Svelte store contract — subscribe to attribute-map snapshots. */
|
|
166
|
+
subscribe(fn) {
|
|
167
|
+
this._subscribers.add(fn);
|
|
168
|
+
try {
|
|
169
|
+
fn(this.provide());
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// ignore subscriber errors
|
|
173
|
+
}
|
|
174
|
+
return () => {
|
|
175
|
+
this._subscribers.delete(fn);
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
_notify() {
|
|
179
|
+
if (this._subscribers.size === 0)
|
|
180
|
+
return;
|
|
181
|
+
let snapshot;
|
|
182
|
+
try {
|
|
183
|
+
snapshot = this.provide();
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
for (const fn of this._subscribers) {
|
|
189
|
+
try {
|
|
190
|
+
fn(snapshot);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// ignore subscriber errors
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** Exposed for tests in other packages that want to validate the reserved set. */
|
|
199
|
+
export const __TEST_RESERVED_PREFIXES = RESERVED_PREFIXES;
|
|
200
|
+
//# sourceMappingURL=dev-attribute-provider.js.map
|