@formata/limitr 0.5.13

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 ADDED
@@ -0,0 +1,145 @@
1
+ # Limitr: Open-Source Monetization Policy
2
+ **Limitr is an embedded, open-source policy engine for enforcing plans, limits, and usage in your application.**
3
+
4
+ It is designed for AI products, developer tools, and open-source software where monetization logic must be:
5
+ - inspectable
6
+ - portable
7
+ - deterministic
8
+ - and not hard-coded into application logic
9
+
10
+ > Limitr answers the question:
11
+ > **"What is this customer allowed to consume, right now, and what happens if it exceeds the limit?"**
12
+
13
+ - 👉 Jump to the [Quick Example](#example-seat-based-plan-enforcement-typescript)
14
+ - Additional [info and examples](https://docs.stof.dev/applications/limitr)
15
+ - [Limitr Cloud](https://limitr.dev)
16
+
17
+ ## What Limitr Gives You
18
+ - Define plans, entitlements, and limits in a policy document — not application code
19
+ - Enforce limits locally and offline, embedded directly in your app
20
+ - Change limits without redeploying
21
+ - Stripe-agnostic and billing-system-agnostic
22
+ - Inspectable and auditable by developers and customers
23
+ - Event-driven enforcement (usage changes, overages, denials)
24
+ - Policy evolves independently from product code
25
+ - Built on [Stof](https://docs.stof.dev), an open-source data + logic runtime
26
+
27
+ ## What Limitr Is Not
28
+ - Not a billing system
29
+ - Not a payment processor
30
+ - Not a hosted SaaS requirement
31
+ - Not a feature-flag system (outside of entitlements)
32
+
33
+ Limitr enforces **truth** about usage and limits.
34
+ Billing and payments can subscribe to Limitr’s events.
35
+
36
+ Limitr is designed to integrate cleanly with existing systems, not replace them.
37
+
38
+ ## Why
39
+ Most applications implement monetization logic in files like `limits.ts`:
40
+ - seat limits
41
+ - usage caps
42
+ - plan checks
43
+ - special cases and overrides
44
+
45
+ Over time, this logic:
46
+ - becomes tightly coupled to the app
47
+ - requires redeploys to change pricing
48
+ - is hard to audit or explain
49
+ - breaks down with usage-based or AI pricing
50
+
51
+ Payments systems (like Stripe) handle money, but not **enforcement**.
52
+
53
+ Limitr separates **monetization policy** from application logic, so limits are:
54
+ - explicit
55
+ - portable
56
+ - testable
57
+ - and easy to evolve
58
+
59
+ ## Who Limitr Is For
60
+ Limitr is a good fit if you are:
61
+ - Building an AI or usage-based product
62
+ - Implementing seat-based or credit-based pricing
63
+ - Shipping developer tools or infrastructure
64
+ - Supporting self-hosted or open-source deployments
65
+ - Tired of hardcoding pricing logic in application code
66
+
67
+ ## Example: Seat-Based Plan Enforcement (TypeScript [JSR](https://jsr.io/@formata/limitr))
68
+ ```typescript
69
+ import { Limitr } from 'jsr:@formata/limitr';
70
+
71
+ // Load a Limitr policy from a DB, string, file, API, etc.
72
+ // Stof is the default format, but can also be yaml, json, etc.
73
+ const policy = await Limitr.new(`
74
+ policy:
75
+ credits:
76
+ seat:
77
+ description: 'A single seat credit that can be tracked per customer.'
78
+ plans:
79
+ free:
80
+ entitlements:
81
+ seats:
82
+ description: 'Each customer (user, org, etc.) with this plan can have up to this many seats.'
83
+ limit:
84
+ credit: 'seat'
85
+ value: 1
86
+ increment: 1
87
+ paid:
88
+ entitlements:
89
+ seats:
90
+ description: 'Each customer (user, org, etc.) with this plan can have up to this many seats.'
91
+ limit:
92
+ credit: 'seat'
93
+ value: 3
94
+ increment: 1
95
+ `, 'yaml');
96
+
97
+ // Entitlements define features + limits (gated behaviors) on plans.
98
+ // Meters are state stored per customer per entitlement.
99
+
100
+ // Create/save/load customers (database, Stripe, etc.)
101
+ await policy.addCustomer('cus_free_customer', 'free');
102
+ await policy.addCustomer('cus_paid_customer', 'paid');
103
+
104
+ // Perform entitlement checks, meter usage, etc.
105
+ await policy.increment('cus_free_customer', 'seats'); // adds one seat
106
+ await policy.increment('cus_paid_customer', 'seats'); // adds one seat
107
+
108
+ // Add callbacks to the document directly or as a library function
109
+ policy.doc.lib('App', 'meter_limit', (json: string) => {
110
+ const record = JSON.parse(json);
111
+ if (record.customer.plan === 'free' && record.entitlement === 'seats') {
112
+ console.log('FREE PLAN SEAT LIMIT HIT, current: ', record.customer.meters.seats.value, ' requested: ', record.invalid_value);
113
+ }
114
+ });
115
+
116
+ // Will return false and emit an meter-limit event in the doc (if App.meter_limit exists, it will also be called)
117
+ if (await policy.increment('cus_free_customer', 'seats')) throw Error("will not get here");
118
+ if (await policy.allow('cus_free_customer', 'seats', 2)) throw Error("cannot request 2 additional seats..");
119
+ if (await policy.increment('cus_paid_customer', 'seats')) {
120
+ // paid customer can add a second seat
121
+ } else {
122
+ throw Error("will not get here");
123
+ }
124
+ ```
125
+ ```bash
126
+ > deno run --allow-all typescript/examples/seats.ts
127
+ FREE PLAN SEAT LIMIT HIT, current: 1 requested: 2
128
+ FREE PLAN SEAT LIMIT HIT, current: 1 requested: 3
129
+ ```
130
+
131
+ ### What's happening here?
132
+ - Plans define which entitlements a customer has
133
+ - Entitlements define how a meter is allowed to change with limits
134
+ - Meters are stored on each customer as state
135
+ - Limitr enforces limits and emits events when limits are hit
136
+ - The application decides how to respond (deny, warn, bill, notify)
137
+
138
+ ## License
139
+ Apache 2.0. See LICENSE for details.
140
+
141
+ ## Contributing
142
+ - Open issues or discussions on [GitHub](https://github.com/dev-formata-io/limitr)
143
+ - Chat with us on [Discord](https://discord.gg/Up5kxdeXZt)
144
+
145
+ > Reach out to info@stof.dev to contact us directly
package/dist/gate.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Internally ensure there's no overlap between policy calls.
3
+ * Supports both tail-append and head-prepend execution.
4
+ * WASM-safe promise gating.
5
+ */
6
+ export declare class LimitrGate {
7
+ private head;
8
+ private tail;
9
+ /**
10
+ * Run at the back of this scheduler (non-priority).
11
+ */
12
+ run<T>(fn: () => Promise<T> | T): Promise<T>;
13
+ /**
14
+ * Run at the front of this scheduler.
15
+ */
16
+ runFront<T>(fn: () => Promise<T> | T): Promise<T>;
17
+ }
18
+ /**
19
+ * Helper function for waiting for the WebSocket to open.
20
+ */
21
+ export declare function waitOnOpen(ws: WebSocket): Promise<void>;
22
+ //# sourceMappingURL=gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../gate.ts"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,qBAAa,UAAU;IAEnB,OAAO,CAAC,IAAI,CAAuC;IAGnD,OAAO,CAAC,IAAI,CAA+B;IAG3C;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAO5C;;OAEG;IACH,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAepD;AAGD;;GAEG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBvD"}
package/dist/gate.js ADDED
@@ -0,0 +1,79 @@
1
+ //
2
+ // Copyright 2026 Formata, Inc. All rights reserved.
3
+ //
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // you may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ //
8
+ // http://www.apache.org/licenses/LICENSE-2.0
9
+ //
10
+ // Unless required by applicable law or agreed to in writing, software
11
+ // distributed under the License is distributed on an "AS IS" BASIS,
12
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ // See the License for the specific language governing permissions and
14
+ // limitations under the License.
15
+ //
16
+ /**
17
+ * Internally ensure there's no overlap between policy calls.
18
+ * Supports both tail-append and head-prepend execution.
19
+ * WASM-safe promise gating.
20
+ */
21
+ export class LimitrGate {
22
+ constructor() {
23
+ // Earliest scheduled work
24
+ this.head = Promise.resolve();
25
+ // Latest scheduled work
26
+ this.tail = this.head;
27
+ }
28
+ /**
29
+ * Run at the back of this scheduler (non-priority).
30
+ */
31
+ run(fn) {
32
+ const next = this.tail.then(fn);
33
+ this.tail = next.catch(() => { });
34
+ return next;
35
+ }
36
+ /**
37
+ * Run at the front of this scheduler.
38
+ */
39
+ runFront(fn) {
40
+ let release;
41
+ const blocker = new Promise(resolve => {
42
+ release = resolve;
43
+ });
44
+ const prevTail = this.tail;
45
+ this.tail = blocker;
46
+ const next = Promise.resolve()
47
+ .then(fn)
48
+ .finally(() => {
49
+ this.tail = prevTail;
50
+ release();
51
+ });
52
+ return next;
53
+ }
54
+ }
55
+ /**
56
+ * Helper function for waiting for the WebSocket to open.
57
+ */
58
+ export function waitOnOpen(ws) {
59
+ if (ws.readyState === WebSocket.OPEN) {
60
+ return Promise.resolve();
61
+ }
62
+ return new Promise((resolve, reject) => {
63
+ const onOpen = () => {
64
+ cleanup();
65
+ resolve();
66
+ };
67
+ const onError = (err) => {
68
+ cleanup();
69
+ reject(err);
70
+ };
71
+ const cleanup = () => {
72
+ ws.removeEventListener("open", onOpen);
73
+ ws.removeEventListener("error", onError);
74
+ };
75
+ ws.addEventListener("open", onOpen);
76
+ ws.addEventListener("error", onError);
77
+ });
78
+ }
79
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../gate.ts"],"names":[],"mappings":"AAAA,EAAE;AACF,oDAAoD;AACpD,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,gDAAgD;AAChD,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AACjC,EAAE;AAGF;;;;GAIG;AACH,MAAM,OAAO,UAAU;IAAvB;QACI,0BAA0B;QAClB,SAAI,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;QAEnD,wBAAwB;QAChB,SAAI,GAAqB,IAAI,CAAC,IAAI,CAAC;IA+B/C,CAAC;IA5BG;;OAEG;IACH,GAAG,CAAI,EAAwB;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC;IAChB,CAAC;IAGD;;OAEG;IACH,QAAQ,CAAI,EAAwB;QAChC,IAAI,OAAoB,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE;YACxC,OAAO,GAAG,OAAO,CAAC;QACtB,CAAC,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC;QACpB,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE;aACzB,IAAI,CAAC,EAAE,CAAC;aACR,OAAO,CAAC,GAAG,EAAE;YACV,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC;YACrB,OAAO,EAAE,CAAC;QACd,CAAC,CAAC,CAAC;QACP,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ;AAGD;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,EAAa;IACpC,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,MAAM,MAAM,GAAG,GAAG,EAAE;YAChB,OAAO,EAAE,CAAC;YACV,OAAO,EAAE,CAAC;QACd,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,CAAC,GAAU,EAAE,EAAE;YAC3B,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,GAAG,EAAE;YACjB,EAAE,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACvC,EAAE,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7C,CAAC,CAAC;QACF,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACpC,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const limitrApi: Uint8Array<ArrayBuffer>;
2
+ //# sourceMappingURL=limitr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limitr.d.ts","sourceRoot":"","sources":["../limitr.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,yBAAyppkB,CAAC"}