Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/v12-docs-adapter-accuracy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@objectstack/runtime": patch
"@objectstack/driver-memory": patch
---

docs: align hardening / driver docs with the Hono-only adapter surface (12.0)

Follow-up to the adapter trim (#2391): the hardening guide's rate-limit/CORS
recipes are rewritten from Fastify to **Hono** (the shipped adapter; the old
`@objectstack/fastify` import was broken), CSRF guidance points at `hono/csrf`,
and stale `@objectstack/plugin-msw` references are dropped from the driver-memory
and driver-turso docs. README framework lists narrowed to Hono.
52 changes: 30 additions & 22 deletions docs/HARDENING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ adapter layer for a production deployment.
| Security response headers (CSP/XCTO/…) | **On** | `@objectstack/runtime` |
| HSTS | Off (opt-in) | `securityHeaders.hsts: true` |
| Token-bucket rate limit | Off (opt-in) | `RateLimiter` primitive |
| CSRF | Adapter-layer concern | helmet / @fastify/csrf-protection |
| CSRF | Adapter-layer concern | `hono/secure-headers` / `hono/csrf` |
| Auth (better-auth) | On | `@objectstack/plugin-auth` |
| Project membership (RBAC) | On when scoped | dispatcher plugin |
| Field- and row-level perms | On | SecurityPlugin |
Expand Down Expand Up @@ -71,37 +71,40 @@ const write = new RateLimiter(DEFAULT_RATE_LIMITS.write); // 60 req / min / IP
const read = new RateLimiter(DEFAULT_RATE_LIMITS.read); // 600 req / min / IP
```

### Fastify recipe
### Hono recipe

```ts
import Fastify from 'fastify';
import { objectStackPlugin } from '@objectstack/fastify';
import { Hono } from 'hono';
import { secureHeaders } from 'hono/secure-headers';
import { objectStackMiddleware } from '@objectstack/hono';
import { RateLimiter, DEFAULT_RATE_LIMITS } from '@objectstack/runtime';
import helmet from '@fastify/helmet';

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

const buckets = {
auth: new RateLimiter(DEFAULT_RATE_LIMITS.auth),
write: new RateLimiter(DEFAULT_RATE_LIMITS.write),
read: new RateLimiter(DEFAULT_RATE_LIMITS.read),
};

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

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

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

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

## JWT / session lifecycle

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

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

```ts
import cors from '@fastify/cors';
await app.register(cors, {
origin: ['https://app.example.com'],
credentials: true,
import { createHonoApp } from '@objectstack/hono';

const app = createHonoApp({
kernel,
prefix: '/api/v1',
cors: {
origin: ['https://app.example.com'],
credentials: true,
},
});
```

Expand Down
3 changes: 1 addition & 2 deletions docs/design/driver-turso.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ offline-capable desktop applications.
| `@objectstack/rest` | 🟢 None | REST API is driver-agnostic |
| `@objectstack/metadata` | 🟢 None | Metadata service is storage-agnostic |
| `@objectstack/cli` | 🟡 Minor | Add `driver-turso` to `create-objectstack` templates |
| Framework Adapters | 🟢 None | All adapters (Next.js, NestJS, Hono, etc.) are driver-agnostic |
| Framework Adapters | 🟢 None | The Hono adapter is driver-agnostic |

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

**New Capability Unlocked:** With embedded replicas, a future `@objectstack/client-local` package
could provide direct libSQL access in the browser (via WASM), enabling:
Expand Down
2 changes: 1 addition & 1 deletion docs/launch-readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ fix or acceptance.**
draining them. Replaced with: `server.close()` (stop new + drain active) +
`closeIdleConnections()` (release idle keep-alive), and force-close only after a
bounded **drain window** (default 10s, < the kernel's 60s). +2 integration tests.
- **Residual (not blocking):** embedding framework adapters (express/fastify/…)
- **Residual (not blocking):** the Hono adapter
intentionally leave signal handling to the host app; cluster/Redis close should
be registered via `kernel.onShutdown(...)` by the cluster plugin — confirm it is.
- **Owner:** _______ · Verify ✅ (mostly false positive; drain bug fixed) · Sign-off ☐ · Notes: Kernel shutdown already correct; hono drain fixed + tested. Awaiting human sign-off.
Expand Down
2 changes: 0 additions & 2 deletions packages/plugins/driver-memory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ const driver = new InMemoryDriver({
## When to use

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

## When not to use

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

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

## Links

Expand Down
6 changes: 3 additions & 3 deletions packages/runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ new AppPlugin(appConfig)

#### IHttpServer

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

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

async init(ctx: PluginContext) {
const server: IHttpServer = createMyServer(); // Express, Hono, etc.
const server: IHttpServer = createMyServer(); // Hono, or any framework via IHttpServer
ctx.registerService('http-server', server);
}
}
Expand Down Expand Up @@ -599,7 +599,7 @@ createDispatcherPlugin({

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

```ts
Expand Down