@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,71 @@
1
+ import type { Logger } from '../logger.js';
2
+ import type { RealtimeClient, SubscriptionPlanChangedMessage } from '../flags/realtime.js';
3
+ import type { BillingLockedPayload, BillingSubscriptionSnapshot, BillingSubscriptionState, MountOptions } from './types.js';
4
+ type Listener = (snapshot: BillingSubscriptionSnapshot) => void;
5
+ /**
6
+ * Reactive container for the canonical Billing 2.0 subscription state.
7
+ * Framework-agnostic; consumers attach a listener via `subscribe()` and read
8
+ * the current value via `snapshot()`. Compatible with the Svelte store
9
+ * contract (`subscribe(listener) => unsubscribe`).
10
+ *
11
+ * Parallel to the FF 2.0 `BridgeFlags` reactive surface — do NOT unify yet.
12
+ * REF-1 (post-feature) will fold them together.
13
+ */
14
+ export declare class BridgeSubscription {
15
+ private _state;
16
+ private _loading;
17
+ private _error;
18
+ private _listeners;
19
+ /** Current snapshot. */
20
+ snapshot(): BillingSubscriptionSnapshot;
21
+ /**
22
+ * Subscribe to snapshot changes. Invokes the listener immediately with the
23
+ * current snapshot (Svelte-store compatible). Returns an unsubscribe fn.
24
+ */
25
+ subscribe(listener: Listener): () => void;
26
+ /** Replace the cached state and notify subscribers. */
27
+ hydrate(state: BillingSubscriptionState): void;
28
+ setLoading(loading: boolean): void;
29
+ setError(error: string | null): void;
30
+ /**
31
+ * Fetch `GET /billing/state` once and hydrate. Idempotent on success — the
32
+ * SDK caches the result in memory and any subsequent `mount()` call is
33
+ * effectively a manual refetch. US-3 adds the live push that updates the
34
+ * cache without a refetch.
35
+ *
36
+ * HTTP is intentionally inside auth-core here (per US-2 AC) so framework
37
+ * wrappers don't each reimplement the fetch.
38
+ */
39
+ mount(opts: MountOptions, logger?: Logger): Promise<void>;
40
+ /**
41
+ * Flip the store into the locked state from a caught `BillingLockedError`.
42
+ * Patches `gateEngaged`/`status`/`recoveryUrl` so the notice + gate react
43
+ * immediately even when no prior `mount()` baseline exists.
44
+ */
45
+ markLocked(payload: BillingLockedPayload): void;
46
+ /**
47
+ * Billing 2.0 — attach to a Milestone K RealtimeClient so canonical
48
+ * lifecycle events on the workspace channel patch the reactive store.
49
+ * Handles US-3 plan-change + US-4+ lifecycle (payment.*, subscription.*,
50
+ * dunning.*, entitlements.*). Idempotent: re-attaching replaces hooks.
51
+ * Framework SDKs call this once after constructing both the RealtimeClient
52
+ * and the BridgeSubscription instance.
53
+ */
54
+ attach(rt: RealtimeClient): void;
55
+ /**
56
+ * TBP-360 — Hydrate from a `subscription.plan_changed` push. Extracted into
57
+ * a public-but-internal method so `useBridge().attachToRealtimeClient` can
58
+ * install a composite hook (subscription hydrate + dev-handler fan-out)
59
+ * without re-implementing the patch logic.
60
+ */
61
+ _applyPlanChanged(msg: SubscriptionPlanChangedMessage): void;
62
+ /**
63
+ * Patch the snapshot based on a lifecycle event. Conservative — only
64
+ * touches the fields the event semantically affects. Callers can still
65
+ * trigger a full refetch via `mount(opts)` to re-sync from the server.
66
+ */
67
+ private _applyLifecycle;
68
+ private _notify;
69
+ }
70
+ export {};
71
+ //# sourceMappingURL=bridge-subscription.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-subscription.d.ts","sourceRoot":"","sources":["../../src/billing/bridge-subscription.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAEV,cAAc,EACd,8BAA8B,EAC/B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EACV,oBAAoB,EACpB,2BAA2B,EAC3B,wBAAwB,EAExB,YAAY,EAEb,MAAM,YAAY,CAAC;AAEpB,KAAK,QAAQ,GAAG,CAAC,QAAQ,EAAE,2BAA2B,KAAK,IAAI,CAAC;AAQhE;;;;;;;;GAQG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAyC;IACvD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,UAAU,CAAuB;IAEzC,wBAAwB;IACxB,QAAQ,IAAI,2BAA2B;IAQvC;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI;IAQzC,uDAAuD;IACvD,OAAO,CAAC,KAAK,EAAE,wBAAwB,GAAG,IAAI;IAM9C,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAMlC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAKpC;;;;;;;;OAQG;IACG,KAAK,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,GAAE,MAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAc3E;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI;IAW/C;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,EAAE,cAAc,GAAG,IAAI;IAKhC;;;;;OAKG;IACH,iBAAiB,CAAC,GAAG,EAAE,8BAA8B,GAAG,IAAI;IAO5D;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAwEvB,OAAO,CAAC,OAAO;CAMhB"}
@@ -0,0 +1,209 @@
1
+ import { fetchBillingState } from './fetch-billing-state.js';
2
+ const noopLogger = {
3
+ debug: () => { },
4
+ warn: () => { },
5
+ error: () => { },
6
+ };
7
+ /**
8
+ * Reactive container for the canonical Billing 2.0 subscription state.
9
+ * Framework-agnostic; consumers attach a listener via `subscribe()` and read
10
+ * the current value via `snapshot()`. Compatible with the Svelte store
11
+ * contract (`subscribe(listener) => unsubscribe`).
12
+ *
13
+ * Parallel to the FF 2.0 `BridgeFlags` reactive surface — do NOT unify yet.
14
+ * REF-1 (post-feature) will fold them together.
15
+ */
16
+ export class BridgeSubscription {
17
+ _state = null;
18
+ _loading = false;
19
+ _error = null;
20
+ _listeners = new Set();
21
+ /** Current snapshot. */
22
+ snapshot() {
23
+ return {
24
+ state: this._state,
25
+ loading: this._loading,
26
+ error: this._error,
27
+ };
28
+ }
29
+ /**
30
+ * Subscribe to snapshot changes. Invokes the listener immediately with the
31
+ * current snapshot (Svelte-store compatible). Returns an unsubscribe fn.
32
+ */
33
+ subscribe(listener) {
34
+ this._listeners.add(listener);
35
+ listener(this.snapshot());
36
+ return () => {
37
+ this._listeners.delete(listener);
38
+ };
39
+ }
40
+ /** Replace the cached state and notify subscribers. */
41
+ hydrate(state) {
42
+ this._state = state;
43
+ this._error = null;
44
+ this._notify();
45
+ }
46
+ setLoading(loading) {
47
+ if (this._loading === loading)
48
+ return;
49
+ this._loading = loading;
50
+ this._notify();
51
+ }
52
+ setError(error) {
53
+ this._error = error;
54
+ this._notify();
55
+ }
56
+ /**
57
+ * Fetch `GET /billing/state` once and hydrate. Idempotent on success — the
58
+ * SDK caches the result in memory and any subsequent `mount()` call is
59
+ * effectively a manual refetch. US-3 adds the live push that updates the
60
+ * cache without a refetch.
61
+ *
62
+ * HTTP is intentionally inside auth-core here (per US-2 AC) so framework
63
+ * wrappers don't each reimplement the fetch.
64
+ */
65
+ async mount(opts, logger = noopLogger) {
66
+ this.setLoading(true);
67
+ this.setError(null);
68
+ try {
69
+ const state = await fetchBillingState(opts, logger);
70
+ this.hydrate(state);
71
+ }
72
+ catch (err) {
73
+ const message = err instanceof Error ? err.message : 'Failed to load billing state';
74
+ this.setError(message);
75
+ }
76
+ finally {
77
+ this.setLoading(false);
78
+ }
79
+ }
80
+ /**
81
+ * Flip the store into the locked state from a caught `BillingLockedError`.
82
+ * Patches `gateEngaged`/`status`/`recoveryUrl` so the notice + gate react
83
+ * immediately even when no prior `mount()` baseline exists.
84
+ */
85
+ markLocked(payload) {
86
+ const current = this._state;
87
+ const next = current
88
+ ? { ...current }
89
+ : { plan: { slug: 'unknown', name: 'Unknown' }, status: payload.billing.status };
90
+ next.status = payload.billing.status;
91
+ next.gateEngaged = payload.billing.gateEngaged;
92
+ if (payload.billing.recoveryUrl)
93
+ next.recoveryUrl = payload.billing.recoveryUrl;
94
+ this.hydrate(next);
95
+ }
96
+ /**
97
+ * Billing 2.0 — attach to a Milestone K RealtimeClient so canonical
98
+ * lifecycle events on the workspace channel patch the reactive store.
99
+ * Handles US-3 plan-change + US-4+ lifecycle (payment.*, subscription.*,
100
+ * dunning.*, entitlements.*). Idempotent: re-attaching replaces hooks.
101
+ * Framework SDKs call this once after constructing both the RealtimeClient
102
+ * and the BridgeSubscription instance.
103
+ */
104
+ attach(rt) {
105
+ rt.setOnSubscriptionPlanChanged((msg) => this._applyPlanChanged(msg));
106
+ rt.setOnBillingLifecycle((msg) => this._applyLifecycle(msg));
107
+ }
108
+ /**
109
+ * TBP-360 — Hydrate from a `subscription.plan_changed` push. Extracted into
110
+ * a public-but-internal method so `useBridge().attachToRealtimeClient` can
111
+ * install a composite hook (subscription hydrate + dev-handler fan-out)
112
+ * without re-implementing the patch logic.
113
+ */
114
+ _applyPlanChanged(msg) {
115
+ this.hydrate({
116
+ plan: msg.to,
117
+ status: msg.status,
118
+ });
119
+ }
120
+ /**
121
+ * Patch the snapshot based on a lifecycle event. Conservative — only
122
+ * touches the fields the event semantically affects. Callers can still
123
+ * trigger a full refetch via `mount(opts)` to re-sync from the server.
124
+ */
125
+ _applyLifecycle(msg) {
126
+ const current = this._state;
127
+ if (!current) {
128
+ // No baseline yet; lifecycle events can't reconstruct a full state
129
+ // from scratch — they're patches. Skip; consumer should mount first.
130
+ return;
131
+ }
132
+ const next = { ...current };
133
+ switch (msg.kind) {
134
+ case 'payment.failed':
135
+ next.status = 'past_due';
136
+ next.pastDueReason = msg.pastDueReason ?? 'card_declined';
137
+ if (msg.cardLast4)
138
+ next.cardLast4 = msg.cardLast4;
139
+ break;
140
+ case 'payment.succeeded':
141
+ case 'dunning.recovered':
142
+ next.status = 'active';
143
+ next.pastDueReason = null;
144
+ next.nextRetryAt = undefined;
145
+ next.finalRetryAt = undefined;
146
+ next.gateEngaged = false;
147
+ break;
148
+ case 'subscription.canceled':
149
+ next.status = 'canceled';
150
+ if (msg.endsAt)
151
+ next.endsAt = msg.endsAt;
152
+ break;
153
+ case 'subscription.reactivated':
154
+ next.status = 'active';
155
+ next.endsAt = undefined;
156
+ break;
157
+ case 'subscription.trial_started':
158
+ next.status = 'trial';
159
+ if (msg.endsAt)
160
+ next.endsAt = msg.endsAt;
161
+ if (msg.daysLeft !== undefined)
162
+ next.daysLeft = msg.daysLeft;
163
+ if (msg.hasCardOnFile !== undefined)
164
+ next.hasCardOnFile = msg.hasCardOnFile;
165
+ break;
166
+ case 'subscription.trial_ending_soon':
167
+ if (msg.daysLeft !== undefined)
168
+ next.daysLeft = msg.daysLeft;
169
+ break;
170
+ case 'subscription.trial_converted':
171
+ next.status = 'active';
172
+ next.endsAt = undefined;
173
+ next.daysLeft = undefined;
174
+ break;
175
+ case 'subscription.trial_expired':
176
+ next.status = 'past_due';
177
+ next.pastDueReason = 'trial_expired';
178
+ break;
179
+ case 'dunning.entered':
180
+ case 'dunning.retry_scheduled':
181
+ next.status = 'past_due';
182
+ if (msg.nextRetryAt)
183
+ next.nextRetryAt = msg.nextRetryAt;
184
+ if (msg.finalRetryAt)
185
+ next.finalRetryAt = msg.finalRetryAt;
186
+ break;
187
+ case 'dunning.exhausted':
188
+ next.status = 'past_due';
189
+ next.gateEngaged = msg.gateEngaged ?? true;
190
+ break;
191
+ case 'entitlements.changed':
192
+ // Pure signal — consumer apps re-read entitlements. No status mutation here.
193
+ break;
194
+ case 'subscription.created':
195
+ case 'subscription.updated':
196
+ // No-op on the cached snapshot. Mount fetches the full state if the
197
+ // consumer needs the new values.
198
+ break;
199
+ }
200
+ this.hydrate(next);
201
+ }
202
+ _notify() {
203
+ const snap = this.snapshot();
204
+ for (const listener of this._listeners) {
205
+ listener(snap);
206
+ }
207
+ }
208
+ }
209
+ //# sourceMappingURL=bridge-subscription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-subscription.js","sourceRoot":"","sources":["../../src/billing/bridge-subscription.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import type { Logger } from '../logger.js';
2
+ /** Public entitlement snapshot shape — a flat boolean map. */
3
+ export type EntitlementSnapshot = Record<string, boolean>;
4
+ type Listener = (snapshot: EntitlementSnapshot) => void;
5
+ interface ConfigureOptions {
6
+ apiBaseUrl: string;
7
+ /**
8
+ * Returns the current Bearer access token, or null when unauthenticated
9
+ * (hydrate is skipped). Lazy so the wrapper can rotate tokens without
10
+ * reconfiguring the store.
11
+ */
12
+ getAccessToken: () => string | null;
13
+ }
14
+ export declare class EntitlementsStore {
15
+ private _cache;
16
+ private _hydrated;
17
+ private _hydrating;
18
+ private _subscribers;
19
+ private _opts;
20
+ private _logger;
21
+ /** Wire HTTP options + optional logger. Framework wrappers call this once. */
22
+ configure(opts: ConfigureOptions, logger?: Logger): void;
23
+ /** Has the initial REST fetch completed? Drives fail-closed reads. */
24
+ isHydrated(): boolean;
25
+ /**
26
+ * Initial-hydration fetch. Idempotent — concurrent callers share one
27
+ * round-trip; subsequent calls after success are no-ops unless the caller
28
+ * explicitly resets. Failures leave the store unhydrated; `can()` keeps
29
+ * returning false until a live push or a retry succeeds.
30
+ */
31
+ hydrate(): Promise<void>;
32
+ /**
33
+ * Wholesale-replace the cache from a server-pushed snapshot. The server
34
+ * publishes the FULL set on every change so the SDK never has to merge
35
+ * partial deltas — last-write-wins.
36
+ */
37
+ applyEntitlementsChanged(snapshot: EntitlementSnapshot): void;
38
+ /**
39
+ * Fail-closed read.
40
+ * - Before hydration: returns `false`.
41
+ * - After hydration with a key NOT in the cache: returns `false`.
42
+ * - After hydration with a key in the cache: returns the boolean.
43
+ *
44
+ * Consumers gate UX with `{#if bridge.entitlements.can('ai_completions')}`
45
+ * — fail-closed protects against showing privileged UX on a cold start.
46
+ */
47
+ can(name: string): boolean;
48
+ /** All cached entitlements (shallow copy). */
49
+ all(): EntitlementSnapshot;
50
+ /**
51
+ * Subscribe to snapshot changes. Returns an unsubscribe fn.
52
+ * Does NOT invoke immediately with the current snapshot — the consumer can
53
+ * read `all()` synchronously if it needs the initial value.
54
+ */
55
+ subscribe(cb: Listener): () => void;
56
+ /** Test-only: clear cache + listeners. */
57
+ __resetForTests(): void;
58
+ private _notify;
59
+ }
60
+ export {};
61
+ //# sourceMappingURL=entitlements-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entitlements-store.d.ts","sourceRoot":"","sources":["../../src/billing/entitlements-store.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,8DAA8D;AAC9D,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1D,KAAK,QAAQ,GAAG,CAAC,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAExD,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,cAAc,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACrC;AAQD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,OAAO,CAAsB;IAErC,8EAA8E;IAC9E,SAAS,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,GAAE,MAAmB,GAAG,IAAI;IAKpE,sEAAsE;IACtE,UAAU,IAAI,OAAO;IAIrB;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA6B9B;;;;OAIG;IACH,wBAAwB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAM7D;;;;;;;;OAQG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAK1B,8CAA8C;IAC9C,GAAG,IAAI,mBAAmB;IAI1B;;;;OAIG;IACH,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,IAAI;IAOnC,0CAA0C;IAC1C,eAAe,IAAI,IAAI;IAQvB,OAAO,CAAC,OAAO;CAUhB"}
@@ -0,0 +1,127 @@
1
+ // Billing 2.0 US-12 (TBP-264) — SDK entitlement cache + reactive surface.
2
+ //
3
+ // Backs `useBridge().entitlements.can(name)`. Holds the boolean map published
4
+ // by the bridge-api EntitlementService:
5
+ // - `hydrate()` — `GET /entitlements` once on attach.
6
+ // - `applyEntitlementsChanged(snapshot)` — replaces cache wholesale on every
7
+ // `entitlements.changed` push from the workspace channel. Last-write-wins.
8
+ // - `can(name)` — fail-closed: returns `false` until hydrated and only `true`
9
+ // when the key is explicitly present and `true`.
10
+ //
11
+ // Framework-agnostic. Subscribers receive a snapshot notification on every
12
+ // cache mutation; the bridge-svelte/react wrappers wire those into reactive
13
+ // containers.
14
+ import { httpFetch } from '../http.js';
15
+ const noopLogger = {
16
+ debug: () => { },
17
+ warn: () => { },
18
+ error: () => { },
19
+ };
20
+ export class EntitlementsStore {
21
+ _cache = {};
22
+ _hydrated = false;
23
+ _hydrating = false;
24
+ _subscribers = new Set();
25
+ _opts = null;
26
+ _logger = noopLogger;
27
+ /** Wire HTTP options + optional logger. Framework wrappers call this once. */
28
+ configure(opts, logger = noopLogger) {
29
+ this._opts = opts;
30
+ this._logger = logger;
31
+ }
32
+ /** Has the initial REST fetch completed? Drives fail-closed reads. */
33
+ isHydrated() {
34
+ return this._hydrated;
35
+ }
36
+ /**
37
+ * Initial-hydration fetch. Idempotent — concurrent callers share one
38
+ * round-trip; subsequent calls after success are no-ops unless the caller
39
+ * explicitly resets. Failures leave the store unhydrated; `can()` keeps
40
+ * returning false until a live push or a retry succeeds.
41
+ */
42
+ async hydrate() {
43
+ if (this._hydrated || this._hydrating)
44
+ return;
45
+ if (!this._opts)
46
+ return;
47
+ const token = this._opts.getAccessToken();
48
+ if (!token)
49
+ return;
50
+ this._hydrating = true;
51
+ try {
52
+ const url = `${this._opts.apiBaseUrl.replace(/\/+$/, '')}/entitlements`;
53
+ const body = await httpFetch(url, {
54
+ method: 'GET',
55
+ headers: { Authorization: `Bearer ${token}` },
56
+ }, this._logger);
57
+ this._cache = { ...(body?.entitlements ?? {}) };
58
+ this._hydrated = true;
59
+ this._notify();
60
+ }
61
+ catch (err) {
62
+ this._logger.warn('[bridge.entitlements] hydrate failed', err instanceof Error ? err.message : err);
63
+ }
64
+ finally {
65
+ this._hydrating = false;
66
+ }
67
+ }
68
+ /**
69
+ * Wholesale-replace the cache from a server-pushed snapshot. The server
70
+ * publishes the FULL set on every change so the SDK never has to merge
71
+ * partial deltas — last-write-wins.
72
+ */
73
+ applyEntitlementsChanged(snapshot) {
74
+ this._cache = { ...snapshot };
75
+ this._hydrated = true;
76
+ this._notify();
77
+ }
78
+ /**
79
+ * Fail-closed read.
80
+ * - Before hydration: returns `false`.
81
+ * - After hydration with a key NOT in the cache: returns `false`.
82
+ * - After hydration with a key in the cache: returns the boolean.
83
+ *
84
+ * Consumers gate UX with `{#if bridge.entitlements.can('ai_completions')}`
85
+ * — fail-closed protects against showing privileged UX on a cold start.
86
+ */
87
+ can(name) {
88
+ if (!this._hydrated)
89
+ return false;
90
+ return this._cache[name] === true;
91
+ }
92
+ /** All cached entitlements (shallow copy). */
93
+ all() {
94
+ return { ...this._cache };
95
+ }
96
+ /**
97
+ * Subscribe to snapshot changes. Returns an unsubscribe fn.
98
+ * Does NOT invoke immediately with the current snapshot — the consumer can
99
+ * read `all()` synchronously if it needs the initial value.
100
+ */
101
+ subscribe(cb) {
102
+ this._subscribers.add(cb);
103
+ return () => {
104
+ this._subscribers.delete(cb);
105
+ };
106
+ }
107
+ /** Test-only: clear cache + listeners. */
108
+ __resetForTests() {
109
+ this._cache = {};
110
+ this._hydrated = false;
111
+ this._hydrating = false;
112
+ this._subscribers.clear();
113
+ this._opts = null;
114
+ }
115
+ _notify() {
116
+ const snap = this.all();
117
+ for (const cb of this._subscribers) {
118
+ try {
119
+ cb(snap);
120
+ }
121
+ catch {
122
+ // ignore — listener errors must not break the cache
123
+ }
124
+ }
125
+ }
126
+ }
127
+ //# sourceMappingURL=entitlements-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entitlements-store.js","sourceRoot":"","sources":["../../src/billing/entitlements-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Logger } from '../logger.js';
2
+ import type { BillingSubscriptionState, MountOptions } from './types.js';
3
+ /**
4
+ * Pure, isomorphic read of `GET /billing/state`. No store, no realtime — server
5
+ * plugins (bridge-nextjs) call this directly; the client store's `mount()` wraps
6
+ * it. A billing-locked workspace throws `BillingLockedError` via the http chokepoint.
7
+ */
8
+ export declare function fetchBillingState(opts: MountOptions, logger?: Logger): Promise<BillingSubscriptionState>;
9
+ //# sourceMappingURL=fetch-billing-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-billing-state.d.ts","sourceRoot":"","sources":["../../src/billing/fetch-billing-state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAQzE;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,YAAY,EAClB,MAAM,GAAE,MAAmB,GAC1B,OAAO,CAAC,wBAAwB,CAAC,CAanC"}
@@ -0,0 +1,22 @@
1
+ import { httpFetch } from '../http.js';
2
+ const noopLogger = {
3
+ debug: () => { },
4
+ warn: () => { },
5
+ error: () => { },
6
+ };
7
+ /**
8
+ * Pure, isomorphic read of `GET /billing/state`. No store, no realtime — server
9
+ * plugins (bridge-nextjs) call this directly; the client store's `mount()` wraps
10
+ * it. A billing-locked workspace throws `BillingLockedError` via the http chokepoint.
11
+ */
12
+ export async function fetchBillingState(opts, logger = noopLogger) {
13
+ const url = `${opts.apiBaseUrl}/billing/state`;
14
+ return httpFetch(url, {
15
+ method: 'GET',
16
+ headers: {
17
+ Authorization: `Bearer ${opts.accessToken}`,
18
+ 'x-app-id': opts.appId,
19
+ },
20
+ }, logger);
21
+ }
22
+ //# sourceMappingURL=fetch-billing-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-billing-state.js","sourceRoot":"","sources":["../../src/billing/fetch-billing-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIvC,MAAM,UAAU,GAAW;IACzB,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;IACf,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAkB,EAClB,SAAiB,UAAU;IAE3B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,UAAU,gBAAgB,CAAC;IAC/C,OAAO,SAAS,CACd,GAAG,EACH;QACE,MAAM,EAAE,KAAK;QACb,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,IAAI,CAAC,WAAW,EAAE;YAC3C,UAAU,EAAE,IAAI,CAAC,KAAK;SACvB;KACF,EACD,MAAM,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { BillingLockedPayload } from './types.js';
2
+ type LockHandler = (payload: BillingLockedPayload) => void;
3
+ export declare function setBillingLockHandler(fn: LockHandler | null): void;
4
+ export declare function emitBillingLock(payload: BillingLockedPayload): void;
5
+ export {};
6
+ //# sourceMappingURL=lock-signal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock-signal.d.ts","sourceRoot":"","sources":["../../src/billing/lock-signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAMvD,KAAK,WAAW,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAI3D,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAElE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAEnE"}
@@ -0,0 +1,8 @@
1
+ let handler = null;
2
+ export function setBillingLockHandler(fn) {
3
+ handler = fn;
4
+ }
5
+ export function emitBillingLock(payload) {
6
+ handler?.(payload);
7
+ }
8
+ //# sourceMappingURL=lock-signal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock-signal.js","sourceRoot":"","sources":["../../src/billing/lock-signal.ts"],"names":[],"mappings":"AAQA,IAAI,OAAO,GAAuB,IAAI,CAAC;AAEvC,MAAM,UAAU,qBAAqB,CAAC,EAAsB;IAC1D,OAAO,GAAG,EAAE,CAAC;AACf,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAA6B;IAC3D,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC"}
@@ -0,0 +1,86 @@
1
+ import type { Logger } from '../logger.js';
2
+ import type { QuotaUpdatedMessage, RealtimeClient } from '../flags/realtime.js';
3
+ /** Public quota snapshot shape exposed to SDK consumers. */
4
+ export interface QuotaSnapshot {
5
+ metric: string;
6
+ used: number;
7
+ limit: number;
8
+ remaining: number;
9
+ /** Convenience: `used / limit` clamped to [0, +inf). */
10
+ percent_used: number;
11
+ /**
12
+ * US-12 — server-driven per-metric policy.
13
+ * - `metered` → Stripe bills the overage automatically; NO entitlement
14
+ * produced; UI shows "metered usage" copy.
15
+ * - `hard` → entitlement flips to false at the cap; UI gates on
16
+ * `useBridge().entitlements.can(<key>)` instead of on the counter.
17
+ *
18
+ * Default for backward compatibility (pre-US-12 server payload missing
19
+ * `policy`): `'metered'`.
20
+ */
21
+ policy: 'hard' | 'metered';
22
+ warningLevel: null | 'approaching' | 'critical';
23
+ /** Display label. US-11 uses the raw metric key; framework wrappers can override. */
24
+ label: string;
25
+ }
26
+ type Listener = (metric: string, snap: QuotaSnapshot | undefined) => void;
27
+ interface MountOptions {
28
+ apiBaseUrl: string;
29
+ /** Bearer access token. Null when unauthenticated — hydrate is skipped. */
30
+ accessToken: string | null;
31
+ appId: string;
32
+ }
33
+ export declare class QuotaStore {
34
+ private _snapshots;
35
+ private _hydrating;
36
+ private _listeners;
37
+ /**
38
+ * Options used for hydration HTTP. Set via `configure()` before the first
39
+ * `get(metric)` call; otherwise hydration is skipped and only live pushes
40
+ * populate the cache.
41
+ */
42
+ private _opts;
43
+ private _logger;
44
+ /** Wire HTTP options + optional logger. Framework wrappers call this once. */
45
+ configure(opts: MountOptions, logger?: Logger): void;
46
+ /** Current cached snapshot for a metric, or undefined while hydrating / unconfigured. */
47
+ get(metric: string): QuotaSnapshot | undefined;
48
+ /** All cached snapshots. */
49
+ getAll(): Map<string, QuotaSnapshot>;
50
+ /** Subscribe to per-metric snapshot changes. Returns an unsubscribe fn. */
51
+ subscribe(listener: Listener): () => void;
52
+ /**
53
+ * Lazy hydration entry point. On the first call for a metric, kicks off a
54
+ * `GET /usage/quota/:metric` round-trip and marks the metric as hydrating.
55
+ * Subsequent calls are no-ops until the response arrives or a live push
56
+ * lands.
57
+ *
58
+ * Non-blocking — returns the current cached snapshot (or undefined) and
59
+ * lets the fetch resolve asynchronously. Consumers re-render via the
60
+ * subscriber notification.
61
+ */
62
+ ensureHydrated(metric: string): QuotaSnapshot | undefined;
63
+ /**
64
+ * Apply a live `quota.updated` payload to the cache. Last-write-wins;
65
+ * notifies subscribers. Hydration is implicitly cleared if it was pending.
66
+ */
67
+ applyQuotaUpdated(msg: QuotaUpdatedMessage): void;
68
+ /**
69
+ * Apply the result of an initial-hydration REST fetch. Same semantics as a
70
+ * live push but driven by the server's `GET /usage/quota/:metric` reply.
71
+ */
72
+ applyInitialSnapshot(metric: string, snapshot: (Omit<QuotaSnapshot, 'percent_used' | 'policy' | 'label'> & {
73
+ policy?: 'hard' | 'metered';
74
+ }) | null): void;
75
+ /**
76
+ * Wire the store to a RealtimeClient so `quota.updated` pushes flow into
77
+ * `applyQuotaUpdated`. Idempotent: re-attaching replaces the hook.
78
+ */
79
+ attach(rt: RealtimeClient): void;
80
+ /** Test-only: clear cache + listeners. */
81
+ __resetForTests(): void;
82
+ private _notify;
83
+ private _fetchSnapshot;
84
+ }
85
+ export {};
86
+ //# sourceMappingURL=quota-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota-store.d.ts","sourceRoot":"","sources":["../../src/billing/quota-store.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEhF,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;;;;OASG;IACH,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,YAAY,EAAE,IAAI,GAAG,aAAa,GAAG,UAAU,CAAC;IAChD,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,KAAK,QAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,SAAS,KAAK,IAAI,CAAC;AAE1E,UAAU,YAAY;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAQD,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,UAAU,CAAuB;IACzC;;;;OAIG;IACH,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,OAAO,CAAsB;IAErC,8EAA8E;IAC9E,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,GAAE,MAAmB,GAAG,IAAI;IAKhE,yFAAyF;IACzF,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI9C,4BAA4B;IAC5B,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC;IAIpC,2EAA2E;IAC3E,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI;IAOzC;;;;;;;;;OASG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAUzD;;;OAGG;IACH,iBAAiB,CAAC,GAAG,EAAE,mBAAmB,GAAG,IAAI;IAmBjD;;;OAGG;IACH,oBAAoB,CAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EACJ,CAAC,IAAI,CAAC,aAAa,EAAE,cAAc,GAAG,QAAQ,GAAG,OAAO,CAAC,GAAG;QAC1D,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC7B,CAAC,GACF,IAAI,GACP,IAAI;IAyBP;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,cAAc,GAAG,IAAI;IAIhC,0CAA0C;IAC1C,eAAe,IAAI,IAAI;IAOvB,OAAO,CAAC,OAAO;YAUD,cAAc;CAkD7B"}