@shaferllc/keel 0.83.0 → 0.83.1
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 +2 -2
- package/docs/billing.md +5 -6
- package/docs/changelog.md +106 -21
- package/docs/decorators.md +2 -6
- package/docs/errors.md +1 -1
- package/docs/hooks.md +1 -3
- package/docs/logger.md +2 -2
- package/docs/packages.md +3 -3
- package/docs/providers.md +6 -6
- package/docs/request-response.md +2 -2
- package/docs/testing.md +1 -2
- package/docs/validation.md +1 -2
- package/llms-full.txt +24 -33
- package/llms.txt +1 -1
- 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.1",
|
|
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,7 +50,7 @@
|
|
|
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
55
|
"example": null
|
|
56
56
|
},
|
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
|
|
@@ -221,8 +220,8 @@ resolveBillableUsing(async (customerId) => {
|
|
|
221
220
|
The migration is gateway-neutral: `subscriptions` (with `gateway`,
|
|
222
221
|
`provider_id`, `provider_status`, `provider_price`, trial/grace timestamps),
|
|
223
222
|
`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.
|
|
223
|
+
`billing_customer_id`, `pm_type`, `pm_last_four`, `trial_ends_at`). The default
|
|
224
|
+
migration targets the standard `users` billable table.
|
|
226
225
|
|
|
227
226
|
## Testing
|
|
228
227
|
|
package/docs/changelog.md
CHANGED
|
@@ -4,6 +4,92 @@ All notable changes to Keel are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims to
|
|
5
5
|
adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.83.0] — 2026-07-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Starter kits, and a generator that can't fall behind.** Four curated
|
|
12
|
+
applications — `minimal`, `api`, `app` (views, sessions, register/login, password
|
|
13
|
+
reset, two-factor), and `saas` (teams, roles, invitations, billing,
|
|
14
|
+
multi-tenancy):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm create keeljs@latest my-app -- --preset saas
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The templates ship **inside this package**, so the version a kit is generated from
|
|
21
|
+
is, by construction, the version it was written for. The old standalone starter
|
|
22
|
+
drifted to five releases behind and nothing noticed; CI now generates all four kits
|
|
23
|
+
on every push and typechecks, migrates, boots, serves a request, bundles the
|
|
24
|
+
Worker, and runs their tests.
|
|
25
|
+
|
|
26
|
+
- **Teams (`@shaferllc/keel/teams`)** — multi-tenancy, membership, roles, and
|
|
27
|
+
invitations. `TenantModel` makes isolation the default rather than a habit: reads
|
|
28
|
+
are constrained by an inherited global scope (so even `find(id)` returns `null` for
|
|
29
|
+
another team's row — not a filter you can forget), and writes are stamped with the
|
|
30
|
+
current team, so a row can't be born ownerless.
|
|
31
|
+
|
|
32
|
+
Outside a team context a tenant query **throws**. A job, console command, or
|
|
33
|
+
webhook has no request and therefore no team; returning everything is how one
|
|
34
|
+
customer's data reaches another, and `team_id = NULL` means jobs quietly do
|
|
35
|
+
nothing. So work says which team it is for — `runForTeam(team, fn)` — or says out
|
|
36
|
+
loud that it spans all of them — `withoutTenant(fn)`. Both are greppable at audit
|
|
37
|
+
time.
|
|
38
|
+
|
|
39
|
+
- **Accounts (`@shaferllc/keel/accounts`)** — password reset, email verification, and
|
|
40
|
+
two-factor, mounted with one provider. A correct password on a 2FA account yields a
|
|
41
|
+
short-lived, single-purpose **challenge, not a session**, so there is no
|
|
42
|
+
half-authenticated state for a route to forget to check. TOTP is RFC 6238, verified
|
|
43
|
+
against the RFC's published vectors, WebCrypto-only, and therefore edge-safe. No
|
|
44
|
+
tokens table: reset links carry their own purpose and expiry and are bound to a
|
|
45
|
+
fingerprint of the current password hash, so spending one kills it.
|
|
46
|
+
|
|
47
|
+
- **D1 over HTTP (`@shaferllc/keel/db/d1-http`).** The D1 binding only exists inside a
|
|
48
|
+
Worker, so `keel migrate` had nowhere to point and you could not create your
|
|
49
|
+
tables. The same `Connection` over D1's HTTP API closes that: migrations run from a
|
|
50
|
+
laptop and from CI. It treats an error in the response *body* as an error even when
|
|
51
|
+
the status is 200 — which is how Cloudflare often reports them, and trusting the
|
|
52
|
+
status would let a failed migration look like it succeeded.
|
|
53
|
+
|
|
54
|
+
- **`Model.withoutGlobalScope(...)` / `withoutGlobalScopes()`** — escaping a scope
|
|
55
|
+
should be typed out and greppable, never something you arrive at by forgetting a
|
|
56
|
+
`where`.
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- **Global scopes and model hooks now inherit.** Both were keyed by the exact class,
|
|
61
|
+
so a scope or a `creating` hook declared on a *base* class was silently ignored by
|
|
62
|
+
every subclass. The models guide advertises global scopes as "the base for
|
|
63
|
+
multi-tenancy", and that was precisely the case that didn't work: the scope did
|
|
64
|
+
nothing, the query returned every tenant's rows, and nothing complained. It failed
|
|
65
|
+
**open**. Both now walk the prototype chain, and a scope is passed the model it is
|
|
66
|
+
scoping so a base class can read each subclass's own configuration.
|
|
67
|
+
|
|
68
|
+
- **The official `@libsql/client` no longer needs a cast.** Under
|
|
69
|
+
`strictFunctionTypes` the narrower parameter types made the real `Client`
|
|
70
|
+
unassignable to `LibSqlLike`, so wiring libSQL the obvious way required
|
|
71
|
+
`client as unknown as LibSqlLike` — a cast even Keel's own tests carried.
|
|
72
|
+
|
|
73
|
+
- **`createTeam()` could not give two people with the same name a team.** It slugged
|
|
74
|
+
the name into a `UNIQUE` column, so the second "Ada" to sign up got a 500. Looking
|
|
75
|
+
for a free slug first is check-then-act and loses the race anyway; the unique index
|
|
76
|
+
is the only real arbiter, so it now arbitrates and the insert retries.
|
|
77
|
+
|
|
78
|
+
## [0.82.0] — 2026-07-12
|
|
79
|
+
|
|
80
|
+
### Added
|
|
81
|
+
|
|
82
|
+
- **Query builder: the methods people actually reach for.** `join`/`leftJoin`,
|
|
83
|
+
`groupBy`/`having`/`distinct`, `whereColumn`/`whereRaw`/`orderByRaw`, `when`,
|
|
84
|
+
`increment`/`decrement`, `upsert`/`insertOrIgnore`, and `chunk`, with docs.
|
|
85
|
+
|
|
86
|
+
## [0.81.2] — 2026-07-12
|
|
87
|
+
|
|
88
|
+
### Documentation
|
|
89
|
+
|
|
90
|
+
- **The changelog is published to the docs site** (`docs/changelog.md`), so releases
|
|
91
|
+
are readable at keeljs.com rather than only in the repository.
|
|
92
|
+
|
|
7
93
|
## [0.81.1] — 2026-07-12
|
|
8
94
|
|
|
9
95
|
### Documentation
|
|
@@ -25,8 +111,8 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
25
111
|
|
|
26
112
|
### Added
|
|
27
113
|
|
|
28
|
-
- **ORM:
|
|
29
|
-
|
|
114
|
+
- **ORM: the features people reach for.** The active-record `Model` grows the
|
|
115
|
+
surface a real ORM needs, all backward-compatible and still on the
|
|
30
116
|
driver-agnostic query builder (no JOINs, edge-safe):
|
|
31
117
|
|
|
32
118
|
- **Lifecycle events & observers** — `creating`/`created`, `updating`/
|
|
@@ -91,8 +177,8 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
91
177
|
|
|
92
178
|
### Added
|
|
93
179
|
|
|
94
|
-
- **Billing** — a new `@shaferllc/keel/billing` package: a
|
|
95
|
-
|
|
180
|
+
- **Billing** — a new `@shaferllc/keel/billing` package: a subscription
|
|
181
|
+
layer with **one gateway-neutral API over Stripe and Paddle**
|
|
96
182
|
(switching gateways is a config change). `class User extends Billable(Model)`
|
|
97
183
|
gives a gateway customer, subscriptions (create/swap/quantity/trials/cancel/
|
|
98
184
|
resume + status checks), single charges + refunds, invoices, hosted checkout,
|
|
@@ -286,7 +372,7 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
286
372
|
|
|
287
373
|
### Changed
|
|
288
374
|
|
|
289
|
-
- **Removed
|
|
375
|
+
- **Removed comparisons to other frameworks** from the API-resources guide and source
|
|
290
376
|
comments. Keel isn't that, and the docs shouldn't read as a comparison to another
|
|
291
377
|
framework. The surrounding sentences are rewritten so they still say what the
|
|
292
378
|
feature does rather than leaving a hole. No behavior change.
|
|
@@ -960,7 +1046,7 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
960
1046
|
connection. They honor `X-Forwarded-Proto` / `X-Forwarded-Host` over the raw
|
|
961
1047
|
URL, so an app behind a TLS-terminating proxy or load balancer sees the
|
|
962
1048
|
client's real scheme and host — use `origin` to build absolute links and
|
|
963
|
-
`secure` to gate insecure requests.
|
|
1049
|
+
`secure` to gate insecure requests.
|
|
964
1050
|
- **`response.back(fallback?)` and `redirect("back")`.** Redirect to the
|
|
965
1051
|
request's `Referer`, falling back to `fallback` (default `"/"`) when it's
|
|
966
1052
|
absent — the "return where you came from" shortcut for post-form flows.
|
|
@@ -1189,7 +1275,7 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1189
1275
|
- **Route config / metadata.** Attach arbitrary data to a route or group with
|
|
1190
1276
|
`.config({ … })` and read it in the handler or route middleware via
|
|
1191
1277
|
`request.route.config` — per-route flags like an auth scope, rate tier, or
|
|
1192
|
-
layout choice
|
|
1278
|
+
layout choice. Group config merges into every route,
|
|
1193
1279
|
with a route's own keys winning. The matched-route context is now set *before*
|
|
1194
1280
|
a route's middleware, so route/group middleware can branch on `request.route`.
|
|
1195
1281
|
See [docs/routing.md](./docs/routing.md#route-config).
|
|
@@ -1236,7 +1322,7 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1236
1322
|
- **Response header helpers.** The `response` accessor gains `headers({...})` (set
|
|
1237
1323
|
several at once), `getHeader(name)`, and `hasHeader(name)` — so middleware can
|
|
1238
1324
|
inspect and conditionally set what a handler already put on the response (e.g.
|
|
1239
|
-
a default `cache-control`).
|
|
1325
|
+
a default `cache-control`).
|
|
1240
1326
|
See [docs/request-response.md](./docs/request-response.md).
|
|
1241
1327
|
- **Design principles** documented in
|
|
1242
1328
|
[docs/architecture.md](./docs/architecture.md#design-principles) — edge-safe /
|
|
@@ -1253,9 +1339,9 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1253
1339
|
options at registration: `app.register(RateLimitProvider, { max: 100 })`, typed
|
|
1254
1340
|
via `ServiceProvider<{ max: number }>` and read as `this.options`. The same
|
|
1255
1341
|
provider class can register more than once with different options, so a provider
|
|
1256
|
-
is now genuinely reusable
|
|
1257
|
-
|
|
1258
|
-
|
|
1342
|
+
is now genuinely reusable. Backward compatible — options default to `{}`.
|
|
1343
|
+
(Keel providers stay un-encapsulated by design; per-request scoping is
|
|
1344
|
+
[middleware](./docs/middleware.md).) See
|
|
1259
1345
|
[docs/providers.md](./docs/providers.md#providers-are-keels-plugin-system).
|
|
1260
1346
|
|
|
1261
1347
|
[0.50.0]: https://github.com/shaferllc/keel/releases/tag/v0.50.0
|
|
@@ -1350,8 +1436,8 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1350
1436
|
a bad request with a `422` `ValidationException` (errors from every part
|
|
1351
1437
|
aggregated, keyed `body.field` / `query.field` / `params.field`) so the handler
|
|
1352
1438
|
only ever sees valid input. `validated(part)` returns the parsed, typed value.
|
|
1353
|
-
|
|
1354
|
-
|
|
1439
|
+
Built on the same schema-agnostic `validate()` engine (bring your own
|
|
1440
|
+
Zod-style schema). See
|
|
1355
1441
|
[docs/validation.md](./docs/validation.md#declarative-validation-before-the-handler).
|
|
1356
1442
|
|
|
1357
1443
|
[0.44.0]: https://github.com/shaferllc/keel/releases/tag/v0.44.0
|
|
@@ -1362,7 +1448,7 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1362
1448
|
|
|
1363
1449
|
- **Per-request logging.** `requestLogger()` middleware binds a child logger with
|
|
1364
1450
|
a generated `reqId` to each request, so every log line within a request
|
|
1365
|
-
correlates
|
|
1451
|
+
correlates. It logs request start/completion
|
|
1366
1452
|
(method, path, status, ms) by default; options for `genReqId`, reusing an
|
|
1367
1453
|
incoming `idHeader` (distributed tracing), and disabling the auto lines.
|
|
1368
1454
|
`requestLog()` reaches the current request's logger anywhere (falls back to the
|
|
@@ -1400,8 +1486,8 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1400
1486
|
through the default path (with `code` in the JSON body) and passes
|
|
1401
1487
|
`instanceof HttpException`. The built-in exceptions now carry stable codes too
|
|
1402
1488
|
(`E_NOT_FOUND`, `E_UNAUTHORIZED`, `E_FORBIDDEN`, `E_VALIDATION`), so `code`
|
|
1403
|
-
surfaces without any work.
|
|
1404
|
-
|
|
1489
|
+
surfaces without any work. Also documented: serving over **HTTP/2** needs no
|
|
1490
|
+
framework code — it's a transport
|
|
1405
1491
|
concern handled by the edge platform, a reverse proxy, or a `@hono/node-server`
|
|
1406
1492
|
`node:http2` option. See [docs/errors.md](./docs/errors.md#coded-errors-with-createerror)
|
|
1407
1493
|
and [docs/hono.md](./docs/hono.md#serving-over-http2).
|
|
@@ -1435,8 +1521,8 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1435
1521
|
- **Raw request-body accessors.** `request.text()`, `request.arrayBuffer()`, and
|
|
1436
1522
|
`request.blob()` read the body for content types `json()` / `all()` don't
|
|
1437
1523
|
handle — XML, CSV, protobuf, msgpack, or any custom format — which you then
|
|
1438
|
-
parse yourself. Keel keeps body parsing explicit (no
|
|
1439
|
-
|
|
1524
|
+
parse yourself. Keel keeps body parsing explicit (no content-type parser
|
|
1525
|
+
registry): you call the accessor you want. See
|
|
1440
1526
|
[docs/request-response.md](./docs/request-response.md#other-content-types).
|
|
1441
1527
|
|
|
1442
1528
|
[0.40.1]: https://github.com/shaferllc/keel/releases/tag/v0.40.1
|
|
@@ -1450,9 +1536,8 @@ adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1450
1536
|
life of the request. `decorateRequest(name, resolver)` registers a resolver
|
|
1451
1537
|
(sync or async), `decorated(name)` reads it (computed once, then cached),
|
|
1452
1538
|
`setRequestValue(name, value)` sets it imperatively (e.g. from middleware), and
|
|
1453
|
-
`hasRequestDecorator(name)` checks.
|
|
1454
|
-
|
|
1455
|
-
is keyed off the context via a WeakMap, so nothing leaks between requests.
|
|
1539
|
+
`hasRequestDecorator(name)` checks. The per-request memo is keyed off the
|
|
1540
|
+
context via a WeakMap, so nothing leaks between requests.
|
|
1456
1541
|
(Decorating the *app* is already the container's job.) See
|
|
1457
1542
|
[docs/decorators.md](./docs/decorators.md).
|
|
1458
1543
|
|
package/docs/decorators.md
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Attach named, computed values to the current request — `request.user`,
|
|
4
4
|
`request.tenant`, `request.locale` — resolved **lazily** and **memoized for the
|
|
5
|
-
life of the request**.
|
|
6
|
-
but without the footguns: you register a resolver once, and Keel computes it on
|
|
5
|
+
life of the request**. You register a resolver once, and Keel computes it on
|
|
7
6
|
first access and caches it per request. No null-placeholder declaration, no
|
|
8
|
-
|
|
7
|
+
shared-state leak between requests.
|
|
9
8
|
|
|
10
9
|
> Decorating the **application** is already the [service container's](./container.md)
|
|
11
10
|
> job — `bind` / `singleton` / `instance` / `make`, with `bound()` as
|
|
@@ -53,9 +52,6 @@ setRequestValue("user", theAuthenticatedUser);
|
|
|
53
52
|
const user = await decorated("user"); // returns the value set above, no re-lookup
|
|
54
53
|
```
|
|
55
54
|
|
|
56
|
-
This is the clean version of Fastify's "declare a placeholder, set it in an
|
|
57
|
-
`onRequest` hook" pattern.
|
|
58
|
-
|
|
59
55
|
## Why lazy + memoized
|
|
60
56
|
|
|
61
57
|
Resolving the current user (or tenant, or locale, or a feature-flag set) is the
|
package/docs/errors.md
CHANGED
|
@@ -185,7 +185,7 @@ subclasses don't define either — they render through the default path.
|
|
|
185
185
|
|
|
186
186
|
When all you want is a coded error class — a stable `code`, a message, a status —
|
|
187
187
|
skip the boilerplate and mint one with `createError`. It's the ergonomic shortcut
|
|
188
|
-
for the common case
|
|
188
|
+
for the common case:
|
|
189
189
|
|
|
190
190
|
```ts
|
|
191
191
|
import { createError } from "@shaferllc/keel/core";
|
package/docs/hooks.md
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# Lifecycle Hooks
|
|
2
2
|
|
|
3
3
|
Tap into the **application lifecycle** — run code once the app is ready, clean up
|
|
4
|
-
on shutdown, and observe route registration.
|
|
5
|
-
[Fastify's hooks](https://fastify.dev/docs/latest/Reference/Hooks/), scoped to the
|
|
6
|
-
parts Keel doesn't already cover.
|
|
4
|
+
on shutdown, and observe route registration.
|
|
7
5
|
|
|
8
6
|
> **Request-lifecycle hooks** (before/after a request, on error) are
|
|
9
7
|
> [middleware](./middleware.md) in Keel — `HttpKernel.use()`, route/group
|
package/docs/logger.md
CHANGED
|
@@ -156,8 +156,8 @@ With no options it defaults to `level: "info"`, `pretty: false`, and no bindings
|
|
|
156
156
|
|
|
157
157
|
`requestLogger()` is a built-in middleware that binds a **child logger with a
|
|
158
158
|
generated `reqId` to each request**, so every log line within a request
|
|
159
|
-
correlates
|
|
160
|
-
|
|
159
|
+
correlates. Install it in your HTTP kernel, then reach the request's logger
|
|
160
|
+
anywhere with `requestLog()`:
|
|
161
161
|
|
|
162
162
|
```ts
|
|
163
163
|
import { requestLogger, requestLog } from "@shaferllc/keel/core";
|
package/docs/packages.md
CHANGED
|
@@ -7,9 +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. [Billing](./billing.md) (
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
reference implementation of everything below. [Billing](./billing.md) (Stripe and
|
|
11
|
+
Paddle subscriptions) is another, and shows a package contributing models, a
|
|
12
|
+
schema migration, gateway drivers, and verified webhook routes.
|
|
13
13
|
|
|
14
14
|
## The shape of a package
|
|
15
15
|
|
package/docs/providers.md
CHANGED
|
@@ -111,12 +111,12 @@ app.register(RateLimitProvider, { max: 100 }); // parameterized, like a plugin
|
|
|
111
111
|
The same provider class can be registered more than once with different options.
|
|
112
112
|
Without options, `this.options` is an empty object.
|
|
113
113
|
|
|
114
|
-
>
|
|
115
|
-
>
|
|
116
|
-
>
|
|
117
|
-
>
|
|
118
|
-
>
|
|
119
|
-
>
|
|
114
|
+
> Keel providers are **not encapsulated** — bindings, decorators, and routes are
|
|
115
|
+
> registered into the one global container. That's a deliberate simplification:
|
|
116
|
+
> there's a single, predictable scope, and no plugin-boundary rules to reason
|
|
117
|
+
> about. For per-request behavior in the HTTP pipeline (auth, logging, etc.),
|
|
118
|
+
> reach for [middleware](./middleware.md), which *is* scoped to the routes you
|
|
119
|
+
> attach it to.
|
|
120
120
|
|
|
121
121
|
## Generating a provider
|
|
122
122
|
|
package/docs/request-response.md
CHANGED
|
@@ -51,8 +51,8 @@ and host (handy for building absolute links or forcing HTTPS).
|
|
|
51
51
|
|
|
52
52
|
`all()` understands JSON and form bodies. For anything else — XML, CSV, a binary
|
|
53
53
|
payload, a custom format — read the raw body and parse it yourself. There's no
|
|
54
|
-
content-type parser registry to configure
|
|
55
|
-
|
|
54
|
+
content-type parser registry to configure: parsing is explicit, so you call the
|
|
55
|
+
accessor you want.
|
|
56
56
|
|
|
57
57
|
```ts
|
|
58
58
|
await request.text(); // the body as a string (XML, CSV, …)
|
package/docs/testing.md
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Test your app by **injecting requests** — no server, no port, no network — and
|
|
4
4
|
asserting on the response. `testClient()` wraps your app's Hono instance (which
|
|
5
|
-
already does fetch-style injection) with verb helpers and fluent assertions
|
|
6
|
-
way Fastify's `inject()` works.
|
|
5
|
+
already does fetch-style injection) with verb helpers and fluent assertions.
|
|
7
6
|
|
|
8
7
|
## The client
|
|
9
8
|
|
package/docs/validation.md
CHANGED
|
@@ -112,8 +112,7 @@ router.get("/posts/:id", [Posts, "show"]).middleware([
|
|
|
112
112
|
|
|
113
113
|
`validated(part)` returns the parsed, typed value for that part (defaults to
|
|
114
114
|
`"body"`). Coercion (`z.coerce.number()`) is the schema's job — it applies before
|
|
115
|
-
your handler sees the value.
|
|
116
|
-
route schemas, on top of the same `validate()` engine.
|
|
115
|
+
your handler sees the value. Built on the same `validate()` engine.
|
|
117
116
|
|
|
118
117
|
## Body parsing is JSON-only
|
|
119
118
|
|
package/llms-full.txt
CHANGED
|
@@ -1233,12 +1233,12 @@ app.register(RateLimitProvider, { max: 100 }); // parameterized, like a plugin
|
|
|
1233
1233
|
The same provider class can be registered more than once with different options.
|
|
1234
1234
|
Without options, `this.options` is an empty object.
|
|
1235
1235
|
|
|
1236
|
-
>
|
|
1237
|
-
>
|
|
1238
|
-
>
|
|
1239
|
-
>
|
|
1240
|
-
>
|
|
1241
|
-
>
|
|
1236
|
+
> Keel providers are **not encapsulated** — bindings, decorators, and routes are
|
|
1237
|
+
> registered into the one global container. That's a deliberate simplification:
|
|
1238
|
+
> there's a single, predictable scope, and no plugin-boundary rules to reason
|
|
1239
|
+
> about. For per-request behavior in the HTTP pipeline (auth, logging, etc.),
|
|
1240
|
+
> reach for [middleware](./middleware.md), which *is* scoped to the routes you
|
|
1241
|
+
> attach it to.
|
|
1242
1242
|
|
|
1243
1243
|
## Generating a provider
|
|
1244
1244
|
|
|
@@ -3102,8 +3102,8 @@ and host (handy for building absolute links or forcing HTTPS).
|
|
|
3102
3102
|
|
|
3103
3103
|
`all()` understands JSON and form bodies. For anything else — XML, CSV, a binary
|
|
3104
3104
|
payload, a custom format — read the raw body and parse it yourself. There's no
|
|
3105
|
-
content-type parser registry to configure
|
|
3106
|
-
|
|
3105
|
+
content-type parser registry to configure: parsing is explicit, so you call the
|
|
3106
|
+
accessor you want.
|
|
3107
3107
|
|
|
3108
3108
|
```ts
|
|
3109
3109
|
await request.text(); // the body as a string (XML, CSV, …)
|
|
@@ -5559,10 +5559,9 @@ all gates/policies/hooks (a test helper).
|
|
|
5559
5559
|
|
|
5560
5560
|
# Billing
|
|
5561
5561
|
|
|
5562
|
-
Keel Billing is a subscription-billing layer
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
as a Keel [package](./packages.md) and supports two gateways behind one API:
|
|
5562
|
+
Keel Billing is a subscription-billing layer for charging customers, managing
|
|
5563
|
+
subscriptions, and reconciling gateway state through webhooks. It ships as a
|
|
5564
|
+
Keel [package](./packages.md) and supports two gateways behind one API:
|
|
5566
5565
|
**Stripe** and **Paddle**.
|
|
5567
5566
|
|
|
5568
5567
|
It attaches to a model with a mixin. Your `User` becomes billable, gains a
|
|
@@ -5780,8 +5779,8 @@ resolveBillableUsing(async (customerId) => {
|
|
|
5780
5779
|
The migration is gateway-neutral: `subscriptions` (with `gateway`,
|
|
5781
5780
|
`provider_id`, `provider_status`, `provider_price`, trial/grace timestamps),
|
|
5782
5781
|
`subscription_items`, and columns on `users` (`billing_gateway`,
|
|
5783
|
-
`billing_customer_id`, `pm_type`, `pm_last_four`, `trial_ends_at`).
|
|
5784
|
-
targets the standard `users` billable table.
|
|
5782
|
+
`billing_customer_id`, `pm_type`, `pm_last_four`, `trial_ends_at`). The default
|
|
5783
|
+
migration targets the standard `users` billable table.
|
|
5785
5784
|
|
|
5786
5785
|
## Testing
|
|
5787
5786
|
|
|
@@ -8162,10 +8161,9 @@ arguments still halts the request and renders an empty page.
|
|
|
8162
8161
|
|
|
8163
8162
|
Attach named, computed values to the current request — `request.user`,
|
|
8164
8163
|
`request.tenant`, `request.locale` — resolved **lazily** and **memoized for the
|
|
8165
|
-
life of the request**.
|
|
8166
|
-
but without the footguns: you register a resolver once, and Keel computes it on
|
|
8164
|
+
life of the request**. You register a resolver once, and Keel computes it on
|
|
8167
8165
|
first access and caches it per request. No null-placeholder declaration, no
|
|
8168
|
-
|
|
8166
|
+
shared-state leak between requests.
|
|
8169
8167
|
|
|
8170
8168
|
> Decorating the **application** is already the [service container's](./container.md)
|
|
8171
8169
|
> job — `bind` / `singleton` / `instance` / `make`, with `bound()` as
|
|
@@ -8213,9 +8211,6 @@ setRequestValue("user", theAuthenticatedUser);
|
|
|
8213
8211
|
const user = await decorated("user"); // returns the value set above, no re-lookup
|
|
8214
8212
|
```
|
|
8215
8213
|
|
|
8216
|
-
This is the clean version of Fastify's "declare a placeholder, set it in an
|
|
8217
|
-
`onRequest` hook" pattern.
|
|
8218
|
-
|
|
8219
8214
|
## Why lazy + memoized
|
|
8220
8215
|
|
|
8221
8216
|
Resolving the current user (or tenant, or locale, or a feature-flag set) is the
|
|
@@ -8479,7 +8474,7 @@ subclasses don't define either — they render through the default path.
|
|
|
8479
8474
|
|
|
8480
8475
|
When all you want is a coded error class — a stable `code`, a message, a status —
|
|
8481
8476
|
skip the boilerplate and mint one with `createError`. It's the ergonomic shortcut
|
|
8482
|
-
for the common case
|
|
8477
|
+
for the common case:
|
|
8483
8478
|
|
|
8484
8479
|
```ts
|
|
8485
8480
|
import { createError } from "@shaferllc/keel/core";
|
|
@@ -10418,9 +10413,7 @@ implementing anything — the same `fetch` handler just gets served over it.
|
|
|
10418
10413
|
# Lifecycle Hooks
|
|
10419
10414
|
|
|
10420
10415
|
Tap into the **application lifecycle** — run code once the app is ready, clean up
|
|
10421
|
-
on shutdown, and observe route registration.
|
|
10422
|
-
[Fastify's hooks](https://fastify.dev/docs/latest/Reference/Hooks/), scoped to the
|
|
10423
|
-
parts Keel doesn't already cover.
|
|
10416
|
+
on shutdown, and observe route registration.
|
|
10424
10417
|
|
|
10425
10418
|
> **Request-lifecycle hooks** (before/after a request, on error) are
|
|
10426
10419
|
> [middleware](./middleware.md) in Keel — `HttpKernel.use()`, route/group
|
|
@@ -11585,8 +11578,8 @@ With no options it defaults to `level: "info"`, `pretty: false`, and no bindings
|
|
|
11585
11578
|
|
|
11586
11579
|
`requestLogger()` is a built-in middleware that binds a **child logger with a
|
|
11587
11580
|
generated `reqId` to each request**, so every log line within a request
|
|
11588
|
-
correlates
|
|
11589
|
-
|
|
11581
|
+
correlates. Install it in your HTTP kernel, then reach the request's logger
|
|
11582
|
+
anywhere with `requestLog()`:
|
|
11590
11583
|
|
|
11591
11584
|
```ts
|
|
11592
11585
|
import { requestLogger, requestLog } from "@shaferllc/keel/core";
|
|
@@ -14956,9 +14949,9 @@ adds the conventions a *shippable* package needs so it can carry its own schema
|
|
|
14956
14949
|
and assets instead of asking the app to wire them by hand.
|
|
14957
14950
|
|
|
14958
14951
|
[Keel Watch](./watch.md) — the debug dashboard — is a first-party package and the
|
|
14959
|
-
reference implementation of everything below. [Billing](./billing.md) (
|
|
14960
|
-
|
|
14961
|
-
|
|
14952
|
+
reference implementation of everything below. [Billing](./billing.md) (Stripe and
|
|
14953
|
+
Paddle subscriptions) is another, and shows a package contributing models, a
|
|
14954
|
+
schema migration, gateway drivers, and verified webhook routes.
|
|
14962
14955
|
|
|
14963
14956
|
## The shape of a package
|
|
14964
14957
|
|
|
@@ -19171,8 +19164,7 @@ construct it directly.
|
|
|
19171
19164
|
|
|
19172
19165
|
Test your app by **injecting requests** — no server, no port, no network — and
|
|
19173
19166
|
asserting on the response. `testClient()` wraps your app's Hono instance (which
|
|
19174
|
-
already does fetch-style injection) with verb helpers and fluent assertions
|
|
19175
|
-
way Fastify's `inject()` works.
|
|
19167
|
+
already does fetch-style injection) with verb helpers and fluent assertions.
|
|
19176
19168
|
|
|
19177
19169
|
## The client
|
|
19178
19170
|
|
|
@@ -20355,8 +20347,7 @@ router.get("/posts/:id", [Posts, "show"]).middleware([
|
|
|
20355
20347
|
|
|
20356
20348
|
`validated(part)` returns the parsed, typed value for that part (defaults to
|
|
20357
20349
|
`"body"`). Coercion (`z.coerce.number()`) is the schema's job — it applies before
|
|
20358
|
-
your handler sees the value.
|
|
20359
|
-
route schemas, on top of the same `validate()` engine.
|
|
20350
|
+
your handler sees the value. Built on the same `validate()` engine.
|
|
20360
20351
|
|
|
20361
20352
|
## Body parsing is JSON-only
|
|
20362
20353
|
|
package/llms.txt
CHANGED
|
@@ -15,7 +15,7 @@ Userland imports everything from `@shaferllc/keel/core`.
|
|
|
15
15
|
- [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.
|
|
16
16
|
- [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.
|
|
17
17
|
- [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.
|
|
18
|
-
- [Billing](https://github.com/shaferllc/keel/blob/main/docs/billing.md): Keel Billing is a subscription-billing layer
|
|
18
|
+
- [Billing](https://github.com/shaferllc/keel/blob/main/docs/billing.md): Keel Billing is a subscription-billing layer for charging customers, managing subscriptions, and reconciling gateway state through webhooks.
|
|
19
19
|
- [Broadcasting](https://github.com/shaferllc/keel/blob/main/docs/broadcasting.md): Push events to clients in real time over named channels.
|
|
20
20
|
- [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.
|
|
21
21
|
- [Cache](https://github.com/shaferllc/keel/blob/main/docs/cache.md): A small cache with TTLs and the remember pattern.
|
package/package.json
CHANGED