@nebulr-group/bridge-svelte 0.9.0 → 0.9.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -45
- package/dist/core/billing-store.d.ts +12 -0
- package/dist/core/billing-store.js +67 -0
- package/dist/flags/bootstrap.js +22 -0
- package/package.json +17 -6
package/README.md
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://thebridge.dev/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/thebridgedev/bridge-svelte/main/.github/assets/banner.png"><img src="https://raw.githubusercontent.com/thebridgedev/bridge-svelte/main/.github/assets/banner-light.png" alt="The Bridge for SvelteKit" width="100%"></picture></a>
|
|
3
|
+
</p>
|
|
1
4
|
|
|
2
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@nebulr-group/bridge-svelte"><img src="https://img.shields.io/npm/v/@nebulr-group/bridge-svelte?color=20006b&label=npm" alt="npm version"></a>
|
|
7
|
+
<a href="https://github.com/thebridgedev/bridge-svelte/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@nebulr-group/bridge-svelte?color=20006b" alt="MIT license"></a>
|
|
8
|
+
<a href="https://madewithsvelte.com/p/the-bridge/shield-link"><img src="https://madewithsvelte.com/storage/repo-shields/5996-shield.svg" alt="Made with Svelte"></a>
|
|
9
|
+
</p>
|
|
3
10
|
|
|
4
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://thebridge.dev/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte"><b>Website</b></a> ·
|
|
13
|
+
<a href="https://thebridge.dev/docs/quickstart/svelte/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte"><b>Quickstart</b></a> ·
|
|
14
|
+
<a href="https://thebridge.dev/docs/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte"><b>Docs</b></a> ·
|
|
15
|
+
<a href="https://thebridge.dev/docs/ai-assistants/mcp/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte"><b>Set up with your AI assistant</b></a>
|
|
16
|
+
</p>
|
|
5
17
|
|
|
18
|
+
# The Bridge for SvelteKit
|
|
6
19
|
|
|
7
|
-
|
|
20
|
+
`@nebulr-group/bridge-svelte` adds sign-in, workspaces and roles, feature flags, Stripe subscriptions and plan limits to a SvelteKit 2 + Svelte 5 app, with one `.env` line and three files.
|
|
8
21
|
|
|
9
|
-
|
|
22
|
+
**[The Bridge](https://thebridge.dev/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte)** is a hosted backend for SaaS apps. It gives you sign-in (passwords, magic links, passkeys, social login and SSO), multi-tenant workspaces with roles, Stripe subscriptions with plan limits, and feature flags, all managed from one dashboard. Your AI coding assistant can set it up for you through the [Bridge MCP server](https://thebridge.dev/docs/ai-assistants/mcp/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte).
|
|
23
|
+
|
|
24
|
+
> **Let your AI assistant set it up.** Connect the [Bridge MCP server](https://thebridge.dev/docs/ai-assistants/mcp/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte) to Claude, Cursor, Copilot or Gemini CLI and ask it to add Bridge to your app. Not using MCP? Run `npx @nebulr-group/bridge-cli guide add-login` in your project: it detects your framework from `package.json` and prints the steps for your assistant to follow. `npx @nebulr-group/bridge-cli doctor` checks the result.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
10
27
|
|
|
11
28
|
```bash
|
|
12
29
|
npm i @nebulr-group/bridge-svelte
|
|
13
30
|
```
|
|
14
31
|
|
|
15
|
-
|
|
32
|
+
## Usage
|
|
16
33
|
|
|
17
34
|
The whole integration is one `.env` line and three files:
|
|
18
35
|
|
|
@@ -52,48 +69,28 @@ A stage or local app also sets `VITE_BRIDGE_API_BASE_URL`. Add `loginRoute: '/au
|
|
|
52
69
|
|
|
53
70
|
[How Bridge works](https://github.com/thebridgedev/bridge-svelte/blob/main/learning/mechanisms.md) explains plan limits, the three UI levels and the four customisation levels; the [learning docs](https://github.com/thebridgedev/bridge-svelte/tree/main/learning) cover everything else. Coding agents: `npx @nebulr-group/bridge-cli guide svelte`.
|
|
54
71
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
npm run build
|
|
60
|
-
```
|
|
72
|
+
## Learn more
|
|
61
73
|
|
|
62
|
-
|
|
74
|
+
- [Quickstart](https://thebridge.dev/docs/quickstart/svelte/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte)
|
|
75
|
+
- [Authentication](https://thebridge.dev/docs/auth/svelte/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte)
|
|
76
|
+
- [Sign-in inside your app](https://thebridge.dev/docs/sdk-auth/svelte/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte)
|
|
77
|
+
- [Feature flags](https://thebridge.dev/docs/feature-flags/svelte/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte)
|
|
78
|
+
- [Branding](https://thebridge.dev/docs/branding/svelte/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte)
|
|
79
|
+
- [Live updates](https://thebridge.dev/docs/live-updates/svelte/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte)
|
|
80
|
+
- [Subscriptions and plan limits](https://thebridge.dev/docs/billing/how-it-works/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte)
|
|
63
81
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
# 1) Create release branch
|
|
68
|
-
git checkout -b release/v0.1.0-beta.1
|
|
69
|
-
git push -u origin release/v0.1.0-beta.1
|
|
82
|
+
## Other Bridge packages
|
|
70
83
|
|
|
71
|
-
|
|
84
|
+
| Package | For |
|
|
85
|
+
|---|---|
|
|
86
|
+
| [`@nebulr-group/bridge-react`](https://www.npmjs.com/package/@nebulr-group/bridge-react) | React |
|
|
87
|
+
| [`@nebulr-group/bridge-nextjs`](https://www.npmjs.com/package/@nebulr-group/bridge-nextjs) | Next.js |
|
|
88
|
+
| [`@nebulr-group/bridge-angular`](https://www.npmjs.com/package/@nebulr-group/bridge-angular) | Angular |
|
|
89
|
+
| [`@nebulr-group/bridge-nestjs`](https://www.npmjs.com/package/@nebulr-group/bridge-nestjs) | NestJS |
|
|
90
|
+
| [`@nebulr-group/bridge-express`](https://www.npmjs.com/package/@nebulr-group/bridge-express) | Express |
|
|
91
|
+
| [`@nebulr-group/bridge-cli`](https://www.npmjs.com/package/@nebulr-group/bridge-cli) | CLI for people and AI agents |
|
|
92
|
+
| [`@nebulr-group/bridge-auth-core`](https://www.npmjs.com/package/@nebulr-group/bridge-auth-core) | Any JavaScript app (core) |
|
|
72
93
|
|
|
73
|
-
|
|
74
|
-
git checkout main && git pull
|
|
75
|
-
git tag v0.1.0-beta.1
|
|
76
|
-
git push origin v0.1.0-beta.1
|
|
77
|
-
|
|
78
|
-
# 4) Monitor GitHub Actions "Publish to npm"
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Commit signing (required)
|
|
82
|
-
|
|
83
|
-
Ensure your commits are verified before opening PRs:
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
# Option A: SSH signing (recommended)
|
|
87
|
-
git config --global gpg.format ssh
|
|
88
|
-
git config --global user.signingkey ~/.ssh/id_ed25519.pub
|
|
89
|
-
git config --global commit.gpgsign true
|
|
90
|
-
|
|
91
|
-
# Option B: GPG signing
|
|
92
|
-
gpg --full-generate-key
|
|
93
|
-
gpg --list-secret-keys --keyid-format=long
|
|
94
|
-
git config --global user.signingkey <KEY_ID>
|
|
95
|
-
git config --global commit.gpgsign true
|
|
96
|
-
```
|
|
94
|
+
## License
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
MIT © thebridgedev
|
|
96
|
+
[MIT](https://github.com/thebridgedev/bridge-svelte/blob/main/LICENSE) © Nebulr. Built by [The Bridge](https://thebridge.dev/?utm_source=npm&utm_medium=readme&utm_campaign=bridge-svelte).
|
|
@@ -42,6 +42,18 @@ export declare function ensureBillingState(maxAgeMs?: number): Promise<void>;
|
|
|
42
42
|
* every trigger of the refresh rule call. Never rejects.
|
|
43
43
|
*/
|
|
44
44
|
export declare function refreshBilling(): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* TBP-810 — re-read the plan's features after a checkout return or a plan
|
|
47
|
+
* change. Bridge does not always push `entitlements.changed` for a plan change,
|
|
48
|
+
* and nothing else re-read them, so flags kept the old plan's answer.
|
|
49
|
+
*
|
|
50
|
+
* Not `entitlementsStore.refresh()`: that marks the store unloaded while it
|
|
51
|
+
* fetches, and every flag evaluated in that window reads "not entitled". This
|
|
52
|
+
* keeps the current map until the new one arrives, then writes it to both
|
|
53
|
+
* copies (auth-core's, which flags read, and `bridge.tenant.entitlements`).
|
|
54
|
+
* Never rejects.
|
|
55
|
+
*/
|
|
56
|
+
export declare function refreshEntitlements(): Promise<void>;
|
|
45
57
|
/** On mount: read what has not been read, or was read more than 30 s ago. */
|
|
46
58
|
export declare function ensureBilling(maxAgeMs?: number): Promise<void>;
|
|
47
59
|
/** Live events after which the billing stores are re-read. */
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { fetchBillingState, useBridge as useBillingBridge, } from '@nebulr-group/bridge-auth-core';
|
|
23
23
|
import { billingRetryDelay, ensureSubscription, getBridgeAuth, loadSubscription, subscriptionWanted, tokenStore, } from './bridge-instance.js';
|
|
24
|
+
import { applyEntitlementsChanged } from './snapshot-stores.js';
|
|
24
25
|
import { logger } from '../shared/logger.js';
|
|
25
26
|
/** A read older than this is re-read when the tab regains focus. */
|
|
26
27
|
export const BILLING_STALE_AFTER_MS = 30_000;
|
|
@@ -175,6 +176,7 @@ export async function refreshBilling() {
|
|
|
175
176
|
reads.push(loadSubscription());
|
|
176
177
|
if (_stateWanted || stateInStore())
|
|
177
178
|
reads.push(refreshBillingState());
|
|
179
|
+
reads.push(refreshEntitlements());
|
|
178
180
|
await Promise.all(reads);
|
|
179
181
|
}
|
|
180
182
|
catch (err) {
|
|
@@ -182,6 +184,69 @@ export async function refreshBilling() {
|
|
|
182
184
|
logger.debug('[bridge-billing] refresh skipped:', err);
|
|
183
185
|
}
|
|
184
186
|
}
|
|
187
|
+
// ── Entitlements (what `bridge:billing.entitlement.*` flag rules read) ───────
|
|
188
|
+
let _entInflight = null;
|
|
189
|
+
let _entAgain = false;
|
|
190
|
+
/**
|
|
191
|
+
* TBP-810 — re-read the plan's features after a checkout return or a plan
|
|
192
|
+
* change. Bridge does not always push `entitlements.changed` for a plan change,
|
|
193
|
+
* and nothing else re-read them, so flags kept the old plan's answer.
|
|
194
|
+
*
|
|
195
|
+
* Not `entitlementsStore.refresh()`: that marks the store unloaded while it
|
|
196
|
+
* fetches, and every flag evaluated in that window reads "not entitled". This
|
|
197
|
+
* keeps the current map until the new one arrives, then writes it to both
|
|
198
|
+
* copies (auth-core's, which flags read, and `bridge.tenant.entitlements`).
|
|
199
|
+
* Never rejects.
|
|
200
|
+
*/
|
|
201
|
+
export function refreshEntitlements() {
|
|
202
|
+
if (_entInflight) {
|
|
203
|
+
_entAgain = true;
|
|
204
|
+
return _entInflight;
|
|
205
|
+
}
|
|
206
|
+
_entInflight = (async () => {
|
|
207
|
+
try {
|
|
208
|
+
do {
|
|
209
|
+
_entAgain = false;
|
|
210
|
+
await readEntitlementsOnce();
|
|
211
|
+
} while (_entAgain);
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
_entInflight = null;
|
|
215
|
+
}
|
|
216
|
+
})();
|
|
217
|
+
return _entInflight;
|
|
218
|
+
}
|
|
219
|
+
async function readEntitlementsOnce() {
|
|
220
|
+
try {
|
|
221
|
+
// Nothing loaded them yet: there is no old answer to correct, and an app
|
|
222
|
+
// that never reads entitlements makes no extra request.
|
|
223
|
+
const store = useBillingBridge().entitlementsStore;
|
|
224
|
+
if (!store?.isHydrated())
|
|
225
|
+
return;
|
|
226
|
+
const opts = billingReadOptions();
|
|
227
|
+
if (!opts)
|
|
228
|
+
return;
|
|
229
|
+
const res = await globalThis.fetch(`${opts.apiBaseUrl.replace(/\/+$/, '')}/entitlements`, {
|
|
230
|
+
method: 'GET',
|
|
231
|
+
headers: { Authorization: `Bearer ${opts.accessToken}`, 'x-app-id': opts.appId ?? '' },
|
|
232
|
+
});
|
|
233
|
+
if (!res.ok)
|
|
234
|
+
return;
|
|
235
|
+
const body = (await res.json());
|
|
236
|
+
const map = body?.entitlements;
|
|
237
|
+
if (!map || typeof map !== 'object' || Array.isArray(map))
|
|
238
|
+
return;
|
|
239
|
+
// Signed out or switched workspace while this was in flight.
|
|
240
|
+
if (tokenChangedWorkspace(opts.accessToken))
|
|
241
|
+
return;
|
|
242
|
+
store.applyEntitlementsChanged(map);
|
|
243
|
+
applyEntitlementsChanged({ entitlements: map });
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
// Best-effort: the next plan event, reconnect or token renewal repairs it.
|
|
247
|
+
logger.debug('[bridge-billing] entitlements refresh skipped:', err);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
185
250
|
/** On mount: read what has not been read, or was read more than 30 s ago. */
|
|
186
251
|
export async function ensureBilling(maxAgeMs = BILLING_STALE_AFTER_MS) {
|
|
187
252
|
try {
|
|
@@ -295,4 +360,6 @@ export function __resetBillingStoreForTests() {
|
|
|
295
360
|
_stateLoadedAt = 0;
|
|
296
361
|
_stateWanted = false;
|
|
297
362
|
_stateToken = null;
|
|
363
|
+
_entInflight = null;
|
|
364
|
+
_entAgain = false;
|
|
298
365
|
}
|
package/dist/flags/bootstrap.js
CHANGED
|
@@ -245,8 +245,30 @@ export function createBridgeFlags(config = {}) {
|
|
|
245
245
|
});
|
|
246
246
|
notifyAllFlagsChanged();
|
|
247
247
|
});
|
|
248
|
+
// TBP-810 — the billing provider reads these stores at eval time, so a
|
|
249
|
+
// mounted `<FeatureFlag>` / `useFlag()` only sees a plan change if something
|
|
250
|
+
// re-evaluates after the store moved. Without this, an upgraded user kept
|
|
251
|
+
// the old plan's answer until the component re-mounted. One re-eval per
|
|
252
|
+
// burst: plan_changed, entitlements and quota updates land together.
|
|
253
|
+
let _billingReevalQueued = false;
|
|
254
|
+
const onBillingStoreChange = () => {
|
|
255
|
+
if (_billingReevalQueued)
|
|
256
|
+
return;
|
|
257
|
+
_billingReevalQueued = true;
|
|
258
|
+
queueMicrotask(() => {
|
|
259
|
+
_billingReevalQueued = false;
|
|
260
|
+
notifyAllFlagsChanged();
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
const unsubscribeBilling = [
|
|
264
|
+
useBridge().subscription.subscribe(onBillingStoreChange),
|
|
265
|
+
useBridge().quotas.subscribe(onBillingStoreChange),
|
|
266
|
+
useBridge().entitlementsStore.subscribe(onBillingStoreChange),
|
|
267
|
+
];
|
|
248
268
|
const stop = async () => {
|
|
249
269
|
unsubscribeAuth();
|
|
270
|
+
for (const off of unsubscribeBilling)
|
|
271
|
+
off();
|
|
250
272
|
unsubscribeOpen();
|
|
251
273
|
await telemetry.stop();
|
|
252
274
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebulr-group/bridge-svelte",
|
|
3
|
-
"version": "0.9.0",
|
|
4
|
-
"description": "Bridge
|
|
3
|
+
"version": "0.9.1-beta.0",
|
|
4
|
+
"description": "The Bridge for SvelteKit: sign-in, workspaces and roles, feature flags, Stripe subscriptions and plan limits in one .env line and three files.",
|
|
5
5
|
"author": "Iman Pouya",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/thebridgedev/bridge-svelte/issues"
|
|
13
13
|
},
|
|
14
|
-
"homepage": "https://thebridge.dev",
|
|
14
|
+
"homepage": "https://thebridge.dev/docs/quickstart/svelte/",
|
|
15
15
|
"scripts": {
|
|
16
16
|
"dev": "vite dev",
|
|
17
17
|
"build": "bun run prepack",
|
|
@@ -82,12 +82,23 @@
|
|
|
82
82
|
},
|
|
83
83
|
"keywords": [
|
|
84
84
|
"svelte",
|
|
85
|
+
"sveltekit",
|
|
86
|
+
"svelte5",
|
|
87
|
+
"passkeys",
|
|
88
|
+
"magic-link",
|
|
89
|
+
"the-bridge",
|
|
85
90
|
"bridge",
|
|
86
|
-
"
|
|
91
|
+
"authentication",
|
|
87
92
|
"auth",
|
|
93
|
+
"login",
|
|
94
|
+
"sso",
|
|
88
95
|
"feature-flags",
|
|
89
|
-
"payments",
|
|
90
96
|
"stripe",
|
|
91
|
-
"
|
|
97
|
+
"subscriptions",
|
|
98
|
+
"billing",
|
|
99
|
+
"plan-limits",
|
|
100
|
+
"multi-tenant",
|
|
101
|
+
"rbac",
|
|
102
|
+
"saas"
|
|
92
103
|
]
|
|
93
104
|
}
|