@formata/limitr 0.5.23 → 0.5.25
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 +157 -84
- package/dist/limitr.d.ts.map +1 -1
- package/dist/limitr.js +1 -1
- package/dist/limitr.js.map +1 -1
- package/dist/main.d.ts +24 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +42 -2
- package/dist/main.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,39 +1,82 @@
|
|
|
1
1
|
# Limitr
|
|
2
|
-
**Open-source policy engine for plans, limits, and usage enforcement.**
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
**Stop hardcoding your pricing. Treat it like config.**
|
|
5
4
|
|
|
6
|
-
## What It Does
|
|
7
|
-
Define pricing in a policy document, not application code:
|
|
8
|
-
- **Plans & entitlements** - seat limits, usage caps, feature gates
|
|
9
|
-
- **Offline enforcement** - runs locally, no API calls required
|
|
10
|
-
- **Event-driven** - react to limit hits, overages, denials
|
|
11
|
-
- **Portable** - works anywhere JavaScript runs
|
|
12
|
-
- **Inspectable** - customers and auditors can read your limits
|
|
13
|
-
|
|
14
|
-
## Why Limitr?
|
|
15
|
-
Billing systems (Stripe, etc.) handle payments. **Limitr handles enforcement.**
|
|
16
|
-
|
|
17
|
-
Most apps hardcode limits in `pricing.ts`:
|
|
18
5
|
```typescript
|
|
6
|
+
// Before: Pricing logic scattered everywhere
|
|
19
7
|
if (user.plan === 'free' && user.seats >= 1) {
|
|
20
8
|
throw new Error('Upgrade to add more seats');
|
|
21
9
|
}
|
|
10
|
+
|
|
11
|
+
// After: One source of truth
|
|
12
|
+
if (await policy.increment('user_123', 'seats')) {
|
|
13
|
+
// Add the seat
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Limitr is an open-source pricing engine that moves your limits, quotas, and feature gates out of application code and into a declarative policy document.
|
|
18
|
+
|
|
19
|
+
Powered by WebAssembly for deterministic, portable enforcement across Node.js, browsers, Deno, and Bun.
|
|
20
|
+
|
|
21
|
+
## The Problem
|
|
22
|
+
|
|
23
|
+
Your pricing logic is everywhere:
|
|
24
|
+
- Hardcoded in route handlers
|
|
25
|
+
- Duplicated across services
|
|
26
|
+
- Impossible to change without deploying
|
|
27
|
+
- Breaks when product changes the free tier
|
|
28
|
+
|
|
29
|
+
**Changing "free tier gets 1 seat → 3 seats" shouldn't require a code change.**
|
|
30
|
+
|
|
31
|
+
## How It Works
|
|
32
|
+
|
|
33
|
+
**1. Define your pricing once**
|
|
34
|
+
```yaml
|
|
35
|
+
policy:
|
|
36
|
+
credits:
|
|
37
|
+
seat:
|
|
38
|
+
label: Seat
|
|
39
|
+
plans:
|
|
40
|
+
free:
|
|
41
|
+
entitlements:
|
|
42
|
+
seats:
|
|
43
|
+
limit:
|
|
44
|
+
credit: seat
|
|
45
|
+
value: 1
|
|
46
|
+
pro:
|
|
47
|
+
entitlements:
|
|
48
|
+
seats:
|
|
49
|
+
limit:
|
|
50
|
+
credit: seat
|
|
51
|
+
value: 10
|
|
22
52
|
```
|
|
23
53
|
|
|
24
|
-
|
|
54
|
+
**2. Enforce everywhere**
|
|
55
|
+
```typescript
|
|
56
|
+
const policy = await Limitr.new(policyDoc);
|
|
25
57
|
|
|
26
|
-
|
|
58
|
+
// Seat limits
|
|
59
|
+
await policy.allow('user_123', 'seats', 1);
|
|
60
|
+
|
|
61
|
+
// Usage limits
|
|
62
|
+
await policy.allow('user_456', 'ai_tokens', 4200);
|
|
63
|
+
|
|
64
|
+
// Feature gates
|
|
65
|
+
await policy.allow('user_789', 'advanced_analytics');
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**3. Change pricing without redeploying**
|
|
69
|
+
Update the policy document. That's it.
|
|
27
70
|
|
|
28
71
|
## Install
|
|
72
|
+
|
|
29
73
|
```bash
|
|
30
74
|
npm install @formata/limitr
|
|
31
75
|
```
|
|
32
76
|
|
|
33
|
-
### Initialization
|
|
34
|
-
Limitr uses [Stof](https://docs.stof.dev) for policy enforcement (@formata/stof). This is sandboxed WebAssembly, and needs to be initialized once before use.
|
|
77
|
+
### Initialization (Browser Only)
|
|
35
78
|
|
|
36
|
-
|
|
79
|
+
Limitr uses [Stof](https://docs.stof.dev) for policy enforcement, which compiles to WebAssembly. Browser apps need to initialize WASM once before use. **Node.js, Deno, and Bun handle this automatically—skip this step.**
|
|
37
80
|
|
|
38
81
|
```typescript
|
|
39
82
|
// Vite
|
|
@@ -41,26 +84,22 @@ import { initStof } from '@formata/stof';
|
|
|
41
84
|
import stofWasm from '@formata/stof/wasm?url';
|
|
42
85
|
await initStof(stofWasm);
|
|
43
86
|
|
|
44
|
-
//
|
|
87
|
+
// Other bundlers (with WASM plugin)
|
|
45
88
|
import { initStof } from '@formata/stof';
|
|
46
89
|
import stofWasm from '@formata/stof/wasm';
|
|
47
90
|
await initStof(await stofWasm());
|
|
48
|
-
|
|
49
|
-
// Node.js, Deno, & Bun - Auto-detects and loads WASM (you can skip this though, Limitr does it)
|
|
50
|
-
import { initStof } from '@formata/stof';
|
|
51
|
-
await initStof();
|
|
52
91
|
```
|
|
53
92
|
|
|
54
|
-
## Quick Start
|
|
93
|
+
## Quick Start
|
|
94
|
+
|
|
55
95
|
```typescript
|
|
56
96
|
import { Limitr } from '@formata/limitr';
|
|
57
97
|
|
|
58
|
-
// Define policy (YAML, JSON, TOML, STOF) (load from DB, API, file, etc.)
|
|
59
98
|
const policy = await Limitr.new(`
|
|
60
99
|
policy:
|
|
61
100
|
credits:
|
|
62
101
|
seat:
|
|
63
|
-
|
|
102
|
+
label: Seat
|
|
64
103
|
plans:
|
|
65
104
|
free:
|
|
66
105
|
entitlements:
|
|
@@ -78,90 +117,124 @@ policy:
|
|
|
78
117
|
increment: 1
|
|
79
118
|
`, 'yaml');
|
|
80
119
|
|
|
81
|
-
// Create/load customers
|
|
82
120
|
await policy.createCustomer('user_123', 'free');
|
|
83
|
-
await policy.createCustomer('user_456', 'pro');
|
|
84
121
|
|
|
85
|
-
//
|
|
86
|
-
await policy.increment('user_123', 'seats'); //
|
|
87
|
-
|
|
122
|
+
await policy.increment('user_123', 'seats'); // true (increment -> shorthand for allow(..) with limit's "increment" value)
|
|
123
|
+
await policy.increment('user_123', 'seats'); // false (limit hit)
|
|
124
|
+
```
|
|
88
125
|
|
|
89
|
-
|
|
90
|
-
|
|
126
|
+
## When To Use This
|
|
127
|
+
|
|
128
|
+
- ✅ You have seat-based, usage-based, or hybrid pricing
|
|
129
|
+
- ✅ Your pricing changes more than once a quarter
|
|
130
|
+
- ✅ You support self-hosted deployments
|
|
131
|
+
- ✅ You're building an AI product with token limits
|
|
132
|
+
- ✅ You're tired of pricing logic in 47 different files
|
|
133
|
+
- ✅ Easy to adopt incrementally, wrap existing checks with `policy.allow()` one at a time
|
|
134
|
+
|
|
135
|
+
## Local vs Cloud
|
|
136
|
+
|
|
137
|
+
### Local (Open Source)
|
|
138
|
+
Runs entirely in your app. No external calls. Perfect for self-hosted.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const policy = await Limitr.new(policyDoc);
|
|
91
142
|
```
|
|
92
143
|
|
|
93
|
-
|
|
144
|
+
### Cloud (Managed + Stripe)
|
|
145
|
+
Hosted version with Stripe integration, customer management, dashboard, and analytics.
|
|
94
146
|
|
|
95
|
-
|
|
147
|
+
```typescript
|
|
148
|
+
const policy = await Limitr.cloud({
|
|
149
|
+
token: 'limitr_...'
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- Change pricing in minutes without redeploys
|
|
154
|
+
- Built-in UI for pricing tables, plan selection, invoices
|
|
155
|
+
- Automatic sync with Stripe
|
|
156
|
+
- Analytics dashboards
|
|
157
|
+
|
|
158
|
+
[Learn more about Limitr Cloud →](https://limitr.dev)
|
|
159
|
+
|
|
160
|
+
## How It's Different
|
|
161
|
+
|
|
162
|
+
> **Stripe knows:** "User paid for Pro plan"<br/>
|
|
163
|
+
> **Limitr enforces:** "User can create 10 seats, use 1M tokens, and export PDFs"
|
|
164
|
+
|
|
165
|
+
Most apps:
|
|
166
|
+
1. Check limits in code → `if (user.plan === 'free')`
|
|
167
|
+
2. Take action
|
|
168
|
+
3. Send usage to billing system
|
|
169
|
+
|
|
170
|
+
With Limitr:
|
|
171
|
+
1. Check policy → `await policy.allow('user', 'action')`
|
|
172
|
+
2. Take action
|
|
173
|
+
3. Usage tracked automatically (Cloud) or synced on your schedule (Local)
|
|
174
|
+
|
|
175
|
+
## Real-World Examples
|
|
176
|
+
|
|
177
|
+
**Seat-based SaaS:**
|
|
96
178
|
```typescript
|
|
97
179
|
if (await policy.increment('org_123', 'seats')) {
|
|
98
|
-
|
|
180
|
+
await db.addUserToOrg(userId, orgId);
|
|
99
181
|
}
|
|
100
182
|
```
|
|
101
183
|
|
|
102
|
-
**
|
|
184
|
+
**AI product with token limits:**
|
|
103
185
|
```typescript
|
|
104
|
-
if (await policy.allow('user_456', '
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
} else {
|
|
108
|
-
// denied: limit exceeded
|
|
186
|
+
if (await policy.allow('user_456', 'tokens', estimatedTokens)) {
|
|
187
|
+
const response = await callLLM(prompt);
|
|
188
|
+
// Usage recorded, synced in background
|
|
109
189
|
}
|
|
110
190
|
```
|
|
111
191
|
|
|
112
|
-
**Feature
|
|
192
|
+
**Feature gating:**
|
|
113
193
|
```typescript
|
|
114
|
-
const
|
|
194
|
+
const canExport = await policy.allow('user_789', 'pdf_export');
|
|
195
|
+
if (!canExport) {
|
|
196
|
+
return { error: 'Upgrade to export PDFs' };
|
|
197
|
+
}
|
|
115
198
|
```
|
|
116
199
|
|
|
117
|
-
##
|
|
200
|
+
## Why Policies Are Better Than Code
|
|
118
201
|
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
const policy = await Limitr.new(policyDocument);
|
|
122
|
-
```
|
|
123
|
-
- Runs entirely in your app
|
|
124
|
-
- No external dependencies
|
|
125
|
-
- Perfect for self-hosted deployments
|
|
202
|
+
Update your free tier limit at 3pm on Friday. All users see the new limit and it's enforced instantly. No deploy, no invalidation, no coordination.
|
|
126
203
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
```
|
|
133
|
-
- Syncs policy and customer data automatically
|
|
134
|
-
- Stripe integration built-in (no Stripe dependencies in your app)
|
|
135
|
-
- UI included (pricing tables, plan selection, invoices, cancel/resume, etc)
|
|
136
|
-
- Dashboard for managing plans and customers
|
|
137
|
-
- Analytics & events (payments, revenue, margins)
|
|
138
|
-
- Create/change pricing in a couple of minutes without redeploys
|
|
204
|
+
**Code:**
|
|
205
|
+
- Scattered across files
|
|
206
|
+
- Requires deploys to change
|
|
207
|
+
- Hard to audit
|
|
208
|
+
- Breaks with typos
|
|
139
209
|
|
|
140
|
-
|
|
210
|
+
**Policy:**
|
|
211
|
+
- Single source of truth
|
|
212
|
+
- Change without deploying
|
|
213
|
+
- Human-readable by customers and auditors
|
|
214
|
+
- Type-safe (compiled to WebAssembly)
|
|
215
|
+
|
|
216
|
+
## Tech Details
|
|
217
|
+
|
|
218
|
+
- Written in [Stof](https://docs.stof.dev) (compiles to WebAssembly)
|
|
219
|
+
- Microsecond enforcement (no network calls)
|
|
220
|
+
- Runs in Node.js, browsers, Deno, Bun
|
|
221
|
+
- Deterministic (same input = same output)
|
|
222
|
+
- No external dependencies for local mode
|
|
223
|
+
- Offline-first (no API calls required)
|
|
141
224
|
|
|
142
225
|
## Documentation
|
|
143
|
-
|
|
226
|
+
|
|
227
|
+
- 📖 [Full Docs](https://formata.gitbook.io/limitr)
|
|
144
228
|
- 🚀 [Local Quick Start](https://formata.gitbook.io/limitr/local/quick-start)
|
|
145
229
|
- ☁️ [Cloud Quick Start](https://formata.gitbook.io/limitr/cloud/quick-start)
|
|
146
|
-
- 💬 [Discord
|
|
230
|
+
- 💬 [Discord](https://discord.gg/Up5kxdeXZt)
|
|
147
231
|
|
|
148
|
-
##
|
|
149
|
-
- AI products with usage-based pricing
|
|
150
|
-
- Developer tools with seat or API limits
|
|
151
|
-
- SaaS apps that need flexible pricing
|
|
152
|
-
- Open-source projects offering paid tiers
|
|
153
|
-
- Anyone tired of hardcoding pricing logic
|
|
232
|
+
## Contributing
|
|
154
233
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
- **Portable** - runs in Node.js, browsers, Deno, Bun
|
|
159
|
-
- **Auditable** - human-readable policy documents
|
|
234
|
+
Issues & PRs: [GitHub](https://github.com/dev-formata-io/limitr)
|
|
235
|
+
Questions: [Discord](https://discord.gg/Up5kxdeXZt)
|
|
236
|
+
Contact: info@limitr.dev
|
|
160
237
|
|
|
161
238
|
## License
|
|
162
|
-
Apache 2.0 - See [LICENSE](LICENSE)
|
|
163
239
|
|
|
164
|
-
|
|
165
|
-
- Issues & PRs: [GitHub](https://github.com/dev-formata-io/limitr)
|
|
166
|
-
- Questions: [Discord](https://discord.gg/Up5kxdeXZt)
|
|
167
|
-
- Contact: info@limitr.dev
|
|
240
|
+
Apache 2.0
|
package/dist/limitr.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limitr.d.ts","sourceRoot":"","sources":["../limitr.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"limitr.d.ts","sourceRoot":"","sources":["../limitr.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,yBAAqyivB,CAAC"}
|