@nebulr-group/bridge-cli 0.6.5 → 1.0.0-alpha.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 +93 -460
- package/dist/api/accounts.d.ts +69 -0
- package/dist/api/accounts.d.ts.map +1 -0
- package/dist/api/accounts.js +42 -0
- package/dist/api/accounts.js.map +1 -0
- package/dist/api/apps.d.ts +50 -0
- package/dist/api/apps.d.ts.map +1 -0
- package/dist/api/apps.js +62 -0
- package/dist/api/apps.js.map +1 -0
- package/dist/api/billing.d.ts +131 -0
- package/dist/api/billing.d.ts.map +1 -0
- package/dist/api/billing.js +42 -0
- package/dist/api/billing.js.map +1 -0
- package/dist/api/flags.d.ts +73 -0
- package/dist/api/flags.d.ts.map +1 -0
- package/dist/api/flags.js +108 -0
- package/dist/api/flags.js.map +1 -0
- package/dist/api/keys.d.ts +48 -0
- package/dist/api/keys.d.ts.map +1 -0
- package/dist/api/keys.js +87 -0
- package/dist/api/keys.js.map +1 -0
- package/dist/auth/browser-open.d.ts +21 -4
- package/dist/auth/browser-open.d.ts.map +1 -1
- package/dist/auth/browser-open.js +45 -65
- package/dist/auth/browser-open.js.map +1 -1
- package/dist/auth/lock.d.ts +8 -0
- package/dist/auth/lock.d.ts.map +1 -0
- package/dist/auth/lock.js +43 -0
- package/dist/auth/lock.js.map +1 -0
- package/dist/auth/loopback.d.ts +8 -12
- package/dist/auth/loopback.d.ts.map +1 -1
- package/dist/auth/loopback.js +40 -45
- package/dist/auth/loopback.js.map +1 -1
- package/dist/auth/session.d.ts +61 -0
- package/dist/auth/session.d.ts.map +1 -0
- package/dist/auth/session.js +142 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/sign-in.d.ts +67 -0
- package/dist/auth/sign-in.d.ts.map +1 -0
- package/dist/auth/sign-in.js +113 -0
- package/dist/auth/sign-in.js.map +1 -0
- package/dist/auth/store.d.ts +89 -0
- package/dist/auth/store.d.ts.map +1 -0
- package/dist/auth/store.js +214 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/bin.js +0 -2
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +1 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +13 -17
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +31 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +67 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/accounts.command.d.ts +4 -0
- package/dist/commands/accounts.command.d.ts.map +1 -0
- package/dist/commands/accounts.command.js +116 -0
- package/dist/commands/accounts.command.js.map +1 -0
- package/dist/commands/app.command.d.ts +3 -2
- package/dist/commands/app.command.d.ts.map +1 -1
- package/dist/commands/app.command.js +132 -233
- package/dist/commands/app.command.js.map +1 -1
- package/dist/commands/auth.command.d.ts +2 -12
- package/dist/commands/auth.command.d.ts.map +1 -1
- package/dist/commands/auth.command.js +147 -20
- package/dist/commands/auth.command.js.map +1 -1
- package/dist/commands/billing.command.d.ts +4 -0
- package/dist/commands/billing.command.d.ts.map +1 -0
- package/dist/commands/billing.command.js +185 -0
- package/dist/commands/billing.command.js.map +1 -0
- package/dist/commands/check.command.d.ts +2 -1
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +11 -14
- package/dist/commands/check.command.js.map +1 -1
- package/dist/commands/context.d.ts +58 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +78 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/flag.command.d.ts +3 -62
- package/dist/commands/flag.command.d.ts.map +1 -1
- package/dist/commands/flag.command.js +181 -864
- package/dist/commands/flag.command.js.map +1 -1
- package/dist/commands/key.command.d.ts +4 -0
- package/dist/commands/key.command.d.ts.map +1 -0
- package/dist/commands/key.command.js +44 -0
- package/dist/commands/key.command.js.map +1 -0
- package/dist/commands/plan.command.d.ts +3 -203
- package/dist/commands/plan.command.d.ts.map +1 -1
- package/dist/commands/plan.command.js +200 -759
- package/dist/commands/plan.command.js.map +1 -1
- package/dist/config.d.ts +19 -101
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +51 -364
- package/dist/config.js.map +1 -1
- package/dist/core/discovery.d.ts +27 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +97 -0
- package/dist/core/discovery.js.map +1 -0
- package/dist/core/errors.d.ts +26 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +33 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/http.d.ts +23 -0
- package/dist/core/http.d.ts.map +1 -0
- package/dist/core/http.js +153 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/private-files.d.ts +6 -0
- package/dist/core/private-files.d.ts.map +1 -0
- package/dist/core/private-files.js +96 -0
- package/dist/core/private-files.js.map +1 -0
- package/dist/core/routes.d.ts +94 -0
- package/dist/core/routes.d.ts.map +1 -0
- package/dist/core/routes.js +91 -0
- package/dist/core/routes.js.map +1 -0
- package/dist/core/urls.d.ts +15 -0
- package/dist/core/urls.d.ts.map +1 -0
- package/dist/core/urls.js +55 -0
- package/dist/core/urls.js.map +1 -0
- package/dist/gate-rules.d.ts +1 -2
- package/dist/gate-rules.d.ts.map +1 -1
- package/dist/gate-rules.js +4 -5
- package/dist/gate-rules.js.map +1 -1
- package/dist/index.d.ts +23 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -3
- package/dist/index.js.map +1 -1
- package/dist/io.d.ts +21 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +59 -0
- package/dist/io.js.map +1 -0
- package/dist/money.d.ts +42 -0
- package/dist/money.d.ts.map +1 -0
- package/dist/money.js +112 -0
- package/dist/money.js.map +1 -0
- package/dist/program.d.ts +5 -24
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +25 -113
- package/dist/program.js.map +1 -1
- package/package.json +7 -8
- package/dist/auth/api-client.d.ts +0 -50
- package/dist/auth/api-client.d.ts.map +0 -1
- package/dist/auth/api-client.js +0 -109
- package/dist/auth/api-client.js.map +0 -1
- package/dist/auth/relative-time.d.ts +0 -2
- package/dist/auth/relative-time.d.ts.map +0 -1
- package/dist/auth/relative-time.js +0 -27
- package/dist/auth/relative-time.js.map +0 -1
- package/dist/auth/scopes.d.ts +0 -30
- package/dist/auth/scopes.d.ts.map +0 -1
- package/dist/auth/scopes.js +0 -39
- package/dist/auth/scopes.js.map +0 -1
- package/dist/auth/workspace-client.d.ts +0 -70
- package/dist/auth/workspace-client.d.ts.map +0 -1
- package/dist/auth/workspace-client.js +0 -105
- package/dist/auth/workspace-client.js.map +0 -1
- package/dist/commands/auth/config.command.d.ts +0 -8
- package/dist/commands/auth/config.command.d.ts.map +0 -1
- package/dist/commands/auth/config.command.js +0 -60
- package/dist/commands/auth/config.command.js.map +0 -1
- package/dist/commands/auth/login.command.d.ts +0 -46
- package/dist/commands/auth/login.command.d.ts.map +0 -1
- package/dist/commands/auth/login.command.js +0 -166
- package/dist/commands/auth/login.command.js.map +0 -1
- package/dist/commands/auth/logout.command.d.ts +0 -21
- package/dist/commands/auth/logout.command.d.ts.map +0 -1
- package/dist/commands/auth/logout.command.js +0 -97
- package/dist/commands/auth/logout.command.js.map +0 -1
- package/dist/commands/auth/methods.command.d.ts +0 -68
- package/dist/commands/auth/methods.command.d.ts.map +0 -1
- package/dist/commands/auth/methods.command.js +0 -91
- package/dist/commands/auth/methods.command.js.map +0 -1
- package/dist/commands/auth/status.command.d.ts +0 -18
- package/dist/commands/auth/status.command.d.ts.map +0 -1
- package/dist/commands/auth/status.command.js +0 -104
- package/dist/commands/auth/status.command.js.map +0 -1
- package/dist/commands/auth/use.command.d.ts +0 -17
- package/dist/commands/auth/use.command.d.ts.map +0 -1
- package/dist/commands/auth/use.command.js +0 -47
- package/dist/commands/auth/use.command.js.map +0 -1
- package/dist/commands/branding.command.d.ts +0 -3
- package/dist/commands/branding.command.d.ts.map +0 -1
- package/dist/commands/branding.command.js +0 -42
- package/dist/commands/branding.command.js.map +0 -1
- package/dist/commands/event.command.d.ts +0 -3
- package/dist/commands/event.command.d.ts.map +0 -1
- package/dist/commands/event.command.js +0 -27
- package/dist/commands/event.command.js.map +0 -1
- package/dist/commands/flag-init.command.d.ts +0 -13
- package/dist/commands/flag-init.command.d.ts.map +0 -1
- package/dist/commands/flag-init.command.js +0 -463
- package/dist/commands/flag-init.command.js.map +0 -1
- package/dist/commands/guide.command.d.ts +0 -61
- package/dist/commands/guide.command.d.ts.map +0 -1
- package/dist/commands/guide.command.js +0 -655
- package/dist/commands/guide.command.js.map +0 -1
- package/dist/commands/info.command.d.ts +0 -3
- package/dist/commands/info.command.d.ts.map +0 -1
- package/dist/commands/info.command.js +0 -70
- package/dist/commands/info.command.js.map +0 -1
- package/dist/commands/integration.command.d.ts +0 -35
- package/dist/commands/integration.command.d.ts.map +0 -1
- package/dist/commands/integration.command.js +0 -290
- package/dist/commands/integration.command.js.map +0 -1
- package/dist/commands/journeys.d.ts +0 -43
- package/dist/commands/journeys.d.ts.map +0 -1
- package/dist/commands/journeys.js +0 -221
- package/dist/commands/journeys.js.map +0 -1
- package/dist/commands/ops.command.d.ts +0 -17
- package/dist/commands/ops.command.d.ts.map +0 -1
- package/dist/commands/ops.command.js +0 -129
- package/dist/commands/ops.command.js.map +0 -1
- package/dist/commands/privilege.command.d.ts +0 -16
- package/dist/commands/privilege.command.d.ts.map +0 -1
- package/dist/commands/privilege.command.js +0 -86
- package/dist/commands/privilege.command.js.map +0 -1
- package/dist/commands/role.command.d.ts +0 -3
- package/dist/commands/role.command.d.ts.map +0 -1
- package/dist/commands/role.command.js +0 -101
- package/dist/commands/role.command.js.map +0 -1
- package/dist/commands/runtime-dir.d.ts +0 -16
- package/dist/commands/runtime-dir.d.ts.map +0 -1
- package/dist/commands/runtime-dir.js +0 -18
- package/dist/commands/runtime-dir.js.map +0 -1
- package/dist/commands/setup.command.d.ts +0 -63
- package/dist/commands/setup.command.d.ts.map +0 -1
- package/dist/commands/setup.command.js +0 -148
- package/dist/commands/setup.command.js.map +0 -1
- package/dist/commands/stripe.command.d.ts +0 -29
- package/dist/commands/stripe.command.d.ts.map +0 -1
- package/dist/commands/stripe.command.js +0 -99
- package/dist/commands/stripe.command.js.map +0 -1
- package/dist/commands/tenant.command.d.ts +0 -3
- package/dist/commands/tenant.command.d.ts.map +0 -1
- package/dist/commands/tenant.command.js +0 -135
- package/dist/commands/tenant.command.js.map +0 -1
- package/dist/commands/token.command.d.ts +0 -3
- package/dist/commands/token.command.d.ts.map +0 -1
- package/dist/commands/token.command.js +0 -54
- package/dist/commands/token.command.js.map +0 -1
- package/dist/commands/user.command.d.ts +0 -3
- package/dist/commands/user.command.d.ts.map +0 -1
- package/dist/commands/user.command.js +0 -117
- package/dist/commands/user.command.js.map +0 -1
- package/dist/credentials.d.ts +0 -141
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js +0 -348
- package/dist/credentials.js.map +0 -1
- package/dist/flag-coherence.d.ts +0 -42
- package/dist/flag-coherence.d.ts.map +0 -1
- package/dist/flag-coherence.js +0 -103
- package/dist/flag-coherence.js.map +0 -1
- package/dist/output.d.ts +0 -17
- package/dist/output.d.ts.map +0 -1
- package/dist/output.js +0 -107
- package/dist/output.js.map +0 -1
- package/dist/preview.d.ts +0 -9
- package/dist/preview.d.ts.map +0 -1
- package/dist/preview.js +0 -63
- package/dist/preview.js.map +0 -1
- package/dist/product-decisions.d.ts +0 -40
- package/dist/product-decisions.d.ts.map +0 -1
- package/dist/product-decisions.js +0 -52
- package/dist/product-decisions.js.map +0 -1
- package/dist/prompt-urls.d.ts +0 -31
- package/dist/prompt-urls.d.ts.map +0 -1
- package/dist/prompt-urls.js +0 -35
- package/dist/prompt-urls.js.map +0 -1
- package/dist/prompts/auth-master-integration-prompt.md +0 -255
- package/dist/prompts/billing/master.md +0 -332
- package/dist/prompts/decisions/feature-control.md +0 -131
- package/dist/prompts/decisions/going-live.md +0 -60
- package/dist/prompts/decisions/login.md +0 -71
- package/dist/prompts/decisions/look-and-feel.md +0 -68
- package/dist/prompts/decisions/payments.md +0 -97
- package/dist/prompts/decisions/roles.md +0 -78
- package/dist/prompts/decisions/teams.md +0 -61
- package/dist/prompts/fit-together.md +0 -97
- package/dist/prompts/flags/master.md +0 -220
- package/dist/prompts/guide-coverage.json +0 -17
- package/dist/prompts/integration-success.md +0 -106
- package/dist/prompts/mcp-tools.json +0 -90
- package/dist/prompts/mechanisms.md +0 -298
- package/dist/prompts/orientation.md +0 -42
- package/dist/prompts/product-decisions.json +0 -338
- package/dist/resolve.d.ts +0 -110
- package/dist/resolve.d.ts.map +0 -1
- package/dist/resolve.js +0 -260
- package/dist/resolve.js.map +0 -1
- package/dist/shape.d.ts +0 -25
- package/dist/shape.d.ts.map +0 -1
- package/dist/shape.js +0 -74
- package/dist/shape.js.map +0 -1
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
# How Bridge works — the mechanisms and the levels
|
|
2
|
-
|
|
3
|
-
One page for the rules every Bridge guide builds on. Read it before you write code against a limit, an upgrade prompt or a restyled sign-in page. The per-framework guides (`bridge guide svelte`, `bridge guide nestjs`, …) say *which file*; this page says *why it works* and *which level to pick*.
|
|
4
|
-
|
|
5
|
-
Who gets which feature, and how plans, roles, limits and flags divide the work, is on its own page: `bridge guide fit-together` (MCP: resource `bridge://guides/fit-together`).
|
|
6
|
-
|
|
7
|
-
The examples use the SvelteKit frontend (`@nebulr-group/bridge-svelte`) and the NestJS backend (`@nebulr-group/bridge-nestjs`). The other plugins carry the same surface in their own idiom: React and Next.js serve the pages with `<BridgeAuthRoutes>` and `<BridgeBillingRoutes>`, Angular with `...bridgeAuthRoutes()` and `...bridgeBillingRoutes()`, and Express answers limits with `bridge.requireQuota('exports')` and `bridge.syncQuota('tickets', { current })` (plus `bridge.requireEntitlement` for the one documented exception) — the same `402` / `403` bodies as the NestJS decorators. `bridge guide <framework>` has each one's files.
|
|
8
|
-
|
|
9
|
-
## The whole integration
|
|
10
|
-
|
|
11
|
-
What a developer's repo holds once Bridge is in. Nothing else is required; every other page Bridge needs, it serves.
|
|
12
|
-
|
|
13
|
-
**Frontend (SvelteKit) — 15 lines in four files, plus one line of `.env`:**
|
|
14
|
-
|
|
15
|
-
```env
|
|
16
|
-
# .env
|
|
17
|
-
VITE_BRIDGE_APP_ID=your-app-id
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
```ts
|
|
21
|
-
// src/routes/+layout.ts
|
|
22
|
-
import { bridgeBootstrap } from '@nebulr-group/bridge-svelte';
|
|
23
|
-
export const ssr = false;
|
|
24
|
-
export const load = bridgeBootstrap({ rules: [{ match: new RegExp('^/auth($|/)'), public: true }] });
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
```svelte
|
|
28
|
-
<!-- src/routes/+layout.svelte -->
|
|
29
|
-
<script lang="ts">
|
|
30
|
-
import { BridgeBootstrap } from '@nebulr-group/bridge-svelte';
|
|
31
|
-
import '@nebulr-group/bridge-svelte/styles';
|
|
32
|
-
let { children } = $props();
|
|
33
|
-
</script>
|
|
34
|
-
<BridgeBootstrap>
|
|
35
|
-
{@render children()}
|
|
36
|
-
</BridgeBootstrap>
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
```svelte
|
|
40
|
-
<!-- src/routes/auth/[...bridge]/+page.svelte — every sign-in page -->
|
|
41
|
-
<script lang="ts">
|
|
42
|
-
import { BridgeAuthRoutes } from '@nebulr-group/bridge-svelte';
|
|
43
|
-
</script>
|
|
44
|
-
<BridgeAuthRoutes />
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
With plans, one more four-line file serves the subscription page, the paywall and both checkout return pages:
|
|
48
|
-
|
|
49
|
-
```svelte
|
|
50
|
-
<!-- src/routes/subscription/[...bridge]/+page.svelte -->
|
|
51
|
-
<script lang="ts">
|
|
52
|
-
import { BridgeBillingRoutes } from '@nebulr-group/bridge-svelte';
|
|
53
|
-
</script>
|
|
54
|
-
<BridgeBillingRoutes />
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**Backend (NestJS) — 4 lines, plus one line of environment.** The lines marked `// +` are Bridge's; the rest is what `nest new` already gave you:
|
|
58
|
-
|
|
59
|
-
```env
|
|
60
|
-
# .env
|
|
61
|
-
BRIDGE_APP_ID=your-app-id
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
// src/app.module.ts
|
|
66
|
-
import { Module } from '@nestjs/common';
|
|
67
|
-
import { BridgeModule } from '@nebulr-group/bridge-nestjs'; // +
|
|
68
|
-
import { ExportsController } from './exports.controller';
|
|
69
|
-
|
|
70
|
-
@Module({
|
|
71
|
-
imports: [BridgeModule.forRoot({ guard: { global: true } })], // + every route needs a signed-in user
|
|
72
|
-
controllers: [ExportsController],
|
|
73
|
-
})
|
|
74
|
-
export class AppModule {}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
// src/exports.controller.ts
|
|
79
|
-
import { Controller, Post } from '@nestjs/common';
|
|
80
|
-
import { RequireQuota } from '@nebulr-group/bridge-nestjs'; // +
|
|
81
|
-
|
|
82
|
-
@Controller('exports')
|
|
83
|
-
export class ExportsController {
|
|
84
|
-
@Post()
|
|
85
|
-
@RequireQuota('exports') // + the plan limit
|
|
86
|
-
create() {
|
|
87
|
-
return { exported: true };
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
`BRIDGE_APP_ID` must be in the process environment. NestJS does not read `.env` by itself: start with `node --env-file=.env dist/main.js`, or set it in your container or process manager.
|
|
93
|
-
|
|
94
|
-
**Settings are read for you.** Each field resolves as *explicit option > environment > default*; an empty value counts as unset.
|
|
95
|
-
|
|
96
|
-
| Frontend | Backend | When to set it |
|
|
97
|
-
|---|---|---|
|
|
98
|
-
| `VITE_BRIDGE_APP_ID` | `BRIDGE_APP_ID` | Always. Missing, Bridge refuses to start and names the variable |
|
|
99
|
-
| `VITE_BRIDGE_API_BASE_URL` | `BRIDGE_API_BASE_URL` | Only for a non-production app (stage, local, self-hosted). Unset means production |
|
|
100
|
-
| `VITE_BRIDGE_HOSTED_URL` | — | Only for a local or self-hosted Bridge. On Bridge's own domains it follows the API address (`api-stage` → `auth-stage`) |
|
|
101
|
-
| `VITE_BRIDGE_DEBUG` | `BRIDGE_DEBUG` | `true` for console logging |
|
|
102
|
-
|
|
103
|
-
## 1. Decide once, where the action happens
|
|
104
|
-
|
|
105
|
-
Ask the developer first: **does this action call your server?**
|
|
106
|
-
|
|
107
|
-
- **It calls your backend:** the backend handler counts it and refuses at the limit, because that is where the action happens. The frontend shows the count and the upgrade dialog, and does not count the same metric again.
|
|
108
|
-
- **It happens in the browser** and never reaches a server of yours (local-first, data on the device): the browser counts it and gates the button (section 5). That is a first-class setup.
|
|
109
|
-
|
|
110
|
-
Never both for one metric: it would be counted twice.
|
|
111
|
-
|
|
112
|
-
## 2. A POST increments the limit
|
|
113
|
-
|
|
114
|
-
The backend decorator **is** the increment. There is nothing else to wire: no "report usage" call after the insert, no counter in your database, no endpoint for the frontend.
|
|
115
|
-
|
|
116
|
-
```ts
|
|
117
|
-
@Post()
|
|
118
|
-
@RequireQuota('exports')
|
|
119
|
-
create() { /* your code */ }
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
- **Before the handler:** at the plan's limit the request is refused with `402` and a body the frontend understands (`{ code: 'QUOTA_EXCEEDED', metric, used, limit, fix }`). Your handler never runs.
|
|
123
|
-
- **After the handler:** only when it answered **2xx**, Bridge records the use — exactly one write. A handler that throws or answers 4xx/5xx records nothing.
|
|
124
|
-
- **Retries:** a request that sends an `Idempotency-Key` header is counted once per key, however often the client retries.
|
|
125
|
-
- **Fail-closed:** if Bridge cannot answer, the request is refused with `503`, never let through unchecked.
|
|
126
|
-
- A `metered` quota never refuses: past its allowance it bills per unit.
|
|
127
|
-
|
|
128
|
-
## 3. Counter or gauge
|
|
129
|
-
|
|
130
|
-
**If deleting it frees room, it's a gauge and your app counts it; if it happened, it's a counter and Bridge counts it.**
|
|
131
|
-
|
|
132
|
-
| | Counter | Gauge |
|
|
133
|
-
|---|---|---|
|
|
134
|
-
| Examples | exports, API calls, AI completions, emails sent | tickets, projects, stored files, seats |
|
|
135
|
-
| Who counts | Bridge, one event per successful request | Your app — you pass your own count |
|
|
136
|
-
| Resets | every billing period ("40 of 100 this month") | never ("8 of 10 projects") |
|
|
137
|
-
| Configure | `bridge plan quota set <plan> --metric exports --limit 100 --policy hard` (counter is the default) | `bridge plan quota set <plan> --metric tickets --limit 10 --policy hard --kind gauge` |
|
|
138
|
-
| Backend | `@RequireQuota('exports')` | `@RequireQuota('tickets', { current })` on create, `@SyncQuota('tickets', { current })` on delete |
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
@Post()
|
|
142
|
-
@RequireQuota('tickets', { current: (t, self: TicketsController) => self.tickets.countFor(t.id) })
|
|
143
|
-
create() { /* … */ }
|
|
144
|
-
|
|
145
|
-
@Delete(':id')
|
|
146
|
-
@SyncQuota('tickets', { current: (t, self: TicketsController) => self.tickets.countFor(t.id) })
|
|
147
|
-
remove() { /* … */ }
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
`current` returns your own count, and that count is what the limit is compared against, so Bridge's copy heals itself if it ever missed an update. There is no decrement and no reservation: every create and delete sends the whole count.
|
|
151
|
-
|
|
152
|
-
**Seats** are a gauge the app names (e.g. `seats`) and marks as counted from membership: `bridge plan quota set <plan> --metric seats --limit 10 --policy hard --kind gauge --source membership`. Bridge answers it with the workspace's active members, pending invites included, read fresh each time; the app never reports it. Check it where invites happen: on the built-in team page with `<TeamManagementPanel seatsMetric="seats" />`, or with `@RequireQuota('seats')` on your own invite handler (it checks and writes nothing). Bridge's invite API does not refuse at the limit by itself. Never gate seats with a flag or an entitlement.
|
|
153
|
-
|
|
154
|
-
`bridge plan quota list` shows every metric the app's plans limit, with its kind.
|
|
155
|
-
|
|
156
|
-
**Every hard quota is also an entitlement** with the same name, true while there is room. So never pair `@RequireEntitlement('exports')` with `@RequireQuota('exports')`: at the cap the entitlement answers `403` before the quota can answer the `402` the frontend knows how to upsell. `@RequireQuota` is for a *limit*; `@RequireEntitlement` checks a plan *feature* (`analytics`, `sso`) without a flag, which is the exception (next paragraph).
|
|
157
|
-
|
|
158
|
-
**A plan feature goes in the plan's features list.** `bridge plan feature add pro analytics --name "Analytics"` makes `analytics` true on `pro`, and false on every other plan. The pricing table and the upgrade dialog name it from the same list. Control the feature with a flag whose rule is `bridge:billing.entitlement.analytics eq true`, so changing what Pro sells is one edit on the plan and no flag rule has to follow. Checking it directly, without a flag, is the exception (section Exceptions, at the end). A hard quota with limit 1 that nothing counts still works the same way; it is the older way to do this. `app_active` is always there: true while the workspace's subscription is active, trialing, past due or cancelling at period end.
|
|
159
|
-
|
|
160
|
-
## 4. Three ways to handle a limit in the UI
|
|
161
|
-
|
|
162
|
-
Pick the lowest level that does the job. Each is optional; level 0 is on without code.
|
|
163
|
-
|
|
164
|
-
| Level | What the page writes | What the user sees |
|
|
165
|
-
|---|---|---|
|
|
166
|
-
| **0 — nothing** | a plain button calling your API | Your backend refuses at the cap (`402`), and `<BridgeBootstrap>` opens an **upgrade dialog** naming the metric, linking to the subscription page. A workspace member who cannot manage billing is told to ask the owner instead |
|
|
167
|
-
| **1 — one component** | `<QuotaGate metric="tickets">…</QuotaGate>` around the button; `<FeatureFlag key="analytics" upgrade>…</FeatureFlag>` around a paid feature | The button is disabled at a known hard cap with an upgrade line beside it; the paid feature shows on a plan that includes it, and elsewhere an "Upgrade to use this" button that opens the dialog when clicked |
|
|
168
|
-
| **2 — your own UI** | `useQuota('tickets')`, and `<FeatureFlag key="analytics">` around what the plan sells | Whatever you build from the live numbers |
|
|
169
|
-
|
|
170
|
-
```svelte
|
|
171
|
-
<script lang="ts">
|
|
172
|
-
import { QuotaGate, FeatureFlag, useQuota } from '@nebulr-group/bridge-svelte';
|
|
173
|
-
const tickets = useQuota('tickets');
|
|
174
|
-
</script>
|
|
175
|
-
|
|
176
|
-
<!-- level 1 -->
|
|
177
|
-
<QuotaGate metric="tickets">
|
|
178
|
-
<button onclick={createTicket}>New ticket</button>
|
|
179
|
-
</QuotaGate>
|
|
180
|
-
|
|
181
|
-
<!-- the flag's rule: bridge:billing.entitlement.analytics eq true -->
|
|
182
|
-
<FeatureFlag key="analytics" defaultValue={false} upgrade>
|
|
183
|
-
<a href="/analytics">Analytics</a>
|
|
184
|
-
</FeatureFlag>
|
|
185
|
-
|
|
186
|
-
<!-- level 2 -->
|
|
187
|
-
{#if tickets.loading}
|
|
188
|
-
Loading…
|
|
189
|
-
{:else if tickets.unlimited}
|
|
190
|
-
Unlimited tickets
|
|
191
|
-
{:else}
|
|
192
|
-
{tickets.used} of {tickets.limit} tickets
|
|
193
|
-
{/if}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
- No upgrade dialog opens by itself: it opens on a `402` from your backend, when someone opens a route whose flag is off because of the plan, or when they click an upgrade prompt. A page that only renders a hidden feature opens nothing.
|
|
197
|
-
- "Not loaded yet" is never "zero" and never "not allowed": `useQuota` numbers stay `null` while `loading`, `<QuotaGate>` stays enabled while loading, and `<Entitled>` renders nothing (or its `loading` snippet) until `$entitlements.ready`.
|
|
198
|
-
- The dialog catches a `402 QUOTA_EXCEEDED` from the page's own origin, from Bridge, and from any call made with `bridgeFetch()`; a backend on another origin is listed in `billing.apiOrigins`. `billing: { upgradeDialog: false }` turns it off, `billing: { upgradeDialog: MyDialog }` replaces it.
|
|
199
|
-
- Do not write a quota `if`, a "limit reached" toast or a `/quota` endpoint of your own: level 0 already covers the refusal, and the frontend reads quota directly from Bridge.
|
|
200
|
-
|
|
201
|
-
## 5. Counting in the browser
|
|
202
|
-
|
|
203
|
-
When the action happens in the browser and never reaches a server of yours — local-first, mobile, data on the device — the frontend counts it:
|
|
204
|
-
|
|
205
|
-
```ts
|
|
206
|
-
import { bridge } from '@nebulr-group/bridge-svelte';
|
|
207
|
-
|
|
208
|
-
bridge.usage.report('exports'); // a counter: it happened
|
|
209
|
-
await bridge.usage.set('projects', projects.length); // a gauge: how many exist now
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
Put `<QuotaGate metric="exports">` around the button (section 4, level 1) so it stops at the limit, and the upgrade line sells the next plan.
|
|
213
|
-
|
|
214
|
-
This is a complete, first-class way to run limits. When the action does call your backend, count it there instead (section 2), never in both places.
|
|
215
|
-
|
|
216
|
-
## 6. Four levels of customising Bridge's pages
|
|
217
|
-
|
|
218
|
-
The same ladder holds for the sign-in pages (`<BridgeAuthRoutes>`) and the subscription pages (`<BridgeBillingRoutes>`). The pages must end up looking like the app they sit in: rung 1, with the tokens taken from the app's own styles, is always applied, and rung 2 too when the app frames its pages in a card or shell. Climb further only as far as you need.
|
|
219
|
-
|
|
220
|
-
| Rung | What you do | What you own |
|
|
221
|
-
|---|---|---|
|
|
222
|
-
| **0 — nothing** | Bridge's pages render inside your own `+layout.svelte` | Your navigation, header and shell already surround them |
|
|
223
|
-
| **1 — tokens** | Set `--bridge-*` CSS variables in your CSS | Colours, radius, spacing |
|
|
224
|
-
| **2 — frame and heading** | Pass the `frame(page, children)` and `heading(page)` snippets | Everything around the form on every page, and each page's heading |
|
|
225
|
-
| **3 — take over one page** | Create that page's own route file, e.g. `src/routes/auth/login/+page.svelte` | That one page; SvelteKit prefers it over `[...bridge]`, every other page keeps working |
|
|
226
|
-
| **4 — headless** | Build your own UI on `getBridgeAuth()` | Everything |
|
|
227
|
-
|
|
228
|
-
```svelte
|
|
229
|
-
<!-- rung 2: src/routes/auth/[...bridge]/+page.svelte -->
|
|
230
|
-
<script lang="ts">
|
|
231
|
-
import { BridgeAuthRoutes } from '@nebulr-group/bridge-svelte';
|
|
232
|
-
</script>
|
|
233
|
-
|
|
234
|
-
<BridgeAuthRoutes>
|
|
235
|
-
{#snippet frame(page, children)}
|
|
236
|
-
<main class="auth-card">{@render children()}</main>
|
|
237
|
-
{/snippet}
|
|
238
|
-
{#snippet heading(page)}
|
|
239
|
-
<h1>{page === 'signup' ? 'Create your account' : 'Welcome back'}</h1>
|
|
240
|
-
{/snippet}
|
|
241
|
-
</BridgeAuthRoutes>
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
`heading` replaces only each page's main step (the login credentials step, the signup form, the set-password form); sub-steps such as "Reset your password" keep their own, so two headings never stack. There are no per-page snippets: to change more than frame and heading, take over the page (rung 3). A sign-in page you own navigates itself after sign-in: `<LoginForm onLogin={() => goto(readReturnTo($page.url) ?? '/')} />`.
|
|
245
|
-
|
|
246
|
-
**The token contract (rung 1).** These `--bridge-*` variables are the supported way to restyle the components; each has a built-in default. Set them on `:root` or on any wrapper.
|
|
247
|
-
|
|
248
|
-
| Token | Default | Styles |
|
|
249
|
-
|---|---|---|
|
|
250
|
-
| `--bridge-primary` | `#4f46e5` | Primary buttons, selected plan interval, active workspace border |
|
|
251
|
-
| `--bridge-primary-hover` | `#4338ca` | Primary button hover |
|
|
252
|
-
| `--bridge-primary-fg` | `#ffffff` | Text on primary buttons |
|
|
253
|
-
| `--bridge-primary-foreground` | `#ffffff` | Workspace avatar text; plan picker fallback when `--bridge-primary-fg` is unset |
|
|
254
|
-
| `--bridge-primary-light` | `#eff6ff` | Active workspace background |
|
|
255
|
-
| `--bridge-border` | `#d1d5db` | Input and secondary button borders |
|
|
256
|
-
| `--bridge-border-radius` | `6px` | Corners of inputs, buttons, alerts, cards |
|
|
257
|
-
| `--bridge-input-focus` | `#4f46e5` | Input focus ring |
|
|
258
|
-
| `--bridge-foreground` | `#374151` | Password-toggle hover, workspace names |
|
|
259
|
-
| `--bridge-muted` | `#6b7280` | Secondary text, hints, table headings |
|
|
260
|
-
| `--bridge-bg` | `#ffffff` | Plan-change confirmation panel |
|
|
261
|
-
| `--bridge-bg-muted` | `#f5f5f5` | MFA backup code |
|
|
262
|
-
| `--bridge-muted-bg` | `#f3f4f6` | Workspace list hover, plan interval tabs (which fall back to `--bridge-bg-muted`) |
|
|
263
|
-
| `--bridge-alert-error-bg` | `#fef2f2` | Error alert background |
|
|
264
|
-
| `--bridge-alert-error-fg` | `#991b1b` | Error alert text |
|
|
265
|
-
| `--bridge-alert-error-border` | `#fca5a5` | Error alert border |
|
|
266
|
-
| `--bridge-alert-success-bg` | `#f0fdf4` | Success alert background |
|
|
267
|
-
| `--bridge-alert-success-fg` | `#166534` | Success alert text |
|
|
268
|
-
| `--bridge-alert-success-border` | `#86efac` | Success alert border |
|
|
269
|
-
| `--bridge-auth-page-padding` | `3rem 1rem` | Padding of the default sign-in page container |
|
|
270
|
-
| `--bridge-billing-page-width` | `60rem` | Width of the subscription pages and `<BridgePaywallPage>` |
|
|
271
|
-
| `--bridge-billing-page-padding` | `3rem 1rem` | Padding of the subscription pages and `<BridgePaywallPage>` |
|
|
272
|
-
| `--bridge-paywall-bg` | `rgba(15, 23, 42, 0.72)` | Backdrop of the `<BridgePaywall>` overlay |
|
|
273
|
-
| `--bridge-paywall-panel-bg` | `#ffffff` | Panel of the `<BridgePaywall>` overlay |
|
|
274
|
-
|
|
275
|
-
All default rules use `:where()`, so any selector of yours wins without `!important`. Leaving out `import '@nebulr-group/bridge-svelte/styles'` makes the components headless: plain HTML for you to style.
|
|
276
|
-
|
|
277
|
-
## 7. Pages Bridge serves, and the one it only offers
|
|
278
|
-
|
|
279
|
-
- **Sign-in:** `src/routes/auth/[...bridge]/+page.svelte` serves login, signup, the OAuth callback, set password (where signup verification and password-reset emails land), forgot password, magic link, passkey setup and workspace selection. Which methods appear comes from the app's settings at runtime, so turning magic links on needs no code. The pages render inside the app: `loginRoute: '/auth/login'` in `bridgeBootstrap()`. (If the developer explicitly asks for Bridge's hosted sign-in page, or the app already uses it, respect that: leave out `loginRoute`.)
|
|
280
|
-
- **Subscription:** `src/routes/subscription/[...bridge]/+page.svelte` serves `/subscription`, the paywall `/subscription/plan`, and the checkout return pages `/subscription/success` and `/subscription/error`. Bridge's redirects and upgrade links point there by default.
|
|
281
|
-
- **The paywall** redirects a signed-in workspace with no plan to `/subscription/plan` before any page renders — only when the app has plans and its `paymentsAutoRedirect` setting is on (the default). `bridge app update --payments-auto-redirect false` turns it off.
|
|
282
|
-
- **A `/welcome` onboarding page is offered, never created unasked.** It is a product decision: ask the developer. Only if they say yes, add a one-line page rendering `<BridgePaywallPage />` and `billing: { paywallRoute: '/welcome' }` in `bridgeBootstrap()`.
|
|
283
|
-
- Any other address under a catch-all gets the app's own 404.
|
|
284
|
-
|
|
285
|
-
## Where the details live
|
|
286
|
-
|
|
287
|
-
| Topic | Command |
|
|
288
|
-
|---|---|
|
|
289
|
-
| Sign-in and the sign-in pages | `bridge guide <framework>` · `bridge guide <framework> sdk-auth` (svelte, react, nextjs, angular) |
|
|
290
|
-
| Plans, the subscription pages, the UI levels | `bridge guide svelte billing` |
|
|
291
|
-
| Backend limits and entitlements | `bridge guide nestjs billing` · `bridge guide express billing` |
|
|
292
|
-
| Backend sign-in and route protection | `bridge guide nestjs` · `bridge guide express` |
|
|
293
|
-
| Feature flags | `bridge guide flags` |
|
|
294
|
-
| Proving sign-in works | The developer signs up and signs in in the app; `bridge event auth-attempts --user <email>` explains a failure |
|
|
295
|
-
|
|
296
|
-
## Exceptions
|
|
297
|
-
|
|
298
|
-
Every gate in app code is a flag (`bridge guide fit-together`). Checking a plan feature without a flag (`@RequireEntitlement` on the backend, `<Entitled>` or `$entitlements.can` in the UI) exists for the rare case where the developer explicitly asks for no flag. It prints a one-time note in development; mark the line `// bridge-gate-exception: <reason>` so `check gates` leaves it.
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# What Bridge does
|
|
2
|
-
|
|
3
|
-
Relay this to the developer as it stands, then add one sentence about what their app has today (read it first: `get_app` and `get_setup_status`, or `bridge app get` and `bridge setup status`).
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Bridge runs everything between signup and invoice for your app, so you don't build it yourself.
|
|
8
|
-
|
|
9
|
-
- **Login.** People sign up and sign in with a password, a magic link, a passkey, Google, GitHub and other providers, or their company's SSO, with optional two-factor. Bridge serves the pages; your app adds about fifteen lines. *Want to choose how people sign in?*
|
|
10
|
-
- **Teams and workspaces.** Each customer gets a workspace and can invite teammates into it. You decide whether people create their own workspace or join by invitation. *Is your app for single users or for teams?*
|
|
11
|
-
- **Roles and permissions.** Inside a workspace, each person's role carries privileges, and feature flags use them to decide who gets which page, endpoint or feature, the same in the browser and on your backend. *Who are the different kinds of people in a customer's workspace?*
|
|
12
|
-
- **Payments and plans.** Plans with prices, trials, limits ("3 projects on Free, 50 on Pro") and the features each one sells, checkout through your own Stripe account, and an upgrade prompt when someone hits a limit or a feature their plan lacks. *Want to set up a free tier and a paid plan?*
|
|
13
|
-
- **Feature control.** Feature flags switch any page, API endpoint or feature for everyone, a percentage of users, the plans that sell it, the people allowed to use it, or one customer, without a deploy. *Is there a feature you want to roll out carefully or sell on a higher plan?*
|
|
14
|
-
- **Look and feel.** Sign-in and subscription pages in your colours and fonts, inside your own layout, and emails sent in your app's name. *Want the sign-in pages to look like the rest of your app?*
|
|
15
|
-
- **Going live.** A readiness checklist that says exactly what still points at localhost or test keys, and a test sign-in that proves login works end to end. *Ready to check what stands between you and real users?*
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Where to go next
|
|
20
|
-
|
|
21
|
-
Each area has a decision guide: the questions to ask the developer, and the defaults to apply without asking.
|
|
22
|
-
|
|
23
|
-
| Area | Decision guide | Journey |
|
|
24
|
-
|---|---|---|
|
|
25
|
-
| Login | `bridge guide decision login` | `bridge guide add-login` |
|
|
26
|
-
| Teams and workspaces | `bridge guide decision teams` | `bridge guide add-teams` |
|
|
27
|
-
| Roles and permissions | `bridge guide decision roles` | — |
|
|
28
|
-
| Payments and plans | `bridge guide decision payments` | `bridge guide add-paid-plan` |
|
|
29
|
-
| Feature control | `bridge guide decision feature-control` | `bridge guide add-feature-flag` |
|
|
30
|
-
| Look and feel | `bridge guide decision look-and-feel` | — |
|
|
31
|
-
| Going live | `bridge guide decision going-live` | `bridge guide go-live` |
|
|
32
|
-
|
|
33
|
-
In an MCP client the same guides are the resources `bridge://guides/decisions/<area>` (areas: `login`, `teams`, `roles`, `payments`, `feature-control`, `look-and-feel`, `going-live`), and the journeys are the server's prompts of the same names.
|
|
34
|
-
|
|
35
|
-
How roles, plans, limits and flags fit together, the rule every decision guide builds on: `bridge guide fit-together` (MCP: resource `bridge://guides/fit-together`). Read it before setting up any of them.
|
|
36
|
-
|
|
37
|
-
How limits, upgrade prompts and page customisation actually work, for every area: `bridge guide mechanisms`.
|
|
38
|
-
|
|
39
|
-
Two rules hold in every guide:
|
|
40
|
-
|
|
41
|
-
1. **Product questions go to the developer.** What to charge, who may do what, what a plan includes, what users see: ask, and wait for the answer. Never guess them.
|
|
42
|
-
2. **Mechanics you decide.** Where a guide gives a default, apply it and say why in one line. The developer can overrule it.
|