@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,180 @@
1
+ // Billing 2.0 US-11 (TBP-263) — SDK quota cache + reactive surface.
2
+ //
3
+ // In-memory store keyed on metric. The store is the single source of truth
4
+ // for `useBridge().quota(metric)`:
5
+ // - First call for a metric → hydrate via `GET /usage/quota/:metric`, mark
6
+ // the entry as "loading" so subscribers see `undefined` until the server
7
+ // replies.
8
+ // - Live `quota.updated` pushes (via RealtimeClient.setOnQuotaUpdated) replace
9
+ // the cached snapshot — last-write-wins.
10
+ // - Subscribers receive snapshot notifications on every cache mutation.
11
+ //
12
+ // Framework-agnostic. bridge-svelte wraps `subscribe()` in a Svelte 5 rune.
13
+ import { httpFetch } from '../http.js';
14
+ const noopLogger = {
15
+ debug: () => { },
16
+ warn: () => { },
17
+ error: () => { },
18
+ };
19
+ export class QuotaStore {
20
+ _snapshots = new Map();
21
+ _hydrating = new Set();
22
+ _listeners = new Set();
23
+ /**
24
+ * Options used for hydration HTTP. Set via `configure()` before the first
25
+ * `get(metric)` call; otherwise hydration is skipped and only live pushes
26
+ * populate the cache.
27
+ */
28
+ _opts = null;
29
+ _logger = noopLogger;
30
+ /** Wire HTTP options + optional logger. Framework wrappers call this once. */
31
+ configure(opts, logger = noopLogger) {
32
+ this._opts = opts;
33
+ this._logger = logger;
34
+ }
35
+ /** Current cached snapshot for a metric, or undefined while hydrating / unconfigured. */
36
+ get(metric) {
37
+ return this._snapshots.get(metric);
38
+ }
39
+ /** All cached snapshots. */
40
+ getAll() {
41
+ return new Map(this._snapshots);
42
+ }
43
+ /** Subscribe to per-metric snapshot changes. Returns an unsubscribe fn. */
44
+ subscribe(listener) {
45
+ this._listeners.add(listener);
46
+ return () => {
47
+ this._listeners.delete(listener);
48
+ };
49
+ }
50
+ /**
51
+ * Lazy hydration entry point. On the first call for a metric, kicks off a
52
+ * `GET /usage/quota/:metric` round-trip and marks the metric as hydrating.
53
+ * Subsequent calls are no-ops until the response arrives or a live push
54
+ * lands.
55
+ *
56
+ * Non-blocking — returns the current cached snapshot (or undefined) and
57
+ * lets the fetch resolve asynchronously. Consumers re-render via the
58
+ * subscriber notification.
59
+ */
60
+ ensureHydrated(metric) {
61
+ const cached = this._snapshots.get(metric);
62
+ if (cached)
63
+ return cached;
64
+ if (this._hydrating.has(metric))
65
+ return undefined;
66
+ if (!this._opts || !this._opts.accessToken)
67
+ return undefined;
68
+ this._hydrating.add(metric);
69
+ void this._fetchSnapshot(metric);
70
+ return undefined;
71
+ }
72
+ /**
73
+ * Apply a live `quota.updated` payload to the cache. Last-write-wins;
74
+ * notifies subscribers. Hydration is implicitly cleared if it was pending.
75
+ */
76
+ applyQuotaUpdated(msg) {
77
+ const snap = {
78
+ metric: msg.metric,
79
+ used: msg.used,
80
+ limit: msg.limit,
81
+ remaining: msg.remaining,
82
+ percent_used: msg.limit > 0 ? msg.used / msg.limit : 0,
83
+ // US-12 — server now publishes `policy`; fall back to `'metered'` when
84
+ // an older bridge-api hasn't shipped US-12 yet so existing UI stays
85
+ // visually identical.
86
+ policy: msg.policy === 'hard' ? 'hard' : 'metered',
87
+ warningLevel: msg.warningLevel ?? null,
88
+ label: msg.metric,
89
+ };
90
+ this._snapshots.set(msg.metric, snap);
91
+ this._hydrating.delete(msg.metric);
92
+ this._notify(msg.metric, snap);
93
+ }
94
+ /**
95
+ * Apply the result of an initial-hydration REST fetch. Same semantics as a
96
+ * live push but driven by the server's `GET /usage/quota/:metric` reply.
97
+ */
98
+ applyInitialSnapshot(metric, snapshot) {
99
+ this._hydrating.delete(metric);
100
+ if (!snapshot) {
101
+ // Server returned null → no quota configured. Notify with undefined so
102
+ // subscribers can show an "unmetered" UI state without an extra check.
103
+ this._snapshots.delete(metric);
104
+ this._notify(metric, undefined);
105
+ return;
106
+ }
107
+ const snap = {
108
+ metric,
109
+ used: snapshot.used,
110
+ limit: snapshot.limit,
111
+ remaining: snapshot.remaining,
112
+ percent_used: snapshot.limit > 0 ? snapshot.used / snapshot.limit : 0,
113
+ // US-12 — accept the server-supplied policy; fall back to `'metered'`
114
+ // for older bridge-api responses.
115
+ policy: snapshot.policy === 'hard' ? 'hard' : 'metered',
116
+ warningLevel: snapshot.warningLevel ?? null,
117
+ label: metric,
118
+ };
119
+ this._snapshots.set(metric, snap);
120
+ this._notify(metric, snap);
121
+ }
122
+ /**
123
+ * Wire the store to a RealtimeClient so `quota.updated` pushes flow into
124
+ * `applyQuotaUpdated`. Idempotent: re-attaching replaces the hook.
125
+ */
126
+ attach(rt) {
127
+ rt.setOnQuotaUpdated((msg) => this.applyQuotaUpdated(msg));
128
+ }
129
+ /** Test-only: clear cache + listeners. */
130
+ __resetForTests() {
131
+ this._snapshots.clear();
132
+ this._hydrating.clear();
133
+ this._listeners.clear();
134
+ this._opts = null;
135
+ }
136
+ _notify(metric, snap) {
137
+ for (const listener of this._listeners) {
138
+ try {
139
+ listener(metric, snap);
140
+ }
141
+ catch {
142
+ // ignore — listener errors must not break the cache
143
+ }
144
+ }
145
+ }
146
+ async _fetchSnapshot(metric) {
147
+ if (!this._opts || !this._opts.accessToken) {
148
+ this._hydrating.delete(metric);
149
+ return;
150
+ }
151
+ const url = `${this._opts.apiBaseUrl.replace(/\/+$/, '')}/usage/quota/${encodeURIComponent(metric)}`;
152
+ try {
153
+ const body = await httpFetch(url, {
154
+ method: 'GET',
155
+ headers: {
156
+ Authorization: `Bearer ${this._opts.accessToken}`,
157
+ 'x-app-id': this._opts.appId,
158
+ },
159
+ }, this._logger);
160
+ this.applyInitialSnapshot(metric, body
161
+ ? {
162
+ metric: body.metric,
163
+ used: body.used,
164
+ limit: body.limit,
165
+ remaining: body.remaining,
166
+ warningLevel: body.warningLevel,
167
+ policy: body.policy,
168
+ }
169
+ : null);
170
+ }
171
+ catch (err) {
172
+ // Hydration is best-effort. Drop the hydrating mark so a later push or
173
+ // a manual retry can re-populate. Log at warn so the consumer can
174
+ // diagnose if every fetch is failing.
175
+ this._logger.warn('[bridge.quota] hydrate failed', err instanceof Error ? err.message : err);
176
+ this._hydrating.delete(metric);
177
+ }
178
+ }
179
+ }
180
+ //# sourceMappingURL=quota-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota-store.js","sourceRoot":"","sources":["../../src/billing/quota-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ export type BillingSubscriptionStatus = 'trial' | 'active' | 'past_due' | 'cancel_at_period_end' | 'canceled';
2
+ export type BillingSeverity = 'info' | 'warn' | 'critical' | 'locked';
3
+ export type PastDueReason = 'card_declined' | 'trial_expired' | 'card_removed' | null;
4
+ export interface BillingPlanRef {
5
+ slug: string;
6
+ name: string;
7
+ }
8
+ /**
9
+ * Snapshot of canonical billing state exposed by `useBridge().subscription`.
10
+ * Phase A (US-1, US-2) ships `plan` + `status`. Phase B (US-5..US-9) adds
11
+ * the optional fields below as lifecycle events arrive.
12
+ */
13
+ export interface BillingSubscriptionState {
14
+ plan: BillingPlanRef;
15
+ status: BillingSubscriptionStatus;
16
+ pastDueReason?: PastDueReason;
17
+ cardLast4?: string;
18
+ hasCardOnFile?: boolean;
19
+ endsAt?: string;
20
+ daysLeft?: number;
21
+ nextRetryAt?: string;
22
+ finalRetryAt?: string;
23
+ gateEngaged?: boolean;
24
+ renewsAt?: string;
25
+ /** Where to send a locked workspace to recover access (e.g. `/billing`). */
26
+ recoveryUrl?: string;
27
+ }
28
+ /**
29
+ * Wire contract for a billing-locked 402 response. Single source of truth shared
30
+ * by the SDK (detection) and bridge-api (emission, Step 2). `reason` is the
31
+ * detection discriminator — any 402 carrying `reason: 'billing_locked'` is a lock.
32
+ */
33
+ export interface BillingLockedPayload {
34
+ reason: 'billing_locked';
35
+ billing: {
36
+ status: BillingSubscriptionStatus;
37
+ gateEngaged: boolean;
38
+ recoveryUrl?: string;
39
+ };
40
+ }
41
+ export interface BillingSubscriptionSnapshot {
42
+ state: BillingSubscriptionState | null;
43
+ loading: boolean;
44
+ error: string | null;
45
+ }
46
+ export interface MountOptions {
47
+ apiBaseUrl: string;
48
+ accessToken: string;
49
+ appId: string;
50
+ }
51
+ /**
52
+ * Canonical UI states derived from `BillingSubscriptionState` for the
53
+ * `<BridgeBillingNotice />` component. Pure derivation — not stored on
54
+ * the snapshot.
55
+ */
56
+ export type BillingNoticeState = 'active' | 'trial_active' | 'trial_ending_soon' | 'cancel_at_period_end' | 'canceled' | 'past_due' | 'past_due_trial' | 'dunning_active' | 'dunning_final_retry' | 'dunning_exhausted';
57
+ /**
58
+ * Derive the UI state from the canonical subscription snapshot. Used by
59
+ * `<BridgeBillingNotice />` and admin-ui badges to pick the right copy +
60
+ * severity tokens. Pure function — no side effects.
61
+ */
62
+ export declare function deriveNoticeState(state: BillingSubscriptionState | null): BillingNoticeState;
63
+ export declare function deriveSeverity(noticeState: BillingNoticeState): BillingSeverity;
64
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/billing/types.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,yBAAyB,GACjC,OAAO,GACP,QAAQ,GACR,UAAU,GACV,sBAAsB,GACtB,UAAU,CAAC;AAEf,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEtE,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,eAAe,GAAG,cAAc,GAAG,IAAI,CAAC;AAEtF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,yBAAyB,CAAC;IAElC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE;QACP,MAAM,EAAE,yBAAyB,CAAC;QAClC,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,wBAAwB,GAAG,IAAI,CAAC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,GACR,cAAc,GACd,mBAAmB,GACnB,sBAAsB,GACtB,UAAU,GACV,UAAU,GACV,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,GACrB,mBAAmB,CAAC;AAExB;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,GAAG,IAAI,GAAG,kBAAkB,CAgB5F;AAeD,wBAAgB,cAAc,CAAC,WAAW,EAAE,kBAAkB,GAAG,eAAe,CAE/E"}
@@ -0,0 +1,49 @@
1
+ // Billing 2.0 reactive surface types — parallel to FF 2.0.
2
+ // Do NOT merge with existing `SubscriptionStatus` (Stripe-direct path);
3
+ // unification is REF-1 post-feature.
4
+ /**
5
+ * Derive the UI state from the canonical subscription snapshot. Used by
6
+ * `<BridgeBillingNotice />` and admin-ui badges to pick the right copy +
7
+ * severity tokens. Pure function — no side effects.
8
+ */
9
+ export function deriveNoticeState(state) {
10
+ if (!state)
11
+ return 'active';
12
+ if (state.gateEngaged)
13
+ return 'dunning_exhausted';
14
+ if (state.status === 'canceled')
15
+ return 'canceled';
16
+ if (state.status === 'cancel_at_period_end')
17
+ return 'cancel_at_period_end';
18
+ if (state.status === 'past_due') {
19
+ if (state.pastDueReason === 'trial_expired')
20
+ return 'past_due_trial';
21
+ if (state.finalRetryAt)
22
+ return 'dunning_final_retry';
23
+ if (state.nextRetryAt)
24
+ return 'dunning_active';
25
+ return 'past_due';
26
+ }
27
+ if (state.status === 'trial') {
28
+ if (state.daysLeft !== undefined && state.daysLeft <= 3)
29
+ return 'trial_ending_soon';
30
+ return 'trial_active';
31
+ }
32
+ return 'active';
33
+ }
34
+ const SEVERITY_BY_STATE = {
35
+ active: 'info',
36
+ trial_active: 'info',
37
+ trial_ending_soon: 'warn',
38
+ cancel_at_period_end: 'info',
39
+ canceled: 'warn',
40
+ past_due: 'warn',
41
+ past_due_trial: 'warn',
42
+ dunning_active: 'warn',
43
+ dunning_final_retry: 'critical',
44
+ dunning_exhausted: 'locked',
45
+ };
46
+ export function deriveSeverity(noticeState) {
47
+ return SEVERITY_BY_STATE[noticeState];
48
+ }
49
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/billing/types.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,wEAAwE;AACxE,qCAAqC;AAmFrC;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAsC;IACtE,IAAI,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAC;IAC5B,IAAI,KAAK,CAAC,WAAW;QAAE,OAAO,mBAAmB,CAAC;IAClD,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU;QAAE,OAAO,UAAU,CAAC;IACnD,IAAI,KAAK,CAAC,MAAM,KAAK,sBAAsB;QAAE,OAAO,sBAAsB,CAAC;IAC3E,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QAChC,IAAI,KAAK,CAAC,aAAa,KAAK,eAAe;YAAE,OAAO,gBAAgB,CAAC;QACrE,IAAI,KAAK,CAAC,YAAY;YAAE,OAAO,qBAAqB,CAAC;QACrD,IAAI,KAAK,CAAC,WAAW;YAAE,OAAO,gBAAgB,CAAC;QAC/C,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC;YAAE,OAAO,mBAAmB,CAAC;QACpF,OAAO,cAAc,CAAC;IACxB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,iBAAiB,GAAgD;IACrE,MAAM,EAAE,MAAM;IACd,YAAY,EAAE,MAAM;IACpB,iBAAiB,EAAE,MAAM;IACzB,oBAAoB,EAAE,MAAM;IAC5B,QAAQ,EAAE,MAAM;IAChB,QAAQ,EAAE,MAAM;IAChB,cAAc,EAAE,MAAM;IACtB,cAAc,EAAE,MAAM;IACtB,mBAAmB,EAAE,UAAU;IAC/B,iBAAiB,EAAE,QAAQ;CAC5B,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,WAA+B;IAC5D,OAAO,iBAAiB,CAAC,WAAW,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,131 @@
1
+ import type { BillingLifecycleMessage, EntitlementsChangedMessage, QuotaUpdatedMessage, RealtimeClient, SubscriptionPlanChangedMessage } from '../flags/realtime.js';
2
+ import { BridgeSubscription } from './bridge-subscription.js';
3
+ import { EntitlementsStore, type EntitlementSnapshot } from './entitlements-store.js';
4
+ import { QuotaStore, type QuotaSnapshot } from './quota-store.js';
5
+ import type { BillingNoticeState, BillingSeverity } from './types.js';
6
+ /** Resolved billing-gate view for the current snapshot. */
7
+ export interface BillingGateState {
8
+ locked: boolean;
9
+ severity: BillingSeverity;
10
+ noticeState: BillingNoticeState;
11
+ recoveryUrl?: string;
12
+ }
13
+ /**
14
+ * BillingLifecycleMessage's discriminator union includes `'entitlements.changed'`
15
+ * as a signal-only kind. US-12 introduces a parallel payload-carrying variant
16
+ * (EntitlementsChangedMessage) routed through `setOnEntitlementsChanged`. The
17
+ * dev-handler table accepts either shape under that key — the dispatch site
18
+ * routes per-message based on the presence of `entitlements`.
19
+ */
20
+ type EntitlementsChangedHandlerArg = BillingLifecycleMessage | EntitlementsChangedMessage;
21
+ export type BillingEventHandlers = {
22
+ [K in Exclude<BillingLifecycleMessage['kind'], 'entitlements.changed'>]?: (msg: BillingLifecycleMessage) => void;
23
+ } & {
24
+ /**
25
+ * TBP-360 — handler for canonical plan-change pushes. Lives on its own kind
26
+ * (SubscriptionPlanChangedMessage) outside `BillingLifecycleMessage`, but is
27
+ * exposed through the same multi-subscriber dispatch so bridge-svelte's
28
+ * `bridge.events.handle({ 'subscription.plan_changed': fn })` works.
29
+ */
30
+ 'subscription.plan_changed'?: (msg: SubscriptionPlanChangedMessage) => void;
31
+ /**
32
+ * Billing 2.0 US-11 — handler for live quota counter pushes. Mirrors the
33
+ * lifecycle-event handler pattern: framework wrappers + dev consumers can
34
+ * both register; both fire on every matching event.
35
+ */
36
+ 'quota.updated'?: (msg: QuotaUpdatedMessage) => void;
37
+ /**
38
+ * Billing 2.0 US-12 — handler for live entitlement snapshot pushes. Fires
39
+ * alongside the EntitlementsStore cache replacement so dev consumers
40
+ * (analytics, audit, etc.) can react to the same event the UI gates on.
41
+ * Receives the payload-carrying `EntitlementsChangedMessage` (US-12 shape);
42
+ * the legacy signal-only lifecycle variant fans out through the lifecycle
43
+ * dispatch like other lifecycle kinds.
44
+ */
45
+ 'entitlements.changed'?: (msg: EntitlementsChangedHandlerArg) => void;
46
+ };
47
+ /**
48
+ * Billing 2.0 US-12 — top-level entitlements accessor. Mirrors the
49
+ * fail-closed semantics of `EntitlementsStore.can` so consumers can write
50
+ * `bridge.entitlements.can('ai_completions')` without holding a store ref.
51
+ */
52
+ export interface UseBridgeEntitlementsApi {
53
+ can(name: string): boolean;
54
+ all(): EntitlementSnapshot;
55
+ }
56
+ export interface UseBridgeApi {
57
+ subscription: BridgeSubscription;
58
+ /**
59
+ * Billing 2.0 US-11 — reactive quota snapshot for a single metric. The
60
+ * first call for a given metric kicks off a `GET /usage/quota/:metric`
61
+ * hydration; while in flight, returns `undefined`. Once hydrated (and on
62
+ * every subsequent `quota.updated` push), returns the full snapshot.
63
+ *
64
+ * Framework wrappers (bridge-svelte's `useBridge().quota(metric)`) wrap
65
+ * this in a runes-aware proxy so Svelte components re-render on push.
66
+ */
67
+ quota(metric: string): QuotaSnapshot | undefined;
68
+ /**
69
+ * Direct access to the underlying QuotaStore — framework wrappers use this
70
+ * to subscribe to push notifications and re-render on cache mutations.
71
+ */
72
+ readonly quotas: QuotaStore;
73
+ /**
74
+ * Billing 2.0 US-12 — top-level entitlements accessor (`bridge.entitlements.can(name)`).
75
+ * Fail-closed: returns false until hydration completes and the key is
76
+ * explicitly true.
77
+ */
78
+ readonly entitlements: UseBridgeEntitlementsApi;
79
+ /**
80
+ * Direct access to the underlying EntitlementsStore — framework wrappers
81
+ * use this to subscribe to snapshot replacements and re-render UI.
82
+ */
83
+ readonly entitlementsStore: EntitlementsStore;
84
+ /**
85
+ * Register dev-supplied side-effect handlers for canonical lifecycle events
86
+ * (analytics, Slack, audit, etc.) — separate from the UI rendering path
87
+ * which is owned by `<BridgeBillingNotice />`. Per locked decision #9, dev
88
+ * handlers run alongside UI handlers; both fire on every matching event.
89
+ *
90
+ * Caller must call `attachToRealtimeClient(rt)` once (typically done by
91
+ * the framework wrapper) before any handlers can fire.
92
+ */
93
+ handle(handlers: BillingEventHandlers): () => void;
94
+ /**
95
+ * Wire the surface to a Milestone K RealtimeClient. Framework wrappers
96
+ * (bridge-svelte / bridge-react / ...) call this once during init.
97
+ */
98
+ attachToRealtimeClient(rt: RealtimeClient): void;
99
+ /**
100
+ * Billing 2.0 soft gate — true when the workspace is billing-locked
101
+ * (`gateEngaged`). Fail-closed: false until state is known. Use for
102
+ * proactively disabling write actions in the UI.
103
+ */
104
+ isLocked(): boolean;
105
+ /**
106
+ * Resolved gate view (locked + severity + notice state + recoveryUrl) derived
107
+ * from the current snapshot. Drives custom lockscreens without re-deriving.
108
+ */
109
+ gateState(): BillingGateState;
110
+ /**
111
+ * Imperative guard — throws `BillingLockedError` if the workspace is locked.
112
+ * Node-safe; use to wrap a write before it leaves the client/server.
113
+ */
114
+ assertNotLocked(): void;
115
+ }
116
+ /**
117
+ * Billing 2.0 reactive SDK shell. Phase A exposes `.subscription` reactivity;
118
+ * Phase B adds `.handle(...)` for dev-supplied side-effect handlers + the
119
+ * `attachToRealtimeClient(rt)` wiring entry point.
120
+ *
121
+ * Parallel to FF 2.0's existing reactive surface — do NOT unify. REF-1
122
+ * (post-feature) folds them together.
123
+ */
124
+ export declare function useBridge(): UseBridgeApi;
125
+ /**
126
+ * Test-only: drop the singleton so each test starts with a fresh surface.
127
+ * Not exported from the package entry — internal to the test suite.
128
+ */
129
+ export declare function __resetUseBridgeForTests(): void;
130
+ export {};
131
+ //# sourceMappingURL=use-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-bridge.d.ts","sourceRoot":"","sources":["../../src/billing/use-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,0BAA0B,EAC1B,mBAAmB,EACnB,cAAc,EACd,8BAA8B,EAC/B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EACL,iBAAiB,EACjB,KAAK,mBAAmB,EACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEtE,2DAA2D;AAC3D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,eAAe,CAAC;IAC1B,WAAW,EAAE,kBAAkB,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,KAAK,6BAA6B,GAC9B,uBAAuB,GACvB,0BAA0B,CAAC;AAE/B,MAAM,MAAM,oBAAoB,GAAG;KAChC,CAAC,IAAI,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC,EAAE,CACxE,GAAG,EAAE,uBAAuB,KACzB,IAAI;CACV,GAAG;IACF;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,CAAC,GAAG,EAAE,8BAA8B,KAAK,IAAI,CAAC;IAC5E;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACrD;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,6BAA6B,KAAK,IAAI,CAAC;CACvE,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,GAAG,IAAI,mBAAmB,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,kBAAkB,CAAC;IACjC;;;;;;;;OAQG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC;IACjD;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,wBAAwB,CAAC;IAChD;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAC9C;;;;;;;;OAQG;IACH,MAAM,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI,CAAC;IACnD;;;OAGG;IACH,sBAAsB,CAAC,EAAE,EAAE,cAAc,GAAG,IAAI,CAAC;IACjD;;;;OAIG;IACH,QAAQ,IAAI,OAAO,CAAC;IACpB;;;OAGG;IACH,SAAS,IAAI,gBAAgB,CAAC;IAC9B;;;OAGG;IACH,eAAe,IAAI,IAAI,CAAC;CACzB;AAQD;;;;;;;GAOG;AACH,wBAAgB,SAAS,IAAI,YAAY,CA2IxC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAa/C"}
@@ -0,0 +1,181 @@
1
+ import { BillingLockedError } from '../errors.js';
2
+ import { BridgeSubscription } from './bridge-subscription.js';
3
+ import { setBillingLockHandler } from './lock-signal.js';
4
+ import { EntitlementsStore, } from './entitlements-store.js';
5
+ import { QuotaStore } from './quota-store.js';
6
+ import { deriveNoticeState, deriveSeverity } from './types.js';
7
+ let _singleton = null;
8
+ let _devHandlers = [];
9
+ let _attachedRt;
10
+ let _quotaStore = null;
11
+ let _entitlementsStore = null;
12
+ /**
13
+ * Billing 2.0 reactive SDK shell. Phase A exposes `.subscription` reactivity;
14
+ * Phase B adds `.handle(...)` for dev-supplied side-effect handlers + the
15
+ * `attachToRealtimeClient(rt)` wiring entry point.
16
+ *
17
+ * Parallel to FF 2.0's existing reactive surface — do NOT unify. REF-1
18
+ * (post-feature) folds them together.
19
+ */
20
+ export function useBridge() {
21
+ if (!_singleton) {
22
+ const subscription = new BridgeSubscription();
23
+ const quotaStore = new QuotaStore();
24
+ const entitlementsStore = new EntitlementsStore();
25
+ _quotaStore = quotaStore;
26
+ _entitlementsStore = entitlementsStore;
27
+ _singleton = {
28
+ subscription,
29
+ quotas: quotaStore,
30
+ quota(metric) {
31
+ return quotaStore.ensureHydrated(metric);
32
+ },
33
+ entitlementsStore,
34
+ entitlements: {
35
+ can(name) {
36
+ return entitlementsStore.can(name);
37
+ },
38
+ all() {
39
+ return entitlementsStore.all();
40
+ },
41
+ },
42
+ handle(handlers) {
43
+ _devHandlers.push(handlers);
44
+ // Idempotent attach to existing rt if available
45
+ return () => {
46
+ _devHandlers = _devHandlers.filter((h) => h !== handlers);
47
+ };
48
+ },
49
+ attachToRealtimeClient(rt) {
50
+ if (_attachedRt === rt)
51
+ return;
52
+ _attachedRt = rt;
53
+ subscription.attach(rt);
54
+ // TBP-360 — RealtimeClient stores ONE hook per kind; subscription.attach
55
+ // above already set the plan-change hook. Replace it with a composite
56
+ // that applies the subscription patch AND fans out to dev handlers
57
+ // registered via `useBridge().handle({ 'subscription.plan_changed' })`.
58
+ rt.setOnSubscriptionPlanChanged((msg) => {
59
+ subscription._applyPlanChanged(msg);
60
+ for (const handlers of _devHandlers) {
61
+ const fn = handlers['subscription.plan_changed'];
62
+ if (fn) {
63
+ try {
64
+ fn(msg);
65
+ }
66
+ catch {
67
+ // ignore — dev handler errors don't affect SDK state
68
+ }
69
+ }
70
+ }
71
+ });
72
+ // Funnel lifecycle events to dev handlers AFTER the subscription
73
+ // surface has patched its own state.
74
+ rt.setOnBillingLifecycle((msg) => {
75
+ // Patch the snapshot first (BridgeSubscription.attach above already
76
+ // registered its own hook; the RealtimeClient stores ONE hook per
77
+ // kind, so re-set means we replace it). We replicate the snapshot
78
+ // patch + dev fan-out here so both run.
79
+ subscription._applyLifecycle(msg);
80
+ for (const handlers of _devHandlers) {
81
+ // `handlers[msg.kind]` is union-typed across kinds; cast at the
82
+ // call site so each handler receives its declared message shape.
83
+ const fn = handlers[msg.kind];
84
+ if (fn) {
85
+ try {
86
+ fn(msg);
87
+ }
88
+ catch {
89
+ // ignore — dev handler errors don't affect SDK state
90
+ }
91
+ }
92
+ }
93
+ });
94
+ // US-11 — wire quota.updated pushes into the store AND fan out to
95
+ // dev handlers that registered for 'quota.updated'.
96
+ rt.setOnQuotaUpdated((msg) => {
97
+ quotaStore.applyQuotaUpdated(msg);
98
+ for (const handlers of _devHandlers) {
99
+ const fn = handlers['quota.updated'];
100
+ if (fn) {
101
+ try {
102
+ fn(msg);
103
+ }
104
+ catch {
105
+ // ignore — dev handler errors don't affect SDK state
106
+ }
107
+ }
108
+ }
109
+ });
110
+ // US-12 — wire entitlements.changed pushes into the store AND fan
111
+ // out to dev handlers that registered for 'entitlements.changed'.
112
+ rt.setOnEntitlementsChanged((msg) => {
113
+ entitlementsStore.applyEntitlementsChanged(msg.entitlements);
114
+ for (const handlers of _devHandlers) {
115
+ const fn = handlers['entitlements.changed'];
116
+ if (fn) {
117
+ try {
118
+ fn(msg);
119
+ }
120
+ catch {
121
+ // ignore — dev handler errors don't affect SDK state
122
+ }
123
+ }
124
+ }
125
+ });
126
+ // US-12 — initial REST hydration on attach. Fire-and-forget; failures
127
+ // are logged at warn inside the store. Subsequent pushes overwrite.
128
+ void entitlementsStore.hydrate();
129
+ },
130
+ isLocked() {
131
+ return subscription.snapshot().state?.gateEngaged ?? false;
132
+ },
133
+ gateState() {
134
+ const state = subscription.snapshot().state;
135
+ const noticeState = deriveNoticeState(state);
136
+ return {
137
+ locked: state?.gateEngaged ?? false,
138
+ severity: deriveSeverity(noticeState),
139
+ noticeState,
140
+ recoveryUrl: state?.recoveryUrl,
141
+ };
142
+ },
143
+ assertNotLocked() {
144
+ const state = subscription.snapshot().state;
145
+ if (state?.gateEngaged) {
146
+ throw new BillingLockedError({
147
+ reason: 'billing_locked',
148
+ billing: {
149
+ status: state.status,
150
+ gateEngaged: true,
151
+ recoveryUrl: state.recoveryUrl,
152
+ },
153
+ });
154
+ }
155
+ },
156
+ };
157
+ // Auto-flip the store when any SDK call hits a billing-locked 402, so a
158
+ // caught BillingLockedError surfaces in the notice/gate with no extra wiring.
159
+ setBillingLockHandler((payload) => subscription.markLocked(payload));
160
+ }
161
+ return _singleton;
162
+ }
163
+ /**
164
+ * Test-only: drop the singleton so each test starts with a fresh surface.
165
+ * Not exported from the package entry — internal to the test suite.
166
+ */
167
+ export function __resetUseBridgeForTests() {
168
+ _singleton = null;
169
+ _devHandlers = [];
170
+ _attachedRt = undefined;
171
+ setBillingLockHandler(null);
172
+ if (_quotaStore) {
173
+ _quotaStore.__resetForTests();
174
+ }
175
+ _quotaStore = null;
176
+ if (_entitlementsStore) {
177
+ _entitlementsStore.__resetForTests();
178
+ }
179
+ _entitlementsStore = null;
180
+ }
181
+ //# sourceMappingURL=use-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-bridge.js","sourceRoot":"","sources":["../../src/billing/use-bridge.ts"],"names":[],"mappings":"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"}