@shaferllc/keel 0.83.0 → 0.83.2
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 +2 -2
- package/dist/billing/billable.d.ts +2 -3
- package/dist/billing/billable.js +2 -3
- package/dist/billing/billing.config.stub +1 -1
- package/dist/billing/builder.d.ts +2 -2
- package/dist/billing/builder.js +2 -2
- package/dist/billing/provider.d.ts +1 -1
- package/dist/billing/provider.js +2 -2
- package/dist/billing/subscription-item.d.ts +0 -1
- package/dist/billing/subscription-item.js +0 -1
- package/dist/core/decorators.d.ts +5 -7
- package/dist/core/decorators.js +5 -7
- package/dist/core/exceptions.d.ts +2 -3
- package/dist/core/exceptions.js +2 -3
- package/dist/core/model-query.d.ts +1 -1
- package/dist/core/model-query.js +1 -1
- package/dist/core/request-logger.d.ts +2 -2
- package/dist/core/request-logger.js +2 -2
- package/dist/core/request.js +1 -1
- package/dist/core/testing.d.ts +3 -3
- package/dist/core/testing.js +3 -3
- package/docs/ai-manifest.json +14 -14
- package/docs/billing.md +45 -6
- package/docs/changelog.md +124 -21
- package/docs/cors.md +28 -0
- package/docs/decorators.md +2 -6
- package/docs/errors.md +1 -1
- package/docs/examples/billing.ts +128 -0
- package/docs/examples/cors.ts +29 -0
- package/docs/examples/hono.ts +29 -0
- package/docs/examples/openapi.ts +35 -0
- package/docs/examples/orm.ts +30 -0
- package/docs/examples/packages.ts +26 -0
- package/docs/examples/query-builder.ts +118 -0
- package/docs/examples/security.ts +31 -0
- package/docs/examples/social-auth.ts +76 -0
- package/docs/examples/starter-kits.ts +14 -0
- package/docs/examples/watch.ts +10 -0
- package/docs/hooks.md +1 -3
- package/docs/logger.md +2 -2
- package/docs/orm.md +39 -0
- package/docs/packages.md +3 -3
- package/docs/providers.md +6 -6
- package/docs/request-response.md +2 -2
- package/docs/starter-kits.md +14 -0
- package/docs/testing.md +1 -2
- package/docs/validation.md +1 -2
- package/docs/watch.md +1 -1
- package/llms-full.txt +145 -34
- package/llms.txt +13 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -160,7 +160,7 @@ src/core/ The framework
|
|
|
160
160
|
src/db/ Database adapters (D1, Postgres, libSQL)
|
|
161
161
|
src/api/ CRUD REST resources from a model
|
|
162
162
|
src/openapi/ Generates an OpenAPI spec from the routes
|
|
163
|
-
src/billing/ Subscription billing — Stripe + Paddle
|
|
163
|
+
src/billing/ Subscription billing — Stripe + Paddle
|
|
164
164
|
src/watch/ The debug dashboard
|
|
165
165
|
src/mcp/ The MCP server (docs + API for AI agents)
|
|
166
166
|
src/vite/ The Vite plugin
|
|
@@ -228,7 +228,7 @@ See [docs/architecture.md](./docs/architecture.md) for the full picture.
|
|
|
228
228
|
| [Internationalization](./docs/i18n.md) | ICU messages, `Intl` formatters, locale detection |
|
|
229
229
|
| [Pages](./docs/pages.md) | Page-based routing — a file is a route |
|
|
230
230
|
| [Packages](./docs/packages.md) | Redistributable slices of an app: routes, migrations, commands |
|
|
231
|
-
| [Billing](./docs/billing.md) | Subscriptions, charges & webhooks — Stripe + Paddle
|
|
231
|
+
| [Billing](./docs/billing.md) | Subscriptions, charges & webhooks — Stripe + Paddle |
|
|
232
232
|
| [Watch](./docs/watch.md) | Debug dashboard — requests, queries, jobs, logs at `/watch` |
|
|
233
233
|
| [Views](./docs/views.md) | Hono JSX components, layouts, the View service |
|
|
234
234
|
| [Templates](./docs/templates.md) | `{{ }}` + `@`-tag templating engine, edge-safe |
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `Billable` mixin —
|
|
3
|
-
*
|
|
4
|
-
* and checkout:
|
|
2
|
+
* The `Billable` mixin — apply it to a model to give that model a gateway
|
|
3
|
+
* customer, subscriptions, charges, and checkout:
|
|
5
4
|
*
|
|
6
5
|
* class User extends Billable(Model) {
|
|
7
6
|
* static table = "users";
|
package/dist/billing/billable.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `Billable` mixin —
|
|
3
|
-
*
|
|
4
|
-
* and checkout:
|
|
2
|
+
* The `Billable` mixin — apply it to a model to give that model a gateway
|
|
3
|
+
* customer, subscriptions, charges, and checkout:
|
|
5
4
|
*
|
|
6
5
|
* class User extends Billable(Model) {
|
|
7
6
|
* static table = "users";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { env } from "@shaferllc/keel/core";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Billing configuration —
|
|
4
|
+
* Billing configuration — subscriptions and charges, covering Stripe and Paddle.
|
|
5
5
|
* Published with `keel vendor:publish --tag billing-config`.
|
|
6
6
|
*/
|
|
7
7
|
export default {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The fluent subscription builder —
|
|
3
|
-
*
|
|
2
|
+
* The fluent subscription builder — `newSubscription(...).create()`. The
|
|
3
|
+
* `Billable` mixin hands it a small `BillableTarget` (so this file needn't
|
|
4
4
|
* import the mixin, avoiding a cycle); the builder shapes the request, calls the
|
|
5
5
|
* gateway, and persists a local `Subscription` synced from the result.
|
|
6
6
|
*
|
package/dist/billing/builder.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The fluent subscription builder —
|
|
3
|
-
*
|
|
2
|
+
* The fluent subscription builder — `newSubscription(...).create()`. The
|
|
3
|
+
* `Billable` mixin hands it a small `BillableTarget` (so this file needn't
|
|
4
4
|
* import the mixin, avoiding a cycle); the builder shapes the request, calls the
|
|
5
5
|
* gateway, and persists a local `Subscription` synced from the result.
|
|
6
6
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Keel Billing —
|
|
2
|
+
* Keel Billing — subscription billing covering Stripe and Paddle, shipped as a
|
|
3
3
|
* Keel package. One line in `bootstrap/providers.ts` turns it on:
|
|
4
4
|
*
|
|
5
5
|
* app.register(BillingServiceProvider)
|
package/dist/billing/provider.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Keel Billing —
|
|
2
|
+
* Keel Billing — subscription billing covering Stripe and Paddle, shipped as a
|
|
3
3
|
* Keel package. One line in `bootstrap/providers.ts` turns it on:
|
|
4
4
|
*
|
|
5
5
|
* app.register(BillingServiceProvider)
|
|
@@ -29,7 +29,7 @@ export class BillingServiceProvider extends PackageProvider {
|
|
|
29
29
|
registerDefaultGateways(this.manager);
|
|
30
30
|
setBilling(this.manager);
|
|
31
31
|
this.app.instance(BillingManager, this.manager);
|
|
32
|
-
//
|
|
32
|
+
// Default billable table is `users`.
|
|
33
33
|
this.migrations([billingMigration("users")]);
|
|
34
34
|
this.publishes({ [join(here, "billing.config.stub")]: "config/billing.ts" }, "billing-config");
|
|
35
35
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One line of a subscription — a single price and its quantity. A single-price
|
|
3
3
|
* subscription has exactly one item; a multi-product subscription has several.
|
|
4
|
-
* Mirrors Cashier's `subscription_items` table.
|
|
5
4
|
*/
|
|
6
5
|
import { Model } from "../core/model.js";
|
|
7
6
|
import type { Casts } from "../core/casts.js";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One line of a subscription — a single price and its quantity. A single-price
|
|
3
3
|
* subscription has exactly one item; a multi-product subscription has several.
|
|
4
|
-
* Mirrors Cashier's `subscription_items` table.
|
|
5
4
|
*/
|
|
6
5
|
import { Model } from "../core/model.js";
|
|
7
6
|
export class SubscriptionItem extends Model {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Request decorators — attach named, computed values to the current request,
|
|
3
|
-
* resolved lazily and memoized for the life of that request.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* requests and no V8 shape deopt to design around).
|
|
3
|
+
* resolved lazily and memoized for the life of that request. You register a
|
|
4
|
+
* resolver once, and Keel computes it on first access and caches it per request
|
|
5
|
+
* (keyed off the Hono context via a WeakMap, so there's no shared-state leak
|
|
6
|
+
* between requests and no V8 shape deopt to design around).
|
|
8
7
|
*
|
|
9
8
|
* decorateRequest("user", async (c) => findUser(c.req.header("authorization")));
|
|
10
9
|
*
|
|
@@ -18,8 +17,7 @@ import type { Context } from "hono";
|
|
|
18
17
|
/** Resolves a decorator's value from the current request context. */
|
|
19
18
|
export type RequestResolver<T = unknown> = (c: Context) => T | Promise<T>;
|
|
20
19
|
/**
|
|
21
|
-
* Register a request decorator. Throws if `name` is already registered
|
|
22
|
-
* collision guard, like Fastify's.
|
|
20
|
+
* Register a request decorator. Throws if `name` is already registered.
|
|
23
21
|
*/
|
|
24
22
|
export declare function decorateRequest<T>(name: string, resolver: RequestResolver<T>): void;
|
|
25
23
|
/** Whether a request decorator has been registered. */
|
package/dist/core/decorators.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Request decorators — attach named, computed values to the current request,
|
|
3
|
-
* resolved lazily and memoized for the life of that request.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* requests and no V8 shape deopt to design around).
|
|
3
|
+
* resolved lazily and memoized for the life of that request. You register a
|
|
4
|
+
* resolver once, and Keel computes it on first access and caches it per request
|
|
5
|
+
* (keyed off the Hono context via a WeakMap, so there's no shared-state leak
|
|
6
|
+
* between requests and no V8 shape deopt to design around).
|
|
8
7
|
*
|
|
9
8
|
* decorateRequest("user", async (c) => findUser(c.req.header("authorization")));
|
|
10
9
|
*
|
|
@@ -27,8 +26,7 @@ function bag(c) {
|
|
|
27
26
|
return b;
|
|
28
27
|
}
|
|
29
28
|
/**
|
|
30
|
-
* Register a request decorator. Throws if `name` is already registered
|
|
31
|
-
* collision guard, like Fastify's.
|
|
29
|
+
* Register a request decorator. Throws if `name` is already registered.
|
|
32
30
|
*/
|
|
33
31
|
export function decorateRequest(name, resolver) {
|
|
34
32
|
if (resolvers.has(name)) {
|
|
@@ -92,9 +92,8 @@ export declare class ServiceUnavailableException extends HttpException {
|
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
94
|
* Mint a reusable, coded `HttpException` subclass — the ergonomic way to define
|
|
95
|
-
* app-specific errors with a stable, machine-readable `code`.
|
|
96
|
-
*
|
|
97
|
-
* from the constructor arguments.
|
|
95
|
+
* app-specific errors with a stable, machine-readable `code`. The `message` may
|
|
96
|
+
* carry `%s` placeholders, filled in order from the constructor arguments.
|
|
98
97
|
*
|
|
99
98
|
* const InsufficientFunds = createError("E_FUNDS", "Balance too low: need %s", 402);
|
|
100
99
|
* throw new InsufficientFunds("$40");
|
package/dist/core/exceptions.js
CHANGED
|
@@ -196,9 +196,8 @@ export class ServiceUnavailableException extends HttpException {
|
|
|
196
196
|
}
|
|
197
197
|
/**
|
|
198
198
|
* Mint a reusable, coded `HttpException` subclass — the ergonomic way to define
|
|
199
|
-
* app-specific errors with a stable, machine-readable `code`.
|
|
200
|
-
*
|
|
201
|
-
* from the constructor arguments.
|
|
199
|
+
* app-specific errors with a stable, machine-readable `code`. The `message` may
|
|
200
|
+
* carry `%s` placeholders, filled in order from the constructor arguments.
|
|
202
201
|
*
|
|
203
202
|
* const InsufficientFunds = createError("E_FUNDS", "Balance too low: need %s", 402);
|
|
204
203
|
* throw new InsufficientFunds("$40");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A model-aware query builder — what `Model.query()` sugar and eager loading are
|
|
3
3
|
* built on. It wraps the plain `QueryBuilder`, hydrates rows into models (firing
|
|
4
|
-
* `retrieved`), and adds the relationship-aware operations
|
|
4
|
+
* `retrieved`), and adds the relationship-aware operations a query builder lacks and a raw
|
|
5
5
|
* builder can't: `with()` (nested eager loading), `withCount()`, and existence
|
|
6
6
|
* filters `has()` / `whereHas()` / `doesntHave()`.
|
|
7
7
|
*
|
package/dist/core/model-query.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A model-aware query builder — what `Model.query()` sugar and eager loading are
|
|
3
3
|
* built on. It wraps the plain `QueryBuilder`, hydrates rows into models (firing
|
|
4
|
-
* `retrieved`), and adds the relationship-aware operations
|
|
4
|
+
* `retrieved`), and adds the relationship-aware operations a query builder lacks and a raw
|
|
5
5
|
* builder can't: `with()` (nested eager loading), `withCount()`, and existence
|
|
6
6
|
* filters `has()` / `whereHas()` / `doesntHave()`.
|
|
7
7
|
*
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-request logging. `requestLogger()` middleware binds a child logger to each
|
|
3
3
|
* request with a generated `reqId`, so every log line within that request
|
|
4
|
-
* correlates —
|
|
5
|
-
*
|
|
4
|
+
* correlates — built on Keel's `Logger.child()`. It can also log the request
|
|
5
|
+
* start and completion (method, path, status, duration).
|
|
6
6
|
*
|
|
7
7
|
* kernel.use(requestLogger()); // in app/Http/Kernel.ts
|
|
8
8
|
* requestLog().info("charging card"); // anywhere in the request → carries reqId
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-request logging. `requestLogger()` middleware binds a child logger to each
|
|
3
3
|
* request with a generated `reqId`, so every log line within that request
|
|
4
|
-
* correlates —
|
|
5
|
-
*
|
|
4
|
+
* correlates — built on Keel's `Logger.child()`. It can also log the request
|
|
5
|
+
* start and completion (method, path, status, duration).
|
|
6
6
|
*
|
|
7
7
|
* kernel.use(requestLogger()); // in app/Http/Kernel.ts
|
|
8
8
|
* requestLog().info("charging card"); // anywhere in the request → carries reqId
|
package/dist/core/request.js
CHANGED
|
@@ -112,7 +112,7 @@ export function html(body, status) {
|
|
|
112
112
|
}
|
|
113
113
|
export function redirect(location, status) {
|
|
114
114
|
const c = maybeCtx();
|
|
115
|
-
//
|
|
115
|
+
// `redirect("back")`: bounce to the Referer, or "/" if there isn't one.
|
|
116
116
|
if (location === "back")
|
|
117
117
|
location = c?.req.header("referer") ?? "/";
|
|
118
118
|
return c
|
package/dist/core/testing.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A test client for Keel apps — inject requests without a live server and assert
|
|
3
|
-
* on the response
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* on the response. Wraps the app's Hono instance (which already does fetch-style
|
|
4
|
+
* request injection), adding verb helpers with JSON bodies and fluent response
|
|
5
|
+
* assertions.
|
|
6
6
|
*
|
|
7
7
|
* const client = await testClient(app);
|
|
8
8
|
* const res = await client.post("/users", { email: "a@b.com" });
|
package/dist/core/testing.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A test client for Keel apps — inject requests without a live server and assert
|
|
3
|
-
* on the response
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* on the response. Wraps the app's Hono instance (which already does fetch-style
|
|
4
|
+
* request injection), adding verb helpers with JSON bodies and fluent response
|
|
5
|
+
* assertions.
|
|
6
6
|
*
|
|
7
7
|
* const client = await testClient(app);
|
|
8
8
|
* const res = await client.post("/users", { email: "a@b.com" });
|
package/docs/ai-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shaferllc/keel",
|
|
3
|
-
"version": "0.83.
|
|
3
|
+
"version": "0.83.2",
|
|
4
4
|
"description": "The house framework for Node.js — a service container, providers, routing, JSX views, and a code-generating console.",
|
|
5
5
|
"repo": "https://github.com/shaferllc/keel",
|
|
6
6
|
"generated": "run `npm run build:ai` to regenerate",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
{
|
|
51
51
|
"slug": "billing",
|
|
52
52
|
"title": "Billing",
|
|
53
|
-
"summary": "Keel Billing is a subscription-billing layer
|
|
53
|
+
"summary": "Keel Billing is a subscription-billing layer for charging customers, managing subscriptions, and reconciling gateway state through webhooks.",
|
|
54
54
|
"path": "docs/billing.md",
|
|
55
|
-
"example":
|
|
55
|
+
"example": "docs/examples/billing.ts"
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"slug": "broadcasting",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"title": "CORS",
|
|
116
116
|
"summary": "Cross-Origin Resource Sharing lets browsers on other origins call your API.",
|
|
117
117
|
"path": "docs/cors.md",
|
|
118
|
-
"example":
|
|
118
|
+
"example": "docs/examples/cors.ts"
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
"slug": "database",
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
"title": "Built on Hono",
|
|
193
193
|
"summary": "Keel's HTTP layer is Hono — an ultrafast, web-standard router that runs on Node, Cloudflare Workers, Deno, Bun, and more.",
|
|
194
194
|
"path": "docs/hono.md",
|
|
195
|
-
"example":
|
|
195
|
+
"example": "docs/examples/hono.ts"
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
"slug": "hooks",
|
|
@@ -269,21 +269,21 @@
|
|
|
269
269
|
"title": "OpenAPI",
|
|
270
270
|
"summary": "Keel OpenAPI generates an OpenAPI 3 spec from your routes and serves Swagger UI to explore it.",
|
|
271
271
|
"path": "docs/openapi.md",
|
|
272
|
-
"example":
|
|
272
|
+
"example": "docs/examples/openapi.ts"
|
|
273
273
|
},
|
|
274
274
|
{
|
|
275
275
|
"slug": "orm",
|
|
276
276
|
"title": "ORM",
|
|
277
277
|
"summary": "Keel's ORM is a compact active record over the query builder: a model is a class pointed at a table, and its rows come back as typed objects with methods.",
|
|
278
278
|
"path": "docs/orm.md",
|
|
279
|
-
"example":
|
|
279
|
+
"example": "docs/examples/orm.ts"
|
|
280
280
|
},
|
|
281
281
|
{
|
|
282
282
|
"slug": "packages",
|
|
283
283
|
"title": "Packages",
|
|
284
284
|
"summary": "A package is a redistributable slice of a Keel app — routes, a UI, config, migrations, console commands — that installs with a single app.register(...).",
|
|
285
285
|
"path": "docs/packages.md",
|
|
286
|
-
"example":
|
|
286
|
+
"example": "docs/examples/packages.ts"
|
|
287
287
|
},
|
|
288
288
|
{
|
|
289
289
|
"slug": "pages",
|
|
@@ -304,7 +304,7 @@
|
|
|
304
304
|
"title": "Query Builder",
|
|
305
305
|
"summary": "Keel's driver-agnostic query builder — build and run SQL by chaining methods off db(table).",
|
|
306
306
|
"path": "docs/query-builder.md",
|
|
307
|
-
"example":
|
|
307
|
+
"example": "docs/examples/query-builder.ts"
|
|
308
308
|
},
|
|
309
309
|
{
|
|
310
310
|
"slug": "queues",
|
|
@@ -353,7 +353,7 @@
|
|
|
353
353
|
"title": "Securing SSR apps",
|
|
354
354
|
"summary": "Two middlewares harden server-rendered apps: securityHeaders() sets the defensive HTTP headers browsers act on, and csrf() blocks cross-site form submissions.",
|
|
355
355
|
"path": "docs/security.md",
|
|
356
|
-
"example":
|
|
356
|
+
"example": "docs/examples/security.ts"
|
|
357
357
|
},
|
|
358
358
|
{
|
|
359
359
|
"slug": "sessions",
|
|
@@ -367,14 +367,14 @@
|
|
|
367
367
|
"title": "Social authentication",
|
|
368
368
|
"summary": "\"Sign in with GitHub / Google / Discord\" — OAuth 2.0, without an SDK.",
|
|
369
369
|
"path": "docs/social-auth.md",
|
|
370
|
-
"example":
|
|
370
|
+
"example": "docs/examples/social-auth.ts"
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"slug": "starter-kits",
|
|
374
374
|
"title": "Starter kits",
|
|
375
375
|
"summary": "",
|
|
376
376
|
"path": "docs/starter-kits.md",
|
|
377
|
-
"example":
|
|
377
|
+
"example": "docs/examples/starter-kits.ts"
|
|
378
378
|
},
|
|
379
379
|
{
|
|
380
380
|
"slug": "static-files",
|
|
@@ -456,9 +456,9 @@
|
|
|
456
456
|
{
|
|
457
457
|
"slug": "watch",
|
|
458
458
|
"title": "Watch",
|
|
459
|
-
"summary": "Keel Watch is a debug dashboard
|
|
459
|
+
"summary": "Keel Watch is a debug dashboard for Keel apps.",
|
|
460
460
|
"path": "docs/watch.md",
|
|
461
|
-
"example":
|
|
461
|
+
"example": "docs/examples/watch.ts"
|
|
462
462
|
}
|
|
463
463
|
],
|
|
464
464
|
"api": [
|
package/docs/billing.md
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# Billing
|
|
2
2
|
|
|
3
|
-
Keel Billing is a subscription-billing layer
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
as a Keel [package](./packages.md) and supports two gateways behind one API:
|
|
3
|
+
Keel Billing is a subscription-billing layer for charging customers, managing
|
|
4
|
+
subscriptions, and reconciling gateway state through webhooks. It ships as a
|
|
5
|
+
Keel [package](./packages.md) and supports two gateways behind one API:
|
|
7
6
|
**Stripe** and **Paddle**.
|
|
8
7
|
|
|
9
8
|
It attaches to a model with a mixin. Your `User` becomes billable, gains a
|
|
@@ -206,6 +205,46 @@ resolveBillableUsing(async (customerId) => {
|
|
|
206
205
|
});
|
|
207
206
|
```
|
|
208
207
|
|
|
208
|
+
## A complete flow
|
|
209
|
+
|
|
210
|
+
From "user signs up" to "they're subscribed", with the fake gateway for tests:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { Model } from "@shaferllc/keel/core";
|
|
214
|
+
import {
|
|
215
|
+
Billable,
|
|
216
|
+
BillingManager,
|
|
217
|
+
FakeGateway,
|
|
218
|
+
setBilling,
|
|
219
|
+
} from "@shaferllc/keel/billing";
|
|
220
|
+
|
|
221
|
+
class User extends Billable(Model) {
|
|
222
|
+
static table = "users";
|
|
223
|
+
declare email: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// In a test bootstrap:
|
|
227
|
+
const fake = new FakeGateway();
|
|
228
|
+
const manager = new BillingManager({
|
|
229
|
+
default: "fake",
|
|
230
|
+
currency: "usd",
|
|
231
|
+
billableModel: "User",
|
|
232
|
+
webhook: { path: "billing/webhook" },
|
|
233
|
+
gateways: { stripe: { key: "", webhookSecret: "" }, paddle: { key: "", webhookSecret: "" }, fake: {} },
|
|
234
|
+
});
|
|
235
|
+
manager.register("fake", () => fake);
|
|
236
|
+
setBilling(manager);
|
|
237
|
+
|
|
238
|
+
const user = await User.create({ email: "ada@example.com" });
|
|
239
|
+
await user.newSubscription("default", "price_pro").trialDays(14).create();
|
|
240
|
+
|
|
241
|
+
await user.subscribed(); // true
|
|
242
|
+
fake.calls.some((c) => c.method === "createSubscription"); // true
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
In production you skip the fake manager — `BillingServiceProvider` wires the
|
|
246
|
+
real gateway from `config/billing.ts` and `.env`.
|
|
247
|
+
|
|
209
248
|
## Gateway differences
|
|
210
249
|
|
|
211
250
|
| Concern | Stripe | Paddle |
|
|
@@ -221,8 +260,8 @@ resolveBillableUsing(async (customerId) => {
|
|
|
221
260
|
The migration is gateway-neutral: `subscriptions` (with `gateway`,
|
|
222
261
|
`provider_id`, `provider_status`, `provider_price`, trial/grace timestamps),
|
|
223
262
|
`subscription_items`, and columns on `users` (`billing_gateway`,
|
|
224
|
-
`billing_customer_id`, `pm_type`, `pm_last_four`, `trial_ends_at`).
|
|
225
|
-
targets the standard `users` billable table.
|
|
263
|
+
`billing_customer_id`, `pm_type`, `pm_last_four`, `trial_ends_at`). The default
|
|
264
|
+
migration targets the standard `users` billable table.
|
|
226
265
|
|
|
227
266
|
## Testing
|
|
228
267
|
|