@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.
Files changed (131) hide show
  1. package/dist/billing/bridge-subscription.d.ts +71 -0
  2. package/dist/billing/bridge-subscription.d.ts.map +1 -0
  3. package/dist/billing/bridge-subscription.js +209 -0
  4. package/dist/billing/bridge-subscription.js.map +1 -0
  5. package/dist/billing/entitlements-store.d.ts +61 -0
  6. package/dist/billing/entitlements-store.d.ts.map +1 -0
  7. package/dist/billing/entitlements-store.js +127 -0
  8. package/dist/billing/entitlements-store.js.map +1 -0
  9. package/dist/billing/fetch-billing-state.d.ts +9 -0
  10. package/dist/billing/fetch-billing-state.d.ts.map +1 -0
  11. package/dist/billing/fetch-billing-state.js +22 -0
  12. package/dist/billing/fetch-billing-state.js.map +1 -0
  13. package/dist/billing/lock-signal.d.ts +6 -0
  14. package/dist/billing/lock-signal.d.ts.map +1 -0
  15. package/dist/billing/lock-signal.js +8 -0
  16. package/dist/billing/lock-signal.js.map +1 -0
  17. package/dist/billing/quota-store.d.ts +86 -0
  18. package/dist/billing/quota-store.d.ts.map +1 -0
  19. package/dist/billing/quota-store.js +180 -0
  20. package/dist/billing/quota-store.js.map +1 -0
  21. package/dist/billing/types.d.ts +64 -0
  22. package/dist/billing/types.d.ts.map +1 -0
  23. package/dist/billing/types.js +49 -0
  24. package/dist/billing/types.js.map +1 -0
  25. package/dist/billing/use-bridge.d.ts +131 -0
  26. package/dist/billing/use-bridge.d.ts.map +1 -0
  27. package/dist/billing/use-bridge.js +181 -0
  28. package/dist/billing/use-bridge.js.map +1 -0
  29. package/dist/bridge-auth.d.ts +36 -1
  30. package/dist/bridge-auth.d.ts.map +1 -1
  31. package/dist/bridge-auth.js +57 -3
  32. package/dist/bridge-auth.js.map +1 -1
  33. package/dist/direct-auth.d.ts +1 -0
  34. package/dist/direct-auth.d.ts.map +1 -1
  35. package/dist/direct-auth.js +7 -0
  36. package/dist/direct-auth.js.map +1 -1
  37. package/dist/errors.d.ts +11 -0
  38. package/dist/errors.d.ts.map +1 -1
  39. package/dist/errors.js +14 -0
  40. package/dist/errors.js.map +1 -1
  41. package/dist/flags/attribute-providers.d.ts +229 -0
  42. package/dist/flags/attribute-providers.d.ts.map +1 -0
  43. package/dist/flags/attribute-providers.js +375 -0
  44. package/dist/flags/attribute-providers.js.map +1 -0
  45. package/dist/flags/dev-attribute-provider.d.ts +67 -0
  46. package/dist/flags/dev-attribute-provider.d.ts.map +1 -0
  47. package/dist/flags/dev-attribute-provider.js +200 -0
  48. package/dist/flags/dev-attribute-provider.js.map +1 -0
  49. package/dist/flags/evaluator.d.ts +58 -0
  50. package/dist/flags/evaluator.d.ts.map +1 -0
  51. package/dist/flags/evaluator.js +160 -0
  52. package/dist/flags/evaluator.js.map +1 -0
  53. package/dist/flags/flag.d.ts +199 -0
  54. package/dist/flags/flag.d.ts.map +1 -0
  55. package/dist/flags/flag.js +337 -0
  56. package/dist/flags/flag.js.map +1 -0
  57. package/dist/flags/identity.d.ts +77 -0
  58. package/dist/flags/identity.d.ts.map +1 -0
  59. package/dist/flags/identity.js +134 -0
  60. package/dist/flags/identity.js.map +1 -0
  61. package/dist/flags/index.d.ts +20 -0
  62. package/dist/flags/index.d.ts.map +1 -0
  63. package/dist/flags/index.js +21 -0
  64. package/dist/flags/index.js.map +1 -0
  65. package/dist/flags/operators.d.ts +63 -0
  66. package/dist/flags/operators.d.ts.map +1 -0
  67. package/dist/flags/operators.js +299 -0
  68. package/dist/flags/operators.js.map +1 -0
  69. package/dist/flags/propagation.d.ts +16 -0
  70. package/dist/flags/propagation.d.ts.map +1 -0
  71. package/dist/flags/propagation.js +107 -0
  72. package/dist/flags/propagation.js.map +1 -0
  73. package/dist/flags/realtime.d.ts +328 -0
  74. package/dist/flags/realtime.d.ts.map +1 -0
  75. package/dist/flags/realtime.js +504 -0
  76. package/dist/flags/realtime.js.map +1 -0
  77. package/dist/flags/runtime-mode.d.ts +32 -0
  78. package/dist/flags/runtime-mode.d.ts.map +1 -0
  79. package/dist/flags/runtime-mode.js +84 -0
  80. package/dist/flags/runtime-mode.js.map +1 -0
  81. package/dist/flags/telemetry.d.ts +79 -0
  82. package/dist/flags/telemetry.d.ts.map +1 -0
  83. package/dist/flags/telemetry.js +286 -0
  84. package/dist/flags/telemetry.js.map +1 -0
  85. package/dist/http.d.ts.map +1 -1
  86. package/dist/http.js +11 -1
  87. package/dist/http.js.map +1 -1
  88. package/dist/index.d.ts +14 -2
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +16 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/management-types.d.ts +38 -0
  93. package/dist/management-types.d.ts.map +1 -1
  94. package/dist/route-guard.d.ts.map +1 -1
  95. package/dist/route-guard.js +15 -5
  96. package/dist/route-guard.js.map +1 -1
  97. package/dist/token-manager.d.ts +3 -1
  98. package/dist/token-manager.d.ts.map +1 -1
  99. package/dist/token-manager.js +10 -6
  100. package/dist/token-manager.js.map +1 -1
  101. package/dist/types.d.ts +10 -2
  102. package/dist/types.d.ts.map +1 -1
  103. package/dist/usage/index.d.ts +3 -0
  104. package/dist/usage/index.d.ts.map +1 -0
  105. package/dist/usage/index.js +4 -0
  106. package/dist/usage/index.js.map +1 -0
  107. package/dist/usage/storage/durable-storage.d.ts +47 -0
  108. package/dist/usage/storage/durable-storage.d.ts.map +1 -0
  109. package/dist/usage/storage/durable-storage.js +27 -0
  110. package/dist/usage/storage/durable-storage.js.map +1 -0
  111. package/dist/usage/storage/index.d.ts +7 -0
  112. package/dist/usage/storage/index.d.ts.map +1 -0
  113. package/dist/usage/storage/index.js +28 -0
  114. package/dist/usage/storage/index.js.map +1 -0
  115. package/dist/usage/storage/indexeddb-storage.d.ts +18 -0
  116. package/dist/usage/storage/indexeddb-storage.d.ts.map +1 -0
  117. package/dist/usage/storage/indexeddb-storage.js +159 -0
  118. package/dist/usage/storage/indexeddb-storage.js.map +1 -0
  119. package/dist/usage/storage/node-fs-storage.d.ts +29 -0
  120. package/dist/usage/storage/node-fs-storage.d.ts.map +1 -0
  121. package/dist/usage/storage/node-fs-storage.js +180 -0
  122. package/dist/usage/storage/node-fs-storage.js.map +1 -0
  123. package/dist/usage/storage/noop-storage.d.ts +17 -0
  124. package/dist/usage/storage/noop-storage.d.ts.map +1 -0
  125. package/dist/usage/storage/noop-storage.js +67 -0
  126. package/dist/usage/storage/noop-storage.js.map +1 -0
  127. package/dist/usage/usage-reporter.d.ts +64 -0
  128. package/dist/usage/usage-reporter.d.ts.map +1 -0
  129. package/dist/usage/usage-reporter.js +234 -0
  130. package/dist/usage/usage-reporter.js.map +1 -0
  131. 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