@shaferllc/keel 0.79.0 → 0.80.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 (44) hide show
  1. package/README.md +3 -1
  2. package/dist/billing/billable.d.ts +83 -0
  3. package/dist/billing/billable.js +177 -0
  4. package/dist/billing/billing.config.stub +33 -0
  5. package/dist/billing/builder.d.ts +54 -0
  6. package/dist/billing/builder.js +104 -0
  7. package/dist/billing/config.d.ts +43 -0
  8. package/dist/billing/config.js +35 -0
  9. package/dist/billing/crypto.d.ts +11 -0
  10. package/dist/billing/crypto.js +27 -0
  11. package/dist/billing/drivers/fake.d.ts +58 -0
  12. package/dist/billing/drivers/fake.js +190 -0
  13. package/dist/billing/drivers/index.d.ts +11 -0
  14. package/dist/billing/drivers/index.js +22 -0
  15. package/dist/billing/drivers/paddle.d.ts +39 -0
  16. package/dist/billing/drivers/paddle.js +197 -0
  17. package/dist/billing/drivers/stripe.d.ts +33 -0
  18. package/dist/billing/drivers/stripe.js +278 -0
  19. package/dist/billing/events.d.ts +25 -0
  20. package/dist/billing/events.js +7 -0
  21. package/dist/billing/gateway.d.ts +170 -0
  22. package/dist/billing/gateway.js +24 -0
  23. package/dist/billing/index.d.ts +28 -0
  24. package/dist/billing/index.js +19 -0
  25. package/dist/billing/manager.d.ts +34 -0
  26. package/dist/billing/manager.js +61 -0
  27. package/dist/billing/migration.d.ts +13 -0
  28. package/dist/billing/migration.js +68 -0
  29. package/dist/billing/provider.d.ts +20 -0
  30. package/dist/billing/provider.js +42 -0
  31. package/dist/billing/routes.d.ts +11 -0
  32. package/dist/billing/routes.js +21 -0
  33. package/dist/billing/subscription-item.d.ts +18 -0
  34. package/dist/billing/subscription-item.js +11 -0
  35. package/dist/billing/subscription.d.ts +85 -0
  36. package/dist/billing/subscription.js +157 -0
  37. package/dist/billing/webhooks.d.ts +26 -0
  38. package/dist/billing/webhooks.js +75 -0
  39. package/docs/ai-manifest.json +8 -1
  40. package/docs/billing.md +242 -0
  41. package/docs/packages.md +3 -1
  42. package/llms-full.txt +252 -1
  43. package/llms.txt +1 -0
  44. package/package.json +6 -2
