Skip to content

Commit 69ae136

Browse files
os-zhuangclaude
andauthored
docs: align hardening + driver docs with Hono-only adapters (#2392, 12.0) (#2393)
Follow-up to the adapter trim (#2391). Hand-written docs only (ADRs / audits / CHANGELOGs left as historical): - docs/HARDENING.md: Fastify recipe → Hono recipe (the @objectstack/fastify import was broken after removal); CSRF → hono/csrf; CORS → createHonoApp. - docs/design/driver-turso.md: drop plugin-msw row; "all adapters" → Hono. - packages/plugins/driver-memory/README.md: drop plugin-msw bullets. - packages/runtime/README.md: framework examples narrowed to Hono. - docs/launch-readiness.md: stale express/fastify residual note → Hono. Verified: no removed-package refs remain in current docs. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7087cfe commit 69ae136

6 files changed

Lines changed: 47 additions & 30 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/runtime": patch
3+
"@objectstack/driver-memory": patch
4+
---
5+
6+
docs: align hardening / driver docs with the Hono-only adapter surface (12.0)
7+
8+
Follow-up to the adapter trim (#2391): the hardening guide's rate-limit/CORS
9+
recipes are rewritten from Fastify to **Hono** (the shipped adapter; the old
10+
`@objectstack/fastify` import was broken), CSRF guidance points at `hono/csrf`,
11+
and stale `@objectstack/plugin-msw` references are dropped from the driver-memory
12+
and driver-turso docs. README framework lists narrowed to Hono.

docs/HARDENING.md

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ adapter layer for a production deployment.
1414
| Security response headers (CSP/XCTO/…) | **On** | `@objectstack/runtime` |
1515
| HSTS | Off (opt-in) | `securityHeaders.hsts: true` |
1616
| Token-bucket rate limit | Off (opt-in) | `RateLimiter` primitive |
17-
| CSRF | Adapter-layer concern | helmet / @fastify/csrf-protection |
17+
| CSRF | Adapter-layer concern | `hono/secure-headers` / `hono/csrf` |
1818
| Auth (better-auth) | On | `@objectstack/plugin-auth` |
1919
| Project membership (RBAC) | On when scoped | dispatcher plugin |
2020
| Field- and row-level perms | On | SecurityPlugin |
@@ -71,37 +71,40 @@ const write = new RateLimiter(DEFAULT_RATE_LIMITS.write); // 60 req / min / IP
7171
const read = new RateLimiter(DEFAULT_RATE_LIMITS.read); // 600 req / min / IP
7272
```
7373

74-
### Fastify recipe
74+
### Hono recipe
7575

7676
```ts
77-
import Fastify from 'fastify';
78-
import { objectStackPlugin } from '@objectstack/fastify';
77+
import { Hono } from 'hono';
78+
import { secureHeaders } from 'hono/secure-headers';
79+
import { objectStackMiddleware } from '@objectstack/hono';
7980
import { RateLimiter, DEFAULT_RATE_LIMITS } from '@objectstack/runtime';
80-
import helmet from '@fastify/helmet';
8181

82-
const app = Fastify({ trustProxy: 1 }); // trust ONE upstream proxy hop
83-
await app.register(helmet, { contentSecurityPolicy: false });
82+
const app = new Hono();
83+
app.use('*', secureHeaders()); // CSP / X-Content-Type-Options / X-Frame-Options / …
8484

8585
const buckets = {
8686
auth: new RateLimiter(DEFAULT_RATE_LIMITS.auth),
8787
write: new RateLimiter(DEFAULT_RATE_LIMITS.write),
8888
read: new RateLimiter(DEFAULT_RATE_LIMITS.read),
8989
};
9090

91-
app.addHook('onRequest', async (req, reply) => {
92-
const ip = req.ip;
93-
const bucket = req.url.startsWith('/api/v1/auth/') ? 'auth'
94-
: ['POST','PUT','PATCH','DELETE'].includes(req.method) ? 'write'
91+
// Rate-limit BEFORE the dispatcher middleware so rejected requests never reach it.
92+
app.use('/api/v1/*', async (c, next) => {
93+
const ip = c.req.header('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown';
94+
const bucket = c.req.path.startsWith('/api/v1/auth/') ? 'auth'
95+
: ['POST','PUT','PATCH','DELETE'].includes(c.req.method) ? 'write'
9596
: 'read';
9697
const decision = buckets[bucket].consume(`${ip}:${bucket}`);
97-
reply.header('X-RateLimit-Remaining', String(decision.remaining));
98+
c.header('X-RateLimit-Remaining', String(decision.remaining));
9899
if (!decision.allowed) {
99-
reply.header('Retry-After', String(Math.ceil(decision.retryAfterMs / 1000)));
100-
return reply.code(429).send({ error: 'Too many requests' });
100+
c.header('Retry-After', String(Math.ceil(decision.retryAfterMs / 1000)));
101+
return c.json({ error: 'Too many requests' }, 429);
101102
}
103+
await next();
102104
});
103105

104-
app.register(objectStackPlugin, { kernel, prefix: '/api/v1' });
106+
app.use('/api/v1/*', objectStackMiddleware(kernel)); // dispatcher last
107+
export default app; // Cloudflare Workers / Bun / Deno / Node (@hono/node-server)
105108
```
106109

107110
For multi-instance deploys, replace the default `MemoryStore` with a
@@ -131,8 +134,8 @@ Bearer …`. With bearer tokens stored in `localStorage` / memory there
131134
is no CSRF surface — browsers don't auto-attach the header.
132135

133136
CSRF protection becomes required when you switch to **cookie-based
134-
session auth**. In that case wire `@fastify/csrf-protection` (or the
135-
equivalent for your adapter) and exempt only the auth callback routes.
137+
session auth**. In that case wire a CSRF middleware (e.g. `hono/csrf`)
138+
and exempt only the auth callback routes.
136139

137140
## JWT / session lifecycle
138141

@@ -167,13 +170,18 @@ curl -H "Authorization: Bearer $TOK" $API/data/account
167170

168171
CORS is intentionally **not** opinionated by the runtime — it's an
169172
app-level policy that depends on which origins host your front-end.
170-
Configure at the adapter:
173+
Configure it on the Hono adapter:
171174

172175
```ts
173-
import cors from '@fastify/cors';
174-
await app.register(cors, {
175-
origin: ['https://app.example.com'],
176-
credentials: true,
176+
import { createHonoApp } from '@objectstack/hono';
177+
178+
const app = createHonoApp({
179+
kernel,
180+
prefix: '/api/v1',
181+
cors: {
182+
origin: ['https://app.example.com'],
183+
credentials: true,
184+
},
177185
});
178186
```
179187

docs/design/driver-turso.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ offline-capable desktop applications.
9090
| `@objectstack/rest` | 🟢 None | REST API is driver-agnostic |
9191
| `@objectstack/metadata` | 🟢 None | Metadata service is storage-agnostic |
9292
| `@objectstack/cli` | 🟡 Minor | Add `driver-turso` to `create-objectstack` templates |
93-
| Framework Adapters | 🟢 None | All adapters (Next.js, NestJS, Hono, etc.) are driver-agnostic |
93+
| Framework Adapters | 🟢 None | The Hono adapter is driver-agnostic |
9494

9595
**Key Insight:** The microkernel architecture means adding a new driver has **zero impact** on
9696
the server-side stack. The `IDataDriver` contract completely decouples the data layer.
@@ -122,7 +122,6 @@ export default defineStack({
122122
|:---|:---:|:---|
123123
| `@objectstack/client` | 🟢 None | Client SDK communicates via REST/GraphQL; driver-agnostic |
124124
| `@objectstack/client-react` | 🟢 None | React hooks use client SDK; no changes |
125-
| `@objectstack/plugin-msw` | 🟢 None | MSW mocks REST endpoints; driver-irrelevant |
126125

127126
**New Capability Unlocked:** With embedded replicas, a future `@objectstack/client-local` package
128127
could provide direct libSQL access in the browser (via WASM), enabling:

docs/launch-readiness.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ fix or acceptance.**
175175
draining them. Replaced with: `server.close()` (stop new + drain active) +
176176
`closeIdleConnections()` (release idle keep-alive), and force-close only after a
177177
bounded **drain window** (default 10s, < the kernel's 60s). +2 integration tests.
178-
- **Residual (not blocking):** embedding framework adapters (express/fastify/…)
178+
- **Residual (not blocking):** the Hono adapter
179179
intentionally leave signal handling to the host app; cluster/Redis close should
180180
be registered via `kernel.onShutdown(...)` by the cluster plugin — confirm it is.
181181
- **Owner:** _______ · Verify ✅ (mostly false positive; drain bug fixed) · Sign-off ☐ · Notes: Kernel shutdown already correct; hono drain fixed + tested. Awaiting human sign-off.

packages/plugins/driver-memory/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ const driver = new InMemoryDriver({
7979
## When to use
8080

8181
- ✅ Development, unit tests, CI, Storybook.
82-
- ✅ Browser-only demos pairing with [`@objectstack/plugin-msw`](../plugin-msw).
8382

8483
## When not to use
8584

@@ -90,7 +89,6 @@ const driver = new InMemoryDriver({
9089

9190
- [`@objectstack/objectql`](../../objectql) — query engine.
9291
- [`@objectstack/driver-sql`](../driver-sql) — production driver (ObjectStack Cloud ships an additional `@objectstack/driver-turso` for edge/multi-tenant deployments).
93-
- [`@objectstack/plugin-msw`](../plugin-msw) — browser mock API.
9492

9593
## Links
9694

packages/runtime/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ new AppPlugin(appConfig)
134134

135135
#### IHttpServer
136136

137-
Abstract interface for HTTP server capabilities. Allows plugins to work with any HTTP framework (Express, Fastify, Hono, etc.) without tight coupling.
137+
Abstract interface for HTTP server capabilities. Allows plugins to work with any HTTP framework (e.g. Hono) without tight coupling.
138138

139139
```typescript
140140
import { IHttpServer, IHttpRequest, IHttpResponse } from '@objectstack/runtime';
@@ -144,7 +144,7 @@ class MyHttpServerPlugin implements Plugin {
144144
name = 'http-server';
145145

146146
async init(ctx: PluginContext) {
147-
const server: IHttpServer = createMyServer(); // Express, Hono, etc.
147+
const server: IHttpServer = createMyServer(); // Hono, or any framework via IHttpServer
148148
ctx.registerService('http-server', server);
149149
}
150150
}
@@ -599,7 +599,7 @@ createDispatcherPlugin({
599599

600600
Token-bucket `RateLimiter` with pluggable `RateLimitStore` (in-memory default,
601601
Redis-friendly contract). Curated `DEFAULT_RATE_LIMITS` for auth / write / read
602-
buckets. Fastify / Hono / Express recipes in
602+
buckets. A Hono recipe in
603603
[`docs/HARDENING.md`](../../docs/HARDENING.md#rate-limiting).
604604

605605
```ts

0 commit comments

Comments
 (0)