@vivero/stoma 0.1.0-rc.7 → 0.1.0-rc.8

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 (2) hide show
  1. package/README.md +325 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,325 @@
1
+ # Stoma 🌱
2
+
3
+ **Declarative API gateway as a TypeScript library. Runs on Cloudflare Workers, Node.js, Deno, Bun, and more.**
4
+
5
+ ![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue)
6
+ ![Hono](https://img.shields.io/badge/Hono-4.7-orange)
7
+ ![License](https://img.shields.io/badge/License-MIT-green)
8
+
9
+ ---
10
+
11
+ ## Why
12
+
13
+ API gateways in the JavaScript ecosystem are stuck between two bad options:
14
+
15
+ 1. **Infrastructure you deploy** -- Kong, KrakenD, AWS API Gateway. Powerful, but they are separate services you provision, configure with YAML, and operate independently from your application code. You lose type safety, your gateway config drifts from your codebase, and you pay for another piece of infrastructure.
16
+
17
+ 2. **Dead or abandoned libraries** -- Express Gateway was the closest thing to a TS-native gateway. It is unmaintained, locked to Express 4, and has no story for modern runtimes.
18
+
19
+ There is a third option: **a declarative gateway library that lives in your codebase and deploys with your standard workflow.**
20
+
21
+ `@vivero/stoma` fills this gap. It is a TypeScript library you import -- not a Go binary or YAML config you deploy. Define your routes, policies, and upstreams as typed objects. The library compiles them into an HTTP application you can export directly.
22
+
23
+ - Configured with **TypeScript**, not YAML
24
+ - Lives in **your codebase** -- deploy as part of your app or as a standalone service
25
+ - Runs on Cloudflare Workers, Node.js, Deno, Bun, Fastly, Lambda@Edge, Vercel Edge Functions
26
+ - Optionally leverages **platform-specific features** (Cloudflare Service Bindings, KV, Durable Objects) through a pluggable adapter system
27
+
28
+ Think KrakenD's declarative route-to-pipeline model, but as a TypeScript library.
29
+
30
+ ## Features
31
+
32
+ - **TypeScript-first with full type safety** -- Every config object, policy, and upstream is typed. No YAML. No JSON Schema. Your editor catches mistakes before your users do.
33
+ - **Powered by Hono** -- lightweight, zero dependencies, Web Standards API. One of the fastest routers in the JavaScript ecosystem.
34
+ - **Declarative route / pipeline / policy chain model** -- Define routes, attach ordered policy chains, and point at an upstream. The gateway wires it all together.
35
+ - **Dozens of built-in policies** -- Auth (JWT, API key, OAuth2, RBAC, HTTP signatures), traffic control (rate limiting, IP filtering, caching, threat protection), resilience (circuit breaker, retry, timeout), transforms (CORS, request/response rewriting, validation), and observability (structured logging, metrics, health checks).
36
+ - **Multi-runtime** -- Core depends only on Hono and the Web `Request`/`Response` API. No Node.js built-ins, no platform lock-in.
37
+ - **Pluggable adapter system** -- Runtime-specific capabilities (distributed stores, background tasks, service bindings) are injected through adapters, not hard-coded. Adapters ship for Cloudflare Workers, Node.js, Deno, Bun, and in-memory development.
38
+ - **Pluggable policy system** -- Policies are middleware functions with metadata. Write a custom policy in a few lines, or use `definePolicy()` from the SDK for a structured approach.
39
+
40
+ ## Installation
41
+
42
+ ```sh
43
+ npm install @vivero/stoma hono
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ### Basic gateway (runs on any runtime)
49
+
50
+ ```typescript
51
+ import { createGateway, jwtAuth, rateLimit, requestLog, cors } from "@vivero/stoma";
52
+
53
+ const gateway = createGateway({
54
+ name: "my-api-gateway",
55
+ basePath: "/api",
56
+ policies: [requestLog(), cors()],
57
+ routes: [
58
+ {
59
+ path: "/users/*",
60
+ methods: ["GET", "POST", "PUT", "DELETE"],
61
+ pipeline: {
62
+ policies: [
63
+ jwtAuth({ secret: "your-secret" }),
64
+ rateLimit({ max: 100, windowSeconds: 60 }),
65
+ ],
66
+ upstream: {
67
+ type: "url",
68
+ target: "https://users-api.internal.example.com",
69
+ rewritePath: (path) => path.replace("/api/users", ""),
70
+ },
71
+ },
72
+ },
73
+ {
74
+ path: "/health",
75
+ pipeline: {
76
+ upstream: {
77
+ type: "handler",
78
+ handler: () =>
79
+ new Response(JSON.stringify({ status: "ok" }), {
80
+ headers: { "Content-Type": "application/json" },
81
+ }),
82
+ },
83
+ },
84
+ },
85
+ ],
86
+ });
87
+
88
+ export default gateway.app;
89
+ ```
90
+
91
+ This gateway works out of the box on Cloudflare Workers (`export default gateway.app`), Bun (`export default gateway.app`), Deno (`Deno.serve(gateway.app.fetch)`), or Node.js (via `@hono/node-server`).
92
+
93
+ ### With Cloudflare-specific features
94
+
95
+ When deploying to Cloudflare Workers, use the Cloudflare adapter to unlock Service Bindings, KV-backed rate limiting, and Durable Objects:
96
+
97
+ ```typescript
98
+ import { createGateway, jwtAuth, rateLimit, requestLog } from "@vivero/stoma";
99
+ import { cloudflareAdapter } from "@vivero/stoma/adapters/cloudflare";
100
+
101
+ type Env = {
102
+ JWT_SECRET: string;
103
+ USERS_SERVICE: Fetcher;
104
+ RATE_LIMIT_KV: KVNamespace;
105
+ };
106
+
107
+ export default {
108
+ fetch(request: Request, env: Env, ctx: ExecutionContext) {
109
+ const gateway = createGateway({
110
+ name: "my-api-gateway",
111
+ basePath: "/api",
112
+ adapter: cloudflareAdapter({
113
+ rateLimitKv: env.RATE_LIMIT_KV,
114
+ executionCtx: ctx,
115
+ env,
116
+ }),
117
+ policies: [requestLog()],
118
+ routes: [
119
+ {
120
+ path: "/users/*",
121
+ pipeline: {
122
+ policies: [
123
+ jwtAuth({ secret: env.JWT_SECRET }),
124
+ rateLimit({ max: 100, windowSeconds: 60 }),
125
+ ],
126
+ upstream: {
127
+ type: "service-binding",
128
+ service: "USERS_SERVICE",
129
+ },
130
+ },
131
+ },
132
+ ],
133
+ });
134
+
135
+ return gateway.app.fetch(request, env, ctx);
136
+ },
137
+ };
138
+ ```
139
+
140
+ ## Configuration
141
+
142
+ The gateway is configured entirely through TypeScript. The top-level `GatewayConfig` type drives everything:
143
+
144
+ ```typescript
145
+ interface GatewayConfig {
146
+ name?: string; // Gateway name, used in logs and metrics
147
+ basePath?: string; // Base path prefix for all routes (e.g. "/api")
148
+ routes: RouteConfig[]; // Route definitions
149
+ policies?: Policy[]; // Global policies applied to every route
150
+ adapter?: GatewayAdapter; // Runtime adapter for stores and platform capabilities
151
+ onError?: (error: Error, c: unknown) => Response | Promise<Response>;
152
+ }
153
+ ```
154
+
155
+ Each route defines a **path**, optional **methods**, and a **pipeline**:
156
+
157
+ ```typescript
158
+ interface RouteConfig {
159
+ path: string; // Hono path syntax: "/users/:id", "/files/*"
160
+ methods?: HttpMethod[]; // ["GET", "POST", ...] -- defaults to all
161
+ pipeline: PipelineConfig; // Policy chain + upstream target
162
+ metadata?: Record<string, unknown>;
163
+ }
164
+ ```
165
+
166
+ A pipeline is an ordered chain of policies leading to an upstream:
167
+
168
+ ```typescript
169
+ interface PipelineConfig {
170
+ policies?: Policy[]; // Executed in order before the upstream
171
+ upstream: UpstreamConfig; // Where the request goes
172
+ }
173
+ ```
174
+
175
+ ### Upstream Types
176
+
177
+ Three upstream types cover the common deployment patterns:
178
+
179
+ ```typescript
180
+ // URL proxy -- forward to any HTTP endpoint (works on all runtimes)
181
+ { type: "url", target: "https://api.example.com", headers: { "X-Forwarded-By": "gateway" } }
182
+
183
+ // Service Binding -- zero-latency Worker-to-Worker routing (Cloudflare only)
184
+ { type: "service-binding", service: "AUTH_SERVICE" }
185
+
186
+ // Inline handler -- respond directly without proxying (works on all runtimes)
187
+ { type: "handler", handler: (c) => new Response("ok") }
188
+ ```
189
+
190
+ ### Adapters
191
+
192
+ Adapters provide runtime-specific store implementations and platform capabilities. The gateway core is runtime-agnostic; adapters plug in distributed rate limiting, caching, background tasks, and service binding dispatch.
193
+
194
+ ```typescript
195
+ import { cloudflareAdapter } from "@vivero/stoma/adapters/cloudflare";
196
+ import { nodeAdapter } from "@vivero/stoma/adapters/node";
197
+ import { denoAdapter } from "@vivero/stoma/adapters/deno";
198
+ import { bunAdapter } from "@vivero/stoma/adapters/bun";
199
+ import { memoryAdapter } from "@vivero/stoma/adapters/memory";
200
+ ```
201
+
202
+ | Adapter | Stores | `waitUntil` | Service Bindings | Use case |
203
+ |---------|--------|-------------|------------------|----------|
204
+ | `cloudflareAdapter` | KV, Durable Objects, Cache API | Yes | Yes | Production on Cloudflare Workers |
205
+ | `nodeAdapter` | In-memory defaults | No | No | Node.js servers via `@hono/node-server` |
206
+ | `denoAdapter` | In-memory defaults | No | No | Deno / Deno Deploy |
207
+ | `bunAdapter` | In-memory defaults | No | No | Bun servers |
208
+ | `memoryAdapter` | In-memory (all stores) | No | No | Development, testing, single-instance |
209
+
210
+ No adapter is required. Without one, policies that need stores (rate limiting, caching, circuit breaking) fall back to in-memory defaults automatically.
211
+
212
+ ## Policies
213
+
214
+ Policies are the building blocks of gateway pipelines. They are middleware handlers with a name and a priority (lower numbers execute first).
215
+
216
+ ### Built-in Policies
217
+
218
+ | Category | Policies |
219
+ |----------|----------|
220
+ | **Auth** | `jwtAuth`, `apiKeyAuth`, `basicAuth`, `oauth2`, `rbac`, `generateJwt`, `jws`, `generateHttpSignature`, `verifyHttpSignature` |
221
+ | **Traffic** | `rateLimit`, `ipFilter`, `geoIpFilter`, `cache`, `sslEnforce`, `requestLimit`, `jsonThreatProtection`, `regexThreatProtection`, `trafficShadow`, `interrupt`, `dynamicRouting`, `httpCallout`, `resourceFilter` |
222
+ | **Resilience** | `timeout`, `retry`, `circuitBreaker`, `latencyInjection` |
223
+ | **Transform** | `cors`, `overrideMethod`, `assignAttributes`, `assignContent`, `requestTransform`, `responseTransform`, `requestValidation`, `jsonValidation` |
224
+ | **Observability** | `requestLog`, `metricsReporter`, `assignMetrics`, `health` |
225
+ | **Utility** | `proxy`, `mock` |
226
+
227
+ Every policy accepts a `skip` function for conditional execution:
228
+
229
+ ```typescript
230
+ rateLimit({
231
+ max: 100,
232
+ skip: (c) => c.req.header("X-Internal") === "true",
233
+ })
234
+ ```
235
+
236
+ ### Writing a Custom Policy
237
+
238
+ A policy is any object conforming to the `Policy` interface:
239
+
240
+ ```typescript
241
+ import type { Policy } from "@vivero/stoma";
242
+
243
+ function requestTimer(): Policy {
244
+ return {
245
+ name: "request-timer",
246
+ priority: 5,
247
+ handler: async (c, next) => {
248
+ const start = performance.now();
249
+ await next();
250
+ c.header("X-Response-Time", `${(performance.now() - start).toFixed(1)}ms`);
251
+ },
252
+ };
253
+ }
254
+ ```
255
+
256
+ For a more structured approach, use the SDK:
257
+
258
+ ```typescript
259
+ import { definePolicy, Priority } from "@vivero/stoma";
260
+
261
+ const requestTimer = definePolicy({
262
+ name: "request-timer",
263
+ priority: Priority.EARLY_TRANSFORM,
264
+ handler: async ({ c, next }) => {
265
+ const start = performance.now();
266
+ await next();
267
+ c.header("X-Response-Time", `${(performance.now() - start).toFixed(1)}ms`);
268
+ },
269
+ });
270
+ ```
271
+
272
+ ### Policy Execution Order
273
+
274
+ Policies execute in **priority order** (lowest number first), then in **declaration order** for policies sharing a priority. Global policies and route-level policies are merged and sorted together.
275
+
276
+ | Priority | Phase | Examples |
277
+ |----------|-------|---------|
278
+ | 0 | Observability | `requestLog`, `assignMetrics` |
279
+ | 1 | Network filter | `ipFilter`, `geoIpFilter`, `metricsReporter` |
280
+ | 5 | Early transform | `cors`, `sslEnforce`, `requestLimit`, `latencyInjection` |
281
+ | 10 | Authentication | `jwtAuth`, `apiKeyAuth`, `basicAuth`, `oauth2`, `rbac` |
282
+ | 20 | Rate limiting | `rateLimit` |
283
+ | 30 | Circuit breaker | `circuitBreaker` |
284
+ | 40 | Caching | `cache` |
285
+ | 50 | Mid-pipeline | `requestTransform`, `assignAttributes`, `generateJwt`, `dynamicRouting` |
286
+ | 85 | Timeout | `timeout` |
287
+ | 90 | Retry | `retry` |
288
+ | 92 | Response | `responseTransform`, `trafficShadow`, `resourceFilter` |
289
+ | 95 | Proxy | `proxy`, `generateHttpSignature` |
290
+ | 100 | Default | Custom policies |
291
+ | 999 | Terminal | `mock` |
292
+
293
+ ## Documentation & Resources
294
+
295
+ - [Architecture Design](ARCHITECTURE.md) - Deep dive into the gateway's internal design and decisions.
296
+ - [Full Documentation](https://stoma.vivero.dev) - Comprehensive guides, recipes, and API reference.
297
+
298
+ ## Package Exports
299
+
300
+ | Import path | Contents |
301
+ |-------------|----------|
302
+ | `@vivero/stoma` | `createGateway`, all policies, metrics, core types |
303
+ | `@vivero/stoma/policies` | Policies only |
304
+ | `@vivero/stoma/sdk` | `definePolicy`, `Priority`, test harness |
305
+ | `@vivero/stoma/config` | Config types + optional Zod validation (requires `zod` peer dep) |
306
+ | `@vivero/stoma/adapters` | All adapter factories |
307
+ | `@vivero/stoma/adapters/cloudflare` | Cloudflare adapter only |
308
+ | `@vivero/stoma/adapters/node` | Node.js adapter only |
309
+ | `@vivero/stoma/adapters/deno` | Deno adapter only |
310
+ | `@vivero/stoma/adapters/bun` | Bun adapter only |
311
+ | `@vivero/stoma/adapters/memory` | In-memory adapter (dev/test) |
312
+
313
+ ## Contributing
314
+
315
+ Contributions are welcome. Please open an issue to discuss proposed changes before submitting a pull request.
316
+
317
+ ```sh
318
+ yarn install
319
+ yarn test
320
+ yarn typecheck
321
+ ```
322
+
323
+ ## License
324
+
325
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vivero/stoma",
3
- "version": "0.1.0-rc.7",
3
+ "version": "0.1.0-rc.8",
4
4
  "description": "Declarative API gateway as a TypeScript library. Runs on Cloudflare Workers, Node.js, Deno, Bun, and any JavaScript runtime.",
5
5
  "type": "module",
6
6
  "repository": {