@@ -0,0 +1,242 @@
1
+ # Billing
2
+
3
+ Keel Billing is a subscription-billing layer — a port of [Laravel
4
+ Cashier](https://laravel.com/docs/13.x/billing) — for charging customers,
5
+ managing subscriptions, and reconciling gateway state through webhooks. It ships
6
+ as a Keel [package](./packages.md) and supports two gateways behind one API:
7
+ **Stripe** and **Paddle**.
8
+
9
+ It attaches to a model with a mixin. Your `User` becomes billable, gains a
10
+ gateway customer, and can create subscriptions and charges:
11
+
12
+ ```ts
13
+ import { Model } from "@shaferllc/keel/core";
14
+ import { Billable } from "@shaferllc/keel/billing";
15
+
16
+ export class User extends Billable(Model) {
17
+ static table = "users";
18
+ declare email: string;
19
+ }
20
+ ```
21
+
22
+ ## Install
23
+
24
+ ```ts
25
+ // bootstrap/providers.ts
26
+ import { BillingServiceProvider } from "@shaferllc/keel/billing";
27
+
28
+ export const providers = [AppServiceProvider, BillingServiceProvider];
29
+ ```
30
+
31
+ Publish the config and create the tables:
32
+
33
+ ```bash
34
+ keel vendor:publish --tag billing-config # writes config/billing.ts
35
+ keel migrate # creates subscriptions + subscription_items,
36
+ # and adds billing columns to users
37
+ ```
38
+
39
+ Set your keys in `.env`:
40
+
41
+ ```ini
42
+ BILLING_GATEWAY=stripe # or "paddle"
43
+ STRIPE_SECRET_KEY=sk_...
44
+ STRIPE_WEBHOOK_SECRET=whsec_...
45
+ # Paddle:
46
+ PADDLE_API_KEY=...
47
+ PADDLE_WEBHOOK_SECRET=...
48
+ PADDLE_CLIENT_TOKEN=...
49
+ PADDLE_SANDBOX=true
50
+ ```
51
+
52
+ ## One API, two gateways
53
+
54
+ Everything you call goes through a gateway-neutral interface, so switching from
55
+ Stripe to Paddle is a config change. The active gateway comes from
56
+ `config("billing.default")`; a billable can also carry its own in
57
+ `billing_gateway`. Money is always an integer in the smallest currency unit
58
+ (cents). See [Gateway differences](#gateway-differences) for where Paddle's
59
+ merchant-of-record model diverges.
60
+
61
+ ## Customers
62
+
63
+ A gateway customer is created lazily the first time you need one, but you can
64
+ create it up front:
65
+
66
+ ```ts
67
+ await user.createAsCustomer(); // creates the customer, stores its id
68
+ user.hasBillingId(); // true
69
+ await user.getCustomerId(); // the id (creates if missing)
70
+ ```
71
+
72
+ Override what gets synced by defining `billingName()` / `billingEmail()` on your
73
+ model. By default they read `name` / `email`.
74
+
75
+ ## Subscriptions
76
+
77
+ Build a subscription with the fluent builder:
78
+
79
+ ```ts
80
+ await user
81
+ .newSubscription("default", "price_pro")
82
+ .trialDays(14)
83
+ .quantity(3)
84
+ .create(paymentMethodId); // paymentMethodId optional if a default is on file
85
+ ```
86
+
87
+ Multiple prices (add-ons) are an array; `withMetadata`, `trialUntil`, and
88
+ `skipTrial` are also available. To send the customer to a hosted checkout
89
+ instead of charging now, swap `.create()` for `.checkout()`:
90
+
91
+ ```ts
92
+ const session = await user
93
+ .newSubscription("default", "price_pro")
94
+ .checkout({ successUrl: "...", cancelUrl: "..." });
95
+ // Stripe: redirect to session.url. Paddle: open the overlay with session.clientToken.
96
+ ```
97
+
98
+ ### Status
99
+
100
+ Status questions are answered from local columns — no gateway round-trip:
101
+
102
+ ```ts
103
+ await user.subscribed(); // valid (active | trial | grace)
104
+ await user.subscribedToPrice("price_pro");
105
+ await user.onTrial();
106
+ const sub = await user.subscription(); // the "default" subscription, or null
107
+
108
+ sub.active(); sub.onTrial(); sub.recurring();
109
+ sub.canceled(); sub.onGracePeriod(); sub.ended();
110
+ sub.paused(); sub.valid(); sub.hasIncompletePayment();
111
+ ```
112
+
113
+ ### Changing a subscription
114
+
115
+ ```ts
116
+ await sub.swap("price_enterprise"); // change price(s)
117
+ await sub.updateQuantity(10);
118
+ await sub.incrementQuantity(2);
119
+ await sub.decrementQuantity();
120
+ ```
121
+
122
+ Each of these calls the gateway and syncs the result back into the local row.
123
+
124
+ ### Cancelling
125
+
126
+ ```ts
127
+ await sub.cancel(); // at period end — access continues through the grace period
128
+ await sub.onGracePeriod();// true
129
+ await sub.resume(); // revive a subscription still in its grace period
130
+ await sub.cancelNow(); // immediately; sub.ended() becomes true
131
+ ```
132
+
133
+ ### Trials
134
+
135
+ ```ts
136
+ await sub.endTrial();
137
+ await sub.extendTrial(new Date("2026-01-01"));
138
+ user.onGenericTrial(); // a trial_ends_at on the user, before any subscription
139
+ ```
140
+
141
+ ## Single charges
142
+
143
+ ```ts
144
+ const charge = await user.charge(2000, { paymentMethod: "pm_1", description: "Credits" });
145
+ await user.refund(charge.id); // full refund
146
+ await user.refund(charge.id, 500); // partial
147
+
148
+ const session = await user.checkout("price_onetime", { successUrl, cancelUrl });
149
+ ```
150
+
151
+ ## Payment methods (Stripe)
152
+
153
+ Collect a card with a SetupIntent, then create the subscription with the
154
+ resulting payment method:
155
+
156
+ ```ts
157
+ const intent = await user.createSetupIntent(); // return intent.clientSecret to the front end
158
+ const methods = await user.paymentMethods();
159
+ ```
160
+
161
+ These are Stripe-only capabilities; calling them on the Paddle gateway throws a
162
+ `BillingError` (Paddle collects cards in its own hosted checkout).
163
+
164
+ ## Invoices
165
+
166
+ ```ts
167
+ const invoices = await user.invoices(); // GatewayInvoice[] — total, currency, status, url
168
+ ```
169
+
170
+ ## Webhooks
171
+
172
+ The package mounts one webhook endpoint per gateway at
173
+ `config("billing.webhook.path")`:
174
+
175
+ ```
176
+ POST /billing/webhook/stripe
177
+ POST /billing/webhook/paddle
178
+ ```
179
+
180
+ Point your gateway dashboard at the matching URL. Each request is verified
181
+ against the gateway's signing secret (HMAC-SHA256 over the raw body), the local
182
+ subscription is synced, and typed events fire:
183
+
184
+ ```ts
185
+ import { listen } from "@shaferllc/keel/core";
186
+
187
+ listen("billing.subscription.updated", (e) => {
188
+ // e.gateway, e.subscriptionId, e.providerId, e.status
189
+ });
190
+ listen("billing.webhook.received", (e) => { /* e.gateway, e.type, e.id */ });
191
+ ```
192
+
193
+ Events: `billing.webhook.received`, `billing.subscription.created` / `.updated`
194
+ / `.deleted`.
195
+
196
+ An update to a subscription already in your database is always synced. A brand
197
+ new subscription born from a Paddle checkout has no local row yet — register a
198
+ resolver so the handler can create it:
199
+
200
+ ```ts
201
+ import { resolveBillableUsing } from "@shaferllc/keel/billing";
202
+
203
+ resolveBillableUsing(async (customerId) => {
204
+ const user = (await User.query().where("billing_customer_id", customerId).first());
205
+ return user ? { id: user.id, type: "User" } : null;
206
+ });
207
+ ```
208
+
209
+ ## Gateway differences
210
+
211
+ | Concern | Stripe | Paddle |
212
+ |---------|--------|--------|
213
+ | Create a subscription server-side | `create(pmId)` | Not supported — use `checkout()`; the webhook creates the local row |
214
+ | One-off `charge()` | Confirms a PaymentIntent | Not supported — use `checkout({ mode })` / transactions |
215
+ | SetupIntent / `paymentMethods()` | Supported | Throws `BillingError` (hosted checkout) |
216
+ | Checkout handle | `session.url` (redirect) | `session.clientToken` (overlay/inline) |
217
+ | Webhook signature | `Stripe-Signature: t=…,v1=…` | `Paddle-Signature: ts=…;h1=…` |
218
+
219
+ ## Schema
220
+
221
+ The migration is gateway-neutral: `subscriptions` (with `gateway`,
222
+ `provider_id`, `provider_status`, `provider_price`, trial/grace timestamps),
223
+ `subscription_items`, and columns on `users` (`billing_gateway`,
224
+ `billing_customer_id`, `pm_type`, `pm_last_four`, `trial_ends_at`). Cashier
225
+ targets the standard `users` billable table.
226
+
227
+ ## Testing
228
+
229
+ The package ships a `FakeGateway` — an in-memory gateway that records every call
230
+ — so you can drive billing without touching a network:
231
+
232
+ ```ts
233
+ import { BillingManager, setBilling, FakeGateway } from "@shaferllc/keel/billing";
234
+
235
+ const fake = new FakeGateway();
236
+ const manager = new BillingManager(config); // config.default = "fake"
237
+ manager.register("fake", () => fake);
238
+ setBilling(manager);
239
+
240
+ await user.newSubscription("default", "price_pro").create();
241
+ fake.calls.filter((c) => c.method === "createSubscription"); // assert what was asked
242
+ ```
package/docs/packages.md CHANGED
@@ -7,7 +7,9 @@ adds the conventions a *shippable* package needs so it can carry its own schema
7
7
  and assets instead of asking the app to wire them by hand.
8
8
 
9
9
  [Keel Watch](./watch.md) — the debug dashboard — is a first-party package and the
10
- reference implementation of everything below.
10
+ reference implementation of everything below. [Billing](./billing.md) (a Cashier
11
+ port for Stripe and Paddle) is another, and shows a package contributing models,
12
+ a schema migration, gateway drivers, and verified webhook routes.
11
13
 
12
14
  ## The shape of a package
13
15
 
package/llms-full.txt CHANGED
@@ -5332,6 +5332,255 @@ all gates/policies/hooks (a test helper).
5332
5332
 
5333
5333
 
5334
5334
 
5335
+ ---
5336
+
5337
+ <!-- source: docs/billing.md -->
5338
+
5339
+ # Billing
5340
+
5341
+ Keel Billing is a subscription-billing layer — a port of [Laravel
5342
+ Cashier](https://laravel.com/docs/13.x/billing) — for charging customers,
5343
+ managing subscriptions, and reconciling gateway state through webhooks. It ships
5344
+ as a Keel [package](./packages.md) and supports two gateways behind one API:
5345
+ **Stripe** and **Paddle**.
5346
+
5347
+ It attaches to a model with a mixin. Your `User` becomes billable, gains a
5348
+ gateway customer, and can create subscriptions and charges:
5349
+
5350
+ ```ts
5351
+ import { Model } from "@shaferllc/keel/core";
5352
+ import { Billable } from "@shaferllc/keel/billing";
5353
+
5354
+ export class User extends Billable(Model) {
5355
+ static table = "users";
5356
+ declare email: string;
5357
+ }
5358
+ ```
5359
+
5360
+ ## Install
5361
+
5362
+ ```ts
5363
+ // bootstrap/providers.ts
5364
+ import { BillingServiceProvider } from "@shaferllc/keel/billing";
5365
+
5366
+ export const providers = [AppServiceProvider, BillingServiceProvider];
5367
+ ```
5368
+
5369
+ Publish the config and create the tables:
5370
+
5371
+ ```bash
5372
+ keel vendor:publish --tag billing-config # writes config/billing.ts
5373
+ keel migrate # creates subscriptions + subscription_items,
5374
+ # and adds billing columns to users
5375
+ ```
5376
+
5377
+ Set your keys in `.env`:
5378
+
5379
+ ```ini
5380
+ BILLING_GATEWAY=stripe # or "paddle"
5381
+ STRIPE_SECRET_KEY=sk_...
5382
+ STRIPE_WEBHOOK_SECRET=whsec_...
5383
+ # Paddle:
5384
+ PADDLE_API_KEY=...
5385
+ PADDLE_WEBHOOK_SECRET=...
5386
+ PADDLE_CLIENT_TOKEN=...
5387
+ PADDLE_SANDBOX=true
5388
+ ```
5389
+
5390
+ ## One API, two gateways
5391
+
5392
+ Everything you call goes through a gateway-neutral interface, so switching from
5393
+ Stripe to Paddle is a config change. The active gateway comes from
5394
+ `config("billing.default")`; a billable can also carry its own in
5395
+ `billing_gateway`. Money is always an integer in the smallest currency unit
5396
+ (cents). See [Gateway differences](#gateway-differences) for where Paddle's
5397
+ merchant-of-record model diverges.
5398
+
5399
+ ## Customers
5400
+
5401
+ A gateway customer is created lazily the first time you need one, but you can
5402
+ create it up front:
5403
+
5404
+ ```ts
5405
+ await user.createAsCustomer(); // creates the customer, stores its id
5406
+ user.hasBillingId(); // true
5407
+ await user.getCustomerId(); // the id (creates if missing)
5408
+ ```
5409
+
5410
+ Override what gets synced by defining `billingName()` / `billingEmail()` on your
5411
+ model. By default they read `name` / `email`.
5412
+
5413
+ ## Subscriptions
5414
+
5415
+ Build a subscription with the fluent builder:
5416
+
5417
+ ```ts
5418
+ await user
5419
+ .newSubscription("default", "price_pro")
5420
+ .trialDays(14)
5421
+ .quantity(3)
5422
+ .create(paymentMethodId); // paymentMethodId optional if a default is on file
5423
+ ```
5424
+
5425
+ Multiple prices (add-ons) are an array; `withMetadata`, `trialUntil`, and
5426
+ `skipTrial` are also available. To send the customer to a hosted checkout
5427
+ instead of charging now, swap `.create()` for `.checkout()`:
5428
+
5429
+ ```ts
5430
+ const session = await user
5431
+ .newSubscription("default", "price_pro")
5432
+ .checkout({ successUrl: "...", cancelUrl: "..." });
5433
+ // Stripe: redirect to session.url. Paddle: open the overlay with session.clientToken.
5434
+ ```
5435
+
5436
+ ### Status
5437
+
5438
+ Status questions are answered from local columns — no gateway round-trip:
5439
+
5440
+ ```ts
5441
+ await user.subscribed(); // valid (active | trial | grace)
5442
+ await user.subscribedToPrice("price_pro");
5443
+ await user.onTrial();
5444
+ const sub = await user.subscription(); // the "default" subscription, or null
5445
+
5446
+ sub.active(); sub.onTrial(); sub.recurring();
5447
+ sub.canceled(); sub.onGracePeriod(); sub.ended();
5448
+ sub.paused(); sub.valid(); sub.hasIncompletePayment();
5449
+ ```
5450
+
5451
+ ### Changing a subscription
5452
+
5453
+ ```ts
5454
+ await sub.swap("price_enterprise"); // change price(s)
5455
+ await sub.updateQuantity(10);
5456
+ await sub.incrementQuantity(2);
5457
+ await sub.decrementQuantity();
5458
+ ```
5459
+
5460
+ Each of these calls the gateway and syncs the result back into the local row.
5461
+
5462
+ ### Cancelling
5463
+
5464
+ ```ts
5465
+ await sub.cancel(); // at period end — access continues through the grace period
5466
+ await sub.onGracePeriod();// true
5467
+ await sub.resume(); // revive a subscription still in its grace period
5468
+ await sub.cancelNow(); // immediately; sub.ended() becomes true
5469
+ ```
5470
+
5471
+ ### Trials
5472
+
5473
+ ```ts
5474
+ await sub.endTrial();
5475
+ await sub.extendTrial(new Date("2026-01-01"));
5476
+ user.onGenericTrial(); // a trial_ends_at on the user, before any subscription
5477
+ ```
5478
+
5479
+ ## Single charges
5480
+
5481
+ ```ts
5482
+ const charge = await user.charge(2000, { paymentMethod: "pm_1", description: "Credits" });
5483
+ await user.refund(charge.id); // full refund
5484
+ await user.refund(charge.id, 500); // partial
5485
+
5486
+ const session = await user.checkout("price_onetime", { successUrl, cancelUrl });
5487
+ ```
5488
+
5489
+ ## Payment methods (Stripe)
5490
+
5491
+ Collect a card with a SetupIntent, then create the subscription with the
5492
+ resulting payment method:
5493
+
5494
+ ```ts
5495
+ const intent = await user.createSetupIntent(); // return intent.clientSecret to the front end
5496
+ const methods = await user.paymentMethods();
5497
+ ```
5498
+
5499
+ These are Stripe-only capabilities; calling them on the Paddle gateway throws a
5500
+ `BillingError` (Paddle collects cards in its own hosted checkout).
5501
+
5502
+ ## Invoices
5503
+
5504
+ ```ts
5505
+ const invoices = await user.invoices(); // GatewayInvoice[] — total, currency, status, url
5506
+ ```
5507
+
5508
+ ## Webhooks
5509
+
5510
+ The package mounts one webhook endpoint per gateway at
5511
+ `config("billing.webhook.path")`:
5512
+
5513
+ ```
5514
+ POST /billing/webhook/stripe
5515
+ POST /billing/webhook/paddle
5516
+ ```
5517
+
5518
+ Point your gateway dashboard at the matching URL. Each request is verified
5519
+ against the gateway's signing secret (HMAC-SHA256 over the raw body), the local
5520
+ subscription is synced, and typed events fire:
5521
+
5522
+ ```ts
5523
+ import { listen } from "@shaferllc/keel/core";
5524
+
5525
+ listen("billing.subscription.updated", (e) => {
5526
+ // e.gateway, e.subscriptionId, e.providerId, e.status
5527
+ });
5528
+ listen("billing.webhook.received", (e) => { /* e.gateway, e.type, e.id */ });
5529
+ ```
5530
+
5531
+ Events: `billing.webhook.received`, `billing.subscription.created` / `.updated`
5532
+ / `.deleted`.
5533
+
5534
+ An update to a subscription already in your database is always synced. A brand
5535
+ new subscription born from a Paddle checkout has no local row yet — register a
5536
+ resolver so the handler can create it:
5537
+
5538
+ ```ts
5539
+ import { resolveBillableUsing } from "@shaferllc/keel/billing";
5540
+
5541
+ resolveBillableUsing(async (customerId) => {
5542
+ const user = (await User.query().where("billing_customer_id", customerId).first());
5543
+ return user ? { id: user.id, type: "User" } : null;
5544
+ });
5545
+ ```
5546
+
5547
+ ## Gateway differences
5548
+
5549
+ | Concern | Stripe | Paddle |
5550
+ |---------|--------|--------|
5551
+ | Create a subscription server-side | `create(pmId)` | Not supported — use `checkout()`; the webhook creates the local row |
5552
+ | One-off `charge()` | Confirms a PaymentIntent | Not supported — use `checkout({ mode })` / transactions |
5553
+ | SetupIntent / `paymentMethods()` | Supported | Throws `BillingError` (hosted checkout) |
5554
+ | Checkout handle | `session.url` (redirect) | `session.clientToken` (overlay/inline) |
5555
+ | Webhook signature | `Stripe-Signature: t=…,v1=…` | `Paddle-Signature: ts=…;h1=…` |
5556
+
5557
+ ## Schema
5558
+
5559
+ The migration is gateway-neutral: `subscriptions` (with `gateway`,
5560
+ `provider_id`, `provider_status`, `provider_price`, trial/grace timestamps),
5561
+ `subscription_items`, and columns on `users` (`billing_gateway`,
5562
+ `billing_customer_id`, `pm_type`, `pm_last_four`, `trial_ends_at`). Cashier
5563
+ targets the standard `users` billable table.
5564
+
5565
+ ## Testing
5566
+
5567
+ The package ships a `FakeGateway` — an in-memory gateway that records every call
5568
+ — so you can drive billing without touching a network:
5569
+
5570
+ ```ts
5571
+ import { BillingManager, setBilling, FakeGateway } from "@shaferllc/keel/billing";
5572
+
5573
+ const fake = new FakeGateway();
5574
+ const manager = new BillingManager(config); // config.default = "fake"
5575
+ manager.register("fake", () => fake);
5576
+ setBilling(manager);
5577
+
5578
+ await user.newSubscription("default", "price_pro").create();
5579
+ fake.calls.filter((c) => c.method === "createSubscription"); // assert what was asked
5580
+ ```
5581
+
5582
+
5583
+
5335
5584
  ---
5336
5585
 
5337
5586
  <!-- source: docs/broadcasting.md -->
@@ -14329,7 +14578,9 @@ adds the conventions a *shippable* package needs so it can carry its own schema
14329
14578
  and assets instead of asking the app to wire them by hand.
14330
14579
 
14331
14580
  [Keel Watch](./watch.md) — the debug dashboard — is a first-party package and the
14332
- reference implementation of everything below.
14581
+ reference implementation of everything below. [Billing](./billing.md) (a Cashier
14582
+ port for Stripe and Paddle) is another, and shows a package contributing models,
14583
+ a schema migration, gateway drivers, and verified webhook routes.
14333
14584
 
14334
14585
  ## The shape of a package
14335
14586
 
package/llms.txt CHANGED
@@ -14,6 +14,7 @@ Userland imports everything from `@shaferllc/keel/core`.
14
14
  - [Architecture](https://github.com/shaferllc/keel/blob/main/docs/architecture.md): Keel is small on purpose. This page maps the pieces and traces a request from socket to response. Nothing here is magic — every layer is a short, readable file in src/core/, and this guide is mostly a reading order for it.
15
15
  - [Authentication](https://github.com/shaferllc/keel/blob/main/docs/authentication.md): Session-based auth built on the pieces you already have: sessions hold the login, hashing checks passwords.
16
16
  - [Authorization](https://github.com/shaferllc/keel/blob/main/docs/authorization.md): Where authentication answers who you are, authorization answers what you're allowed to do.
17
+ - [Billing](https://github.com/shaferllc/keel/blob/main/docs/billing.md): Keel Billing is a subscription-billing layer — a port of Laravel Cashier — for charging customers, managing subscriptions, and reconciling gateway state through webhooks.
17
18
  - [Broadcasting](https://github.com/shaferllc/keel/blob/main/docs/broadcasting.md): Push events to clients in real time over named channels.
18
19
  - [Service Broker](https://github.com/shaferllc/keel/blob/main/docs/broker.md): Structure an application as services that talk to each other by name instead of by import.
19
20
  - [Cache](https://github.com/shaferllc/keel/blob/main/docs/cache.md): A small cache with TTLs and the remember pattern.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shaferllc/keel",
3
- "version": "0.79.0",
3
+ "version": "0.80.0",
4
4
  "type": "module",
5
5
  "description": "The house framework for Node.js — a service container, providers, routing, JSX views, and a code-generating console.",
6
6
  "license": "MIT",
@@ -61,6 +61,10 @@
61
61
  "types": "./dist/api/index.d.ts",
62
62
  "import": "./dist/api/index.js"
63
63
  },
64
+ "./billing": {
65
+ "types": "./dist/billing/index.d.ts",
66
+ "import": "./dist/billing/index.js"
67
+ },
64
68
  "./cli": {
65
69
  "types": "./dist/core/cli/index.d.ts",
66
70
  "import": "./dist/core/cli/index.js"
@@ -89,7 +93,7 @@
89
93
  "test:coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-exclude='tests/**' tests/*.test.ts",
90
94
  "build:ai": "tsx scripts/build-ai.ts",
91
95
  "build:watch-ui": "vite build --config src/watch/ui/vite.config.ts",
92
- "build:watch-copy": "mkdir -p dist/watch/ui && cp -R src/watch/ui/dist dist/watch/ui/dist && cp src/watch/watch.config.stub dist/watch/ && cp src/openapi/openapi.config.stub dist/openapi/ && cp src/api/api.config.stub dist/api/",
96
+ "build:watch-copy": "mkdir -p dist/watch/ui && cp -R src/watch/ui/dist dist/watch/ui/dist && cp src/watch/watch.config.stub dist/watch/ && cp src/openapi/openapi.config.stub dist/openapi/ && cp src/api/api.config.stub dist/api/ && cp src/billing/billing.config.stub dist/billing/",
93
97
  "build": "npm run build:ai && npm run build:watch-ui && rm -rf dist && tsc -p tsconfig.build.json && npm run build:watch-copy",
94
98
  "prepare": "npm run build",
95
99
  "typecheck:tests": "tsc --noEmit -p tsconfig.tests.json",