@vivero/stoma 0.1.0-rc.5
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/dist/adapters/bun.d.ts +9 -0
- package/dist/adapters/bun.js +8 -0
- package/dist/adapters/bun.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +49 -0
- package/dist/adapters/cloudflare.js +85 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/deno.d.ts +9 -0
- package/dist/adapters/deno.js +8 -0
- package/dist/adapters/deno.js.map +1 -0
- package/dist/adapters/durable-object.d.ts +63 -0
- package/dist/adapters/durable-object.js +46 -0
- package/dist/adapters/durable-object.js.map +1 -0
- package/dist/adapters/index.d.ts +13 -0
- package/dist/adapters/index.js +53 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/memory.d.ts +9 -0
- package/dist/adapters/memory.js +14 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/node.d.ts +9 -0
- package/dist/adapters/node.js +8 -0
- package/dist/adapters/node.js.map +1 -0
- package/dist/adapters/postgres.d.ts +109 -0
- package/dist/adapters/postgres.js +242 -0
- package/dist/adapters/postgres.js.map +1 -0
- package/dist/adapters/redis.d.ts +116 -0
- package/dist/adapters/redis.js +194 -0
- package/dist/adapters/redis.js.map +1 -0
- package/dist/adapters/testing.d.ts +32 -0
- package/dist/adapters/testing.js +33 -0
- package/dist/adapters/testing.js.map +1 -0
- package/dist/adapters/types.d.ts +4 -0
- package/dist/adapters/types.js +1 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/config/index.d.ts +11 -0
- package/dist/config/index.js +21 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/merge.d.ts +48 -0
- package/dist/config/merge.js +83 -0
- package/dist/config/merge.js.map +1 -0
- package/dist/config/schema.d.ts +254 -0
- package/dist/config/schema.js +109 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/core/errors.d.ts +66 -0
- package/dist/core/errors.js +47 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/gateway.d.ts +44 -0
- package/dist/core/gateway.js +390 -0
- package/dist/core/gateway.js.map +1 -0
- package/dist/core/health.d.ts +78 -0
- package/dist/core/health.js +65 -0
- package/dist/core/health.js.map +1 -0
- package/dist/core/pipeline.d.ts +62 -0
- package/dist/core/pipeline.js +214 -0
- package/dist/core/pipeline.js.map +1 -0
- package/dist/core/protocol.d.ts +4 -0
- package/dist/core/protocol.js +1 -0
- package/dist/core/protocol.js.map +1 -0
- package/dist/core/scope.d.ts +67 -0
- package/dist/core/scope.js +44 -0
- package/dist/core/scope.js.map +1 -0
- package/dist/core/types.d.ts +252 -0
- package/dist/core/types.js +1 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +158 -0
- package/dist/index.js.map +1 -0
- package/dist/observability/admin.d.ts +32 -0
- package/dist/observability/admin.js +85 -0
- package/dist/observability/admin.js.map +1 -0
- package/dist/observability/metrics.d.ts +78 -0
- package/dist/observability/metrics.js +107 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/observability/tracing.d.ts +149 -0
- package/dist/observability/tracing.js +191 -0
- package/dist/observability/tracing.js.map +1 -0
- package/dist/policies/auth/api-key-auth.d.ts +64 -0
- package/dist/policies/auth/api-key-auth.js +93 -0
- package/dist/policies/auth/api-key-auth.js.map +1 -0
- package/dist/policies/auth/basic-auth.d.ts +33 -0
- package/dist/policies/auth/basic-auth.js +96 -0
- package/dist/policies/auth/basic-auth.js.map +1 -0
- package/dist/policies/auth/crypto.d.ts +29 -0
- package/dist/policies/auth/crypto.js +100 -0
- package/dist/policies/auth/crypto.js.map +1 -0
- package/dist/policies/auth/generate-http-signature.d.ts +30 -0
- package/dist/policies/auth/generate-http-signature.js +79 -0
- package/dist/policies/auth/generate-http-signature.js.map +1 -0
- package/dist/policies/auth/generate-jwt.d.ts +44 -0
- package/dist/policies/auth/generate-jwt.js +99 -0
- package/dist/policies/auth/generate-jwt.js.map +1 -0
- package/dist/policies/auth/http-signature-base.d.ts +55 -0
- package/dist/policies/auth/http-signature-base.js +140 -0
- package/dist/policies/auth/http-signature-base.js.map +1 -0
- package/dist/policies/auth/jws.d.ts +46 -0
- package/dist/policies/auth/jws.js +317 -0
- package/dist/policies/auth/jws.js.map +1 -0
- package/dist/policies/auth/jwt-auth.d.ts +64 -0
- package/dist/policies/auth/jwt-auth.js +268 -0
- package/dist/policies/auth/jwt-auth.js.map +1 -0
- package/dist/policies/auth/oauth2.d.ts +38 -0
- package/dist/policies/auth/oauth2.js +254 -0
- package/dist/policies/auth/oauth2.js.map +1 -0
- package/dist/policies/auth/rbac.d.ts +30 -0
- package/dist/policies/auth/rbac.js +115 -0
- package/dist/policies/auth/rbac.js.map +1 -0
- package/dist/policies/auth/verify-http-signature.d.ts +30 -0
- package/dist/policies/auth/verify-http-signature.js +147 -0
- package/dist/policies/auth/verify-http-signature.js.map +1 -0
- package/dist/policies/index.d.ts +51 -0
- package/dist/policies/index.js +109 -0
- package/dist/policies/index.js.map +1 -0
- package/dist/policies/mock.d.ts +60 -0
- package/dist/policies/mock.js +29 -0
- package/dist/policies/mock.js.map +1 -0
- package/dist/policies/observability/assign-metrics.d.ts +37 -0
- package/dist/policies/observability/assign-metrics.js +29 -0
- package/dist/policies/observability/assign-metrics.js.map +1 -0
- package/dist/policies/observability/metrics-reporter.d.ts +25 -0
- package/dist/policies/observability/metrics-reporter.js +62 -0
- package/dist/policies/observability/metrics-reporter.js.map +1 -0
- package/dist/policies/observability/request-log.d.ts +135 -0
- package/dist/policies/observability/request-log.js +124 -0
- package/dist/policies/observability/request-log.js.map +1 -0
- package/dist/policies/observability/server-timing.d.ts +35 -0
- package/dist/policies/observability/server-timing.js +89 -0
- package/dist/policies/observability/server-timing.js.map +1 -0
- package/dist/policies/proxy.d.ts +59 -0
- package/dist/policies/proxy.js +47 -0
- package/dist/policies/proxy.js.map +1 -0
- package/dist/policies/resilience/circuit-breaker.d.ts +4 -0
- package/dist/policies/resilience/circuit-breaker.js +280 -0
- package/dist/policies/resilience/circuit-breaker.js.map +1 -0
- package/dist/policies/resilience/latency-injection.d.ts +35 -0
- package/dist/policies/resilience/latency-injection.js +26 -0
- package/dist/policies/resilience/latency-injection.js.map +1 -0
- package/dist/policies/resilience/retry.d.ts +71 -0
- package/dist/policies/resilience/retry.js +79 -0
- package/dist/policies/resilience/retry.js.map +1 -0
- package/dist/policies/resilience/timeout.d.ts +32 -0
- package/dist/policies/resilience/timeout.js +46 -0
- package/dist/policies/resilience/timeout.js.map +1 -0
- package/dist/policies/sdk/define-policy.d.ts +176 -0
- package/dist/policies/sdk/define-policy.js +42 -0
- package/dist/policies/sdk/define-policy.js.map +1 -0
- package/dist/policies/sdk/helpers.d.ts +132 -0
- package/dist/policies/sdk/helpers.js +87 -0
- package/dist/policies/sdk/helpers.js.map +1 -0
- package/dist/policies/sdk/index.d.ts +10 -0
- package/dist/policies/sdk/index.js +35 -0
- package/dist/policies/sdk/index.js.map +1 -0
- package/dist/policies/sdk/priority.d.ts +44 -0
- package/dist/policies/sdk/priority.js +36 -0
- package/dist/policies/sdk/priority.js.map +1 -0
- package/dist/policies/sdk/testing.d.ts +53 -0
- package/dist/policies/sdk/testing.js +41 -0
- package/dist/policies/sdk/testing.js.map +1 -0
- package/dist/policies/sdk/trace.d.ts +73 -0
- package/dist/policies/sdk/trace.js +25 -0
- package/dist/policies/sdk/trace.js.map +1 -0
- package/dist/policies/traffic/cache.d.ts +4 -0
- package/dist/policies/traffic/cache.js +224 -0
- package/dist/policies/traffic/cache.js.map +1 -0
- package/dist/policies/traffic/dynamic-routing.d.ts +54 -0
- package/dist/policies/traffic/dynamic-routing.js +36 -0
- package/dist/policies/traffic/dynamic-routing.js.map +1 -0
- package/dist/policies/traffic/geo-ip-filter.d.ts +37 -0
- package/dist/policies/traffic/geo-ip-filter.js +74 -0
- package/dist/policies/traffic/geo-ip-filter.js.map +1 -0
- package/dist/policies/traffic/http-callout.d.ts +59 -0
- package/dist/policies/traffic/http-callout.js +69 -0
- package/dist/policies/traffic/http-callout.js.map +1 -0
- package/dist/policies/traffic/interrupt.d.ts +46 -0
- package/dist/policies/traffic/interrupt.js +38 -0
- package/dist/policies/traffic/interrupt.js.map +1 -0
- package/dist/policies/traffic/ip-filter.d.ts +47 -0
- package/dist/policies/traffic/ip-filter.js +57 -0
- package/dist/policies/traffic/ip-filter.js.map +1 -0
- package/dist/policies/traffic/json-threat-protection.d.ts +51 -0
- package/dist/policies/traffic/json-threat-protection.js +173 -0
- package/dist/policies/traffic/json-threat-protection.js.map +1 -0
- package/dist/policies/traffic/rate-limit.d.ts +4 -0
- package/dist/policies/traffic/rate-limit.js +145 -0
- package/dist/policies/traffic/rate-limit.js.map +1 -0
- package/dist/policies/traffic/regex-threat-protection.d.ts +54 -0
- package/dist/policies/traffic/regex-threat-protection.js +109 -0
- package/dist/policies/traffic/regex-threat-protection.js.map +1 -0
- package/dist/policies/traffic/request-limit.d.ts +27 -0
- package/dist/policies/traffic/request-limit.js +41 -0
- package/dist/policies/traffic/request-limit.js.map +1 -0
- package/dist/policies/traffic/resource-filter.d.ts +38 -0
- package/dist/policies/traffic/resource-filter.js +184 -0
- package/dist/policies/traffic/resource-filter.js.map +1 -0
- package/dist/policies/traffic/ssl-enforce.d.ts +27 -0
- package/dist/policies/traffic/ssl-enforce.js +38 -0
- package/dist/policies/traffic/ssl-enforce.js.map +1 -0
- package/dist/policies/traffic/traffic-shadow.d.ts +40 -0
- package/dist/policies/traffic/traffic-shadow.js +87 -0
- package/dist/policies/traffic/traffic-shadow.js.map +1 -0
- package/dist/policies/transform/assign-attributes.d.ts +33 -0
- package/dist/policies/transform/assign-attributes.js +38 -0
- package/dist/policies/transform/assign-attributes.js.map +1 -0
- package/dist/policies/transform/assign-content.d.ts +40 -0
- package/dist/policies/transform/assign-content.js +185 -0
- package/dist/policies/transform/assign-content.js.map +1 -0
- package/dist/policies/transform/cors.d.ts +57 -0
- package/dist/policies/transform/cors.js +23 -0
- package/dist/policies/transform/cors.js.map +1 -0
- package/dist/policies/transform/json-validation.d.ts +50 -0
- package/dist/policies/transform/json-validation.js +125 -0
- package/dist/policies/transform/json-validation.js.map +1 -0
- package/dist/policies/transform/override-method.d.ts +33 -0
- package/dist/policies/transform/override-method.js +48 -0
- package/dist/policies/transform/override-method.js.map +1 -0
- package/dist/policies/transform/request-validation.d.ts +59 -0
- package/dist/policies/transform/request-validation.js +121 -0
- package/dist/policies/transform/request-validation.js.map +1 -0
- package/dist/policies/transform/transform.d.ts +75 -0
- package/dist/policies/transform/transform.js +116 -0
- package/dist/policies/transform/transform.js.map +1 -0
- package/dist/policies/types.d.ts +4 -0
- package/dist/policies/types.js +1 -0
- package/dist/policies/types.js.map +1 -0
- package/dist/protocol-2fD3DJrL.d.ts +725 -0
- package/dist/utils/cidr.d.ts +58 -0
- package/dist/utils/cidr.js +107 -0
- package/dist/utils/cidr.js.map +1 -0
- package/dist/utils/debug.d.ts +1 -0
- package/dist/utils/debug.js +13 -0
- package/dist/utils/debug.js.map +1 -0
- package/dist/utils/headers.d.ts +68 -0
- package/dist/utils/headers.js +25 -0
- package/dist/utils/headers.js.map +1 -0
- package/dist/utils/ip.d.ts +58 -0
- package/dist/utils/ip.js +28 -0
- package/dist/utils/ip.js.map +1 -0
- package/dist/utils/redact.d.ts +30 -0
- package/dist/utils/redact.js +52 -0
- package/dist/utils/redact.js.map +1 -0
- package/dist/utils/request-id.d.ts +11 -0
- package/dist/utils/request-id.js +7 -0
- package/dist/utils/request-id.js.map +1 -0
- package/dist/utils/timing-safe.d.ts +31 -0
- package/dist/utils/timing-safe.js +17 -0
- package/dist/utils/timing-safe.js.map +1 -0
- package/dist/utils/timing.d.ts +27 -0
- package/dist/utils/timing.js +12 -0
- package/dist/utils/timing.js.map +1 -0
- package/dist/utils/trace-context.d.ts +51 -0
- package/dist/utils/trace-context.js +37 -0
- package/dist/utils/trace-context.js.map +1 -0
- package/package.json +202 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { G as GatewayAdapter } from '../protocol-2fD3DJrL.js';
|
|
2
|
+
import 'hono';
|
|
3
|
+
import '../policies/sdk/trace.js';
|
|
4
|
+
import '@vivero/stoma-core';
|
|
5
|
+
|
|
6
|
+
/** Create a GatewayAdapter for Bun. Delegates to memoryAdapter() for in-memory stores. */
|
|
7
|
+
declare function bunAdapter(): GatewayAdapter;
|
|
8
|
+
|
|
9
|
+
export { bunAdapter };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/bun.ts"],"sourcesContent":["/**\n * Bun adapter for stoma.\n *\n * Bun doesn't provide `waitUntil` or service bindings natively.\n * Use `memoryAdapter()` for stores and this adapter as a marker/extension\n * point for Bun-specific capabilities.\n *\n * @example\n * ```ts\n * import { createGateway } from \"@vivero/stoma\";\n * import { bunAdapter } from \"@vivero/stoma/adapters/bun\";\n *\n * const gateway = createGateway({\n * adapter: bunAdapter(),\n * routes: [...]\n * });\n *\n * export default gateway.app;\n * ```\n *\n * @module bun-adapter\n */\nimport { memoryAdapter } from \"./memory\";\nimport type { GatewayAdapter } from \"./types\";\n\n/** Create a GatewayAdapter for Bun. Delegates to memoryAdapter() for in-memory stores. */\nexport function bunAdapter(): GatewayAdapter {\n return memoryAdapter();\n}\n"],"mappings":"AAsBA,SAAS,qBAAqB;AAIvB,SAAS,aAA6B;AAC3C,SAAO,cAAc;AACvB;","names":[]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { C as CacheStore, R as RateLimitStore, G as GatewayAdapter } from '../protocol-2fD3DJrL.js';
|
|
2
|
+
import 'hono';
|
|
3
|
+
import '../policies/sdk/trace.js';
|
|
4
|
+
import '@vivero/stoma-core';
|
|
5
|
+
|
|
6
|
+
/** Rate limit store backed by Cloudflare Workers KV. */
|
|
7
|
+
declare class KVRateLimitStore implements RateLimitStore {
|
|
8
|
+
private kv;
|
|
9
|
+
constructor(kv: KVNamespace);
|
|
10
|
+
increment(key: string, windowSeconds: number): Promise<{
|
|
11
|
+
count: number;
|
|
12
|
+
resetAt: number;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
/** Response cache backed by the Cloudflare Cache API. */
|
|
16
|
+
declare class CacheApiCacheStore implements CacheStore {
|
|
17
|
+
private cache;
|
|
18
|
+
private origin;
|
|
19
|
+
/**
|
|
20
|
+
* @param cache - A `Cache` instance (e.g. `caches.default`). Falls back to `caches.default` when omitted.
|
|
21
|
+
* @param origin - Synthetic origin used to construct cache keys. Default: `"https://edge-gateway.internal"`.
|
|
22
|
+
*/
|
|
23
|
+
constructor(cache?: Cache, origin?: string);
|
|
24
|
+
get(key: string): Promise<Response | null>;
|
|
25
|
+
put(key: string, response: Response, ttlSeconds: number): Promise<void>;
|
|
26
|
+
delete(key: string): Promise<boolean>;
|
|
27
|
+
}
|
|
28
|
+
interface CloudflareAdapterBindings {
|
|
29
|
+
rateLimitKv?: KVNamespace;
|
|
30
|
+
rateLimitDo?: DurableObjectNamespace;
|
|
31
|
+
cache?: Cache;
|
|
32
|
+
/** Synthetic origin used for Cache API cache keys. Default: `"https://edge-gateway.internal"`. */
|
|
33
|
+
cacheOrigin?: string;
|
|
34
|
+
/** Workers `ExecutionContext` - enables `waitUntil` for background work (e.g. traffic shadow). */
|
|
35
|
+
executionCtx?: ExecutionContext;
|
|
36
|
+
/** Workers `env` object - enables `dispatchBinding` for service binding dispatch via the adapter. */
|
|
37
|
+
env?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create a GatewayAdapter using Cloudflare-native stores.
|
|
41
|
+
*
|
|
42
|
+
* Rate limiting priority: Durable Objects (strongly consistent) > KV (eventually consistent) > none.
|
|
43
|
+
*
|
|
44
|
+
* Pass `executionCtx` to enable `waitUntil` (for traffic shadow and other background work).
|
|
45
|
+
* Pass `env` to enable `dispatchBinding` (for service binding dispatch via the adapter).
|
|
46
|
+
*/
|
|
47
|
+
declare function cloudflareAdapter(bindings: CloudflareAdapterBindings): GatewayAdapter;
|
|
48
|
+
|
|
49
|
+
export { CacheApiCacheStore, type CloudflareAdapterBindings, KVRateLimitStore, cloudflareAdapter };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { InMemoryCircuitBreakerStore } from "../policies/resilience/circuit-breaker";
|
|
2
|
+
import { DurableObjectRateLimitStore } from "./durable-object";
|
|
3
|
+
class KVRateLimitStore {
|
|
4
|
+
constructor(kv) {
|
|
5
|
+
this.kv = kv;
|
|
6
|
+
}
|
|
7
|
+
async increment(key, windowSeconds) {
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
const raw = await this.kv.get(key, "json");
|
|
10
|
+
if (raw && raw.resetAt > now) {
|
|
11
|
+
const updated = { count: raw.count + 1, resetAt: raw.resetAt };
|
|
12
|
+
const ttl = Math.max(1, Math.ceil((raw.resetAt - now) / 1e3));
|
|
13
|
+
await this.kv.put(key, JSON.stringify(updated), { expirationTtl: ttl });
|
|
14
|
+
return updated;
|
|
15
|
+
}
|
|
16
|
+
const resetAt = now + windowSeconds * 1e3;
|
|
17
|
+
const entry = { count: 1, resetAt };
|
|
18
|
+
await this.kv.put(key, JSON.stringify(entry), {
|
|
19
|
+
expirationTtl: windowSeconds
|
|
20
|
+
});
|
|
21
|
+
return entry;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const DEFAULT_CACHE_ORIGIN = "https://stoma.internal";
|
|
25
|
+
class CacheApiCacheStore {
|
|
26
|
+
cache;
|
|
27
|
+
origin;
|
|
28
|
+
/**
|
|
29
|
+
* @param cache - A `Cache` instance (e.g. `caches.default`). Falls back to `caches.default` when omitted.
|
|
30
|
+
* @param origin - Synthetic origin used to construct cache keys. Default: `"https://edge-gateway.internal"`.
|
|
31
|
+
*/
|
|
32
|
+
constructor(cache, origin) {
|
|
33
|
+
this.cache = cache ?? caches.default;
|
|
34
|
+
this.origin = origin ?? DEFAULT_CACHE_ORIGIN;
|
|
35
|
+
}
|
|
36
|
+
async get(key) {
|
|
37
|
+
const cacheKey = new Request(`${this.origin}/${encodeURIComponent(key)}`);
|
|
38
|
+
const match = await this.cache.match(cacheKey);
|
|
39
|
+
return match ?? null;
|
|
40
|
+
}
|
|
41
|
+
async put(key, response, ttlSeconds) {
|
|
42
|
+
const cacheKey = new Request(`${this.origin}/${encodeURIComponent(key)}`);
|
|
43
|
+
const headers = new Headers(response.headers);
|
|
44
|
+
headers.set("Cache-Control", `s-maxage=${ttlSeconds}`);
|
|
45
|
+
const body = await response.arrayBuffer();
|
|
46
|
+
const cached = new Response(body, {
|
|
47
|
+
status: response.status,
|
|
48
|
+
headers
|
|
49
|
+
});
|
|
50
|
+
await this.cache.put(cacheKey, cached);
|
|
51
|
+
}
|
|
52
|
+
async delete(key) {
|
|
53
|
+
const cacheKey = new Request(`${this.origin}/${encodeURIComponent(key)}`);
|
|
54
|
+
return this.cache.delete(cacheKey);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function cloudflareAdapter(bindings) {
|
|
58
|
+
let rateLimitStore;
|
|
59
|
+
if (bindings.rateLimitDo) {
|
|
60
|
+
rateLimitStore = new DurableObjectRateLimitStore(bindings.rateLimitDo);
|
|
61
|
+
} else if (bindings.rateLimitKv) {
|
|
62
|
+
rateLimitStore = new KVRateLimitStore(bindings.rateLimitKv);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
rateLimitStore,
|
|
66
|
+
circuitBreakerStore: new InMemoryCircuitBreakerStore(),
|
|
67
|
+
cacheStore: bindings.cache ? new CacheApiCacheStore(bindings.cache, bindings.cacheOrigin) : new CacheApiCacheStore(void 0, bindings.cacheOrigin),
|
|
68
|
+
waitUntil: bindings.executionCtx ? (p) => bindings.executionCtx.waitUntil(p) : void 0,
|
|
69
|
+
dispatchBinding: bindings.env ? async (service, request) => {
|
|
70
|
+
const binding = bindings.env[service];
|
|
71
|
+
if (!binding || typeof binding.fetch !== "function") {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Service binding "${service}" is not available in the Worker environment`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return binding.fetch(request);
|
|
77
|
+
} : void 0
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
CacheApiCacheStore,
|
|
82
|
+
KVRateLimitStore,
|
|
83
|
+
cloudflareAdapter
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/cloudflare.ts"],"sourcesContent":["import { InMemoryCircuitBreakerStore } from \"../policies/resilience/circuit-breaker\";\nimport type { CacheStore } from \"../policies/traffic/cache\";\nimport type { RateLimitStore } from \"../policies/traffic/rate-limit\";\nimport { DurableObjectRateLimitStore } from \"./durable-object\";\nimport type { GatewayAdapter } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// KV-backed rate limit store\n// ---------------------------------------------------------------------------\n\n/** Rate limit store backed by Cloudflare Workers KV. */\nexport class KVRateLimitStore implements RateLimitStore {\n constructor(private kv: KVNamespace) {}\n\n async increment(\n key: string,\n windowSeconds: number\n ): Promise<{ count: number; resetAt: number }> {\n const now = Date.now();\n const raw = (await this.kv.get(key, \"json\")) as {\n count: number;\n resetAt: number;\n } | null;\n\n if (raw && raw.resetAt > now) {\n const updated = { count: raw.count + 1, resetAt: raw.resetAt };\n const ttl = Math.max(1, Math.ceil((raw.resetAt - now) / 1000));\n await this.kv.put(key, JSON.stringify(updated), { expirationTtl: ttl });\n return updated;\n }\n\n const resetAt = now + windowSeconds * 1000;\n const entry = { count: 1, resetAt };\n await this.kv.put(key, JSON.stringify(entry), {\n expirationTtl: windowSeconds,\n });\n return entry;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cache API-backed cache store\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_CACHE_ORIGIN = \"https://stoma.internal\";\n\n/** Response cache backed by the Cloudflare Cache API. */\nexport class CacheApiCacheStore implements CacheStore {\n private cache: Cache;\n private origin: string;\n\n /**\n * @param cache - A `Cache` instance (e.g. `caches.default`). Falls back to `caches.default` when omitted.\n * @param origin - Synthetic origin used to construct cache keys. Default: `\"https://edge-gateway.internal\"`.\n */\n constructor(cache?: Cache, origin?: string) {\n // Default to caches.default when available, otherwise accept injected cache\n this.cache = cache ?? (caches as unknown as { default: Cache }).default;\n this.origin = origin ?? DEFAULT_CACHE_ORIGIN;\n }\n\n async get(key: string): Promise<Response | null> {\n const cacheKey = new Request(`${this.origin}/${encodeURIComponent(key)}`);\n const match = await this.cache.match(cacheKey);\n return match ?? null;\n }\n\n async put(\n key: string,\n response: Response,\n ttlSeconds: number\n ): Promise<void> {\n const cacheKey = new Request(`${this.origin}/${encodeURIComponent(key)}`);\n const headers = new Headers(response.headers);\n headers.set(\"Cache-Control\", `s-maxage=${ttlSeconds}`);\n const body = await response.arrayBuffer();\n const cached = new Response(body, {\n status: response.status,\n headers,\n });\n await this.cache.put(cacheKey, cached);\n }\n\n async delete(key: string): Promise<boolean> {\n const cacheKey = new Request(`${this.origin}/${encodeURIComponent(key)}`);\n return this.cache.delete(cacheKey);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport interface CloudflareAdapterBindings {\n rateLimitKv?: KVNamespace;\n rateLimitDo?: DurableObjectNamespace;\n cache?: Cache;\n /** Synthetic origin used for Cache API cache keys. Default: `\"https://edge-gateway.internal\"`. */\n cacheOrigin?: string;\n /** Workers `ExecutionContext` - enables `waitUntil` for background work (e.g. traffic shadow). */\n executionCtx?: ExecutionContext;\n /** Workers `env` object - enables `dispatchBinding` for service binding dispatch via the adapter. */\n env?: Record<string, unknown>;\n}\n\n/**\n * Create a GatewayAdapter using Cloudflare-native stores.\n *\n * Rate limiting priority: Durable Objects (strongly consistent) > KV (eventually consistent) > none.\n *\n * Pass `executionCtx` to enable `waitUntil` (for traffic shadow and other background work).\n * Pass `env` to enable `dispatchBinding` (for service binding dispatch via the adapter).\n */\nexport function cloudflareAdapter(\n bindings: CloudflareAdapterBindings\n): GatewayAdapter {\n let rateLimitStore: RateLimitStore | undefined;\n if (bindings.rateLimitDo) {\n rateLimitStore = new DurableObjectRateLimitStore(bindings.rateLimitDo);\n } else if (bindings.rateLimitKv) {\n rateLimitStore = new KVRateLimitStore(bindings.rateLimitKv);\n }\n\n return {\n rateLimitStore,\n circuitBreakerStore: new InMemoryCircuitBreakerStore(),\n cacheStore: bindings.cache\n ? new CacheApiCacheStore(bindings.cache, bindings.cacheOrigin)\n : new CacheApiCacheStore(undefined, bindings.cacheOrigin),\n waitUntil: bindings.executionCtx\n ? (p) => bindings.executionCtx!.waitUntil(p)\n : undefined,\n dispatchBinding: bindings.env\n ? async (service, request) => {\n const binding = bindings.env![service] as\n | { fetch: typeof fetch }\n | undefined;\n if (!binding || typeof binding.fetch !== \"function\") {\n throw new Error(\n `Service binding \"${service}\" is not available in the Worker environment`\n );\n }\n return binding.fetch(request);\n }\n : undefined,\n };\n}\n"],"mappings":"AAAA,SAAS,mCAAmC;AAG5C,SAAS,mCAAmC;AAQrC,MAAM,iBAA2C;AAAA,EACtD,YAAoB,IAAiB;AAAjB;AAAA,EAAkB;AAAA,EAEtC,MAAM,UACJ,KACA,eAC6C;AAC7C,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,MAAO,MAAM,KAAK,GAAG,IAAI,KAAK,MAAM;AAK1C,QAAI,OAAO,IAAI,UAAU,KAAK;AAC5B,YAAM,UAAU,EAAE,OAAO,IAAI,QAAQ,GAAG,SAAS,IAAI,QAAQ;AAC7D,YAAM,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,UAAU,OAAO,GAAI,CAAC;AAC7D,YAAM,KAAK,GAAG,IAAI,KAAK,KAAK,UAAU,OAAO,GAAG,EAAE,eAAe,IAAI,CAAC;AACtE,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,MAAM,gBAAgB;AACtC,UAAM,QAAQ,EAAE,OAAO,GAAG,QAAQ;AAClC,UAAM,KAAK,GAAG,IAAI,KAAK,KAAK,UAAU,KAAK,GAAG;AAAA,MAC5C,eAAe;AAAA,IACjB,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAMA,MAAM,uBAAuB;AAGtB,MAAM,mBAAyC;AAAA,EAC5C;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMR,YAAY,OAAe,QAAiB;AAE1C,SAAK,QAAQ,SAAU,OAAyC;AAChE,SAAK,SAAS,UAAU;AAAA,EAC1B;AAAA,EAEA,MAAM,IAAI,KAAuC;AAC/C,UAAM,WAAW,IAAI,QAAQ,GAAG,KAAK,MAAM,IAAI,mBAAmB,GAAG,CAAC,EAAE;AACxE,UAAM,QAAQ,MAAM,KAAK,MAAM,MAAM,QAAQ;AAC7C,WAAO,SAAS;AAAA,EAClB;AAAA,EAEA,MAAM,IACJ,KACA,UACA,YACe;AACf,UAAM,WAAW,IAAI,QAAQ,GAAG,KAAK,MAAM,IAAI,mBAAmB,GAAG,CAAC,EAAE;AACxE,UAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;AAC5C,YAAQ,IAAI,iBAAiB,YAAY,UAAU,EAAE;AACrD,UAAM,OAAO,MAAM,SAAS,YAAY;AACxC,UAAM,SAAS,IAAI,SAAS,MAAM;AAAA,MAChC,QAAQ,SAAS;AAAA,MACjB;AAAA,IACF,CAAC;AACD,UAAM,KAAK,MAAM,IAAI,UAAU,MAAM;AAAA,EACvC;AAAA,EAEA,MAAM,OAAO,KAA+B;AAC1C,UAAM,WAAW,IAAI,QAAQ,GAAG,KAAK,MAAM,IAAI,mBAAmB,GAAG,CAAC,EAAE;AACxE,WAAO,KAAK,MAAM,OAAO,QAAQ;AAAA,EACnC;AACF;AA0BO,SAAS,kBACd,UACgB;AAChB,MAAI;AACJ,MAAI,SAAS,aAAa;AACxB,qBAAiB,IAAI,4BAA4B,SAAS,WAAW;AAAA,EACvE,WAAW,SAAS,aAAa;AAC/B,qBAAiB,IAAI,iBAAiB,SAAS,WAAW;AAAA,EAC5D;AAEA,SAAO;AAAA,IACL;AAAA,IACA,qBAAqB,IAAI,4BAA4B;AAAA,IACrD,YAAY,SAAS,QACjB,IAAI,mBAAmB,SAAS,OAAO,SAAS,WAAW,IAC3D,IAAI,mBAAmB,QAAW,SAAS,WAAW;AAAA,IAC1D,WAAW,SAAS,eAChB,CAAC,MAAM,SAAS,aAAc,UAAU,CAAC,IACzC;AAAA,IACJ,iBAAiB,SAAS,MACtB,OAAO,SAAS,YAAY;AAC1B,YAAM,UAAU,SAAS,IAAK,OAAO;AAGrC,UAAI,CAAC,WAAW,OAAO,QAAQ,UAAU,YAAY;AACnD,cAAM,IAAI;AAAA,UACR,oBAAoB,OAAO;AAAA,QAC7B;AAAA,MACF;AACA,aAAO,QAAQ,MAAM,OAAO;AAAA,IAC9B,IACA;AAAA,EACN;AACF;","names":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { G as GatewayAdapter } from '../protocol-2fD3DJrL.js';
|
|
2
|
+
import 'hono';
|
|
3
|
+
import '../policies/sdk/trace.js';
|
|
4
|
+
import '@vivero/stoma-core';
|
|
5
|
+
|
|
6
|
+
/** Create a GatewayAdapter for Deno. Delegates to memoryAdapter() for in-memory stores. */
|
|
7
|
+
declare function denoAdapter(): GatewayAdapter;
|
|
8
|
+
|
|
9
|
+
export { denoAdapter };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/deno.ts"],"sourcesContent":["/**\n * Deno adapter for stoma.\n *\n * Deno Deploy doesn't provide `waitUntil` or service bindings natively.\n * Use `memoryAdapter()` for stores and this adapter as a marker/extension\n * point for Deno-specific capabilities.\n *\n * @example\n * ```ts\n * import { createGateway } from \"@vivero/stoma\";\n * import { denoAdapter } from \"@vivero/stoma/adapters/deno\";\n *\n * const gateway = createGateway({\n * adapter: denoAdapter(),\n * routes: [...]\n * });\n *\n * Deno.serve(gateway.app.fetch);\n * ```\n *\n * @module deno-adapter\n */\nimport { memoryAdapter } from \"./memory\";\nimport type { GatewayAdapter } from \"./types\";\n\n/** Create a GatewayAdapter for Deno. Delegates to memoryAdapter() for in-memory stores. */\nexport function denoAdapter(): GatewayAdapter {\n return memoryAdapter();\n}\n"],"mappings":"AAsBA,SAAS,qBAAqB;AAIvB,SAAS,cAA8B;AAC5C,SAAO,cAAc;AACvB;","names":[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { R as RateLimitStore } from '../protocol-2fD3DJrL.js';
|
|
2
|
+
import 'hono';
|
|
3
|
+
import '../policies/sdk/trace.js';
|
|
4
|
+
import '@vivero/stoma-core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Durable Object that maintains an atomic rate limit counter.
|
|
8
|
+
*
|
|
9
|
+
* Each unique rate limit key maps to one DO instance via `idFromName(key)`.
|
|
10
|
+
* The counter auto-expires using the DO alarm API.
|
|
11
|
+
*
|
|
12
|
+
* **Consumer setup**: Export this class from your Worker entry point and
|
|
13
|
+
* reference it in `wrangler.jsonc`:
|
|
14
|
+
*
|
|
15
|
+
* ```jsonc
|
|
16
|
+
* {
|
|
17
|
+
* "durable_objects": {
|
|
18
|
+
* "bindings": [
|
|
19
|
+
* {
|
|
20
|
+
* "name": "RATE_LIMITER",
|
|
21
|
+
* "class_name": "RateLimiterDO"
|
|
22
|
+
* }
|
|
23
|
+
* ]
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* // worker entry
|
|
30
|
+
* export { RateLimiterDO } from "@vivero/stoma/adapters";
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
declare class RateLimiterDO implements DurableObject {
|
|
34
|
+
private state;
|
|
35
|
+
constructor(state: DurableObjectState);
|
|
36
|
+
fetch(request: Request): Promise<Response>;
|
|
37
|
+
alarm(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Rate limit store backed by a Durable Object.
|
|
41
|
+
*
|
|
42
|
+
* Each rate limit key maps to a unique DO instance, providing strongly
|
|
43
|
+
* consistent atomic counters that survive Worker eviction and work
|
|
44
|
+
* across isolates.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { DurableObjectRateLimitStore } from "@vivero/stoma/adapters";
|
|
49
|
+
*
|
|
50
|
+
* const store = new DurableObjectRateLimitStore(env.RATE_LIMITER);
|
|
51
|
+
* rateLimit({ max: 100, store });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare class DurableObjectRateLimitStore implements RateLimitStore {
|
|
55
|
+
private namespace;
|
|
56
|
+
constructor(namespace: DurableObjectNamespace);
|
|
57
|
+
increment(key: string, windowSeconds: number): Promise<{
|
|
58
|
+
count: number;
|
|
59
|
+
resetAt: number;
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { DurableObjectRateLimitStore, RateLimiterDO };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
class RateLimiterDO {
|
|
2
|
+
state;
|
|
3
|
+
constructor(state) {
|
|
4
|
+
this.state = state;
|
|
5
|
+
}
|
|
6
|
+
async fetch(request) {
|
|
7
|
+
const url = new URL(request.url);
|
|
8
|
+
const windowSeconds = Number(url.searchParams.get("window") ?? "60");
|
|
9
|
+
return this.state.blockConcurrencyWhile(async () => {
|
|
10
|
+
const now = Date.now();
|
|
11
|
+
const stored = await this.state.storage.get("counter");
|
|
12
|
+
if (stored && stored.resetAt > now) {
|
|
13
|
+
const updated = { count: stored.count + 1, resetAt: stored.resetAt };
|
|
14
|
+
await this.state.storage.put("counter", updated);
|
|
15
|
+
return Response.json(updated);
|
|
16
|
+
}
|
|
17
|
+
const resetAt = now + windowSeconds * 1e3;
|
|
18
|
+
const entry = { count: 1, resetAt };
|
|
19
|
+
await this.state.storage.put("counter", entry);
|
|
20
|
+
await this.state.storage.setAlarm(resetAt);
|
|
21
|
+
return Response.json(entry);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async alarm() {
|
|
25
|
+
await this.state.storage.delete("counter");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const DO_ORIGIN = "https://rate-limiter.internal";
|
|
29
|
+
class DurableObjectRateLimitStore {
|
|
30
|
+
constructor(namespace) {
|
|
31
|
+
this.namespace = namespace;
|
|
32
|
+
}
|
|
33
|
+
async increment(key, windowSeconds) {
|
|
34
|
+
const id = this.namespace.idFromName(key);
|
|
35
|
+
const stub = this.namespace.get(id);
|
|
36
|
+
const response = await stub.fetch(
|
|
37
|
+
new Request(`${DO_ORIGIN}/increment?window=${windowSeconds}`)
|
|
38
|
+
);
|
|
39
|
+
return response.json();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
DurableObjectRateLimitStore,
|
|
44
|
+
RateLimiterDO
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=durable-object.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/durable-object.ts"],"sourcesContent":["import type { RateLimitStore } from \"../policies/traffic/rate-limit\";\n\n// ---------------------------------------------------------------------------\n// Durable Object class - exported for the consumer's wrangler.jsonc\n// ---------------------------------------------------------------------------\n\n/**\n * Durable Object that maintains an atomic rate limit counter.\n *\n * Each unique rate limit key maps to one DO instance via `idFromName(key)`.\n * The counter auto-expires using the DO alarm API.\n *\n * **Consumer setup**: Export this class from your Worker entry point and\n * reference it in `wrangler.jsonc`:\n *\n * ```jsonc\n * {\n * \"durable_objects\": {\n * \"bindings\": [\n * {\n * \"name\": \"RATE_LIMITER\",\n * \"class_name\": \"RateLimiterDO\"\n * }\n * ]\n * }\n * }\n * ```\n *\n * ```ts\n * // worker entry\n * export { RateLimiterDO } from \"@vivero/stoma/adapters\";\n * ```\n */\nexport class RateLimiterDO implements DurableObject {\n private state: DurableObjectState;\n\n constructor(state: DurableObjectState) {\n this.state = state;\n }\n\n async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const windowSeconds = Number(url.searchParams.get(\"window\") ?? \"60\");\n\n // Use blockConcurrencyWhile to ensure atomic increments.\n // This serializes all concurrent requests to this DO instance,\n // preventing the race condition where two requests read the same count\n // and both write count+1, losing one increment.\n return this.state.blockConcurrencyWhile(async () => {\n const now = Date.now();\n const stored = (await this.state.storage.get(\"counter\")) as {\n count: number;\n resetAt: number;\n } | null;\n\n if (stored && stored.resetAt > now) {\n // Window still active - increment atomically\n const updated = { count: stored.count + 1, resetAt: stored.resetAt };\n await this.state.storage.put(\"counter\", updated);\n return Response.json(updated);\n }\n\n // Start a new window\n const resetAt = now + windowSeconds * 1000;\n const entry = { count: 1, resetAt };\n await this.state.storage.put(\"counter\", entry);\n // Schedule alarm to clean up expired counter\n await this.state.storage.setAlarm(resetAt);\n return Response.json(entry);\n });\n }\n\n async alarm(): Promise<void> {\n // Clean up expired counter to free storage\n await this.state.storage.delete(\"counter\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// Client store - wraps the DO namespace for use with the rateLimit policy\n// ---------------------------------------------------------------------------\n\nconst DO_ORIGIN = \"https://rate-limiter.internal\";\n\n/**\n * Rate limit store backed by a Durable Object.\n *\n * Each rate limit key maps to a unique DO instance, providing strongly\n * consistent atomic counters that survive Worker eviction and work\n * across isolates.\n *\n * @example\n * ```ts\n * import { DurableObjectRateLimitStore } from \"@vivero/stoma/adapters\";\n *\n * const store = new DurableObjectRateLimitStore(env.RATE_LIMITER);\n * rateLimit({ max: 100, store });\n * ```\n */\nexport class DurableObjectRateLimitStore implements RateLimitStore {\n constructor(private namespace: DurableObjectNamespace) {}\n\n async increment(\n key: string,\n windowSeconds: number\n ): Promise<{ count: number; resetAt: number }> {\n const id = this.namespace.idFromName(key);\n const stub = this.namespace.get(id);\n const response = await stub.fetch(\n new Request(`${DO_ORIGIN}/increment?window=${windowSeconds}`)\n );\n return response.json() as Promise<{ count: number; resetAt: number }>;\n }\n}\n"],"mappings":"AAiCO,MAAM,cAAuC;AAAA,EAC1C;AAAA,EAER,YAAY,OAA2B;AACrC,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,MAAM,MAAM,SAAqC;AAC/C,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,gBAAgB,OAAO,IAAI,aAAa,IAAI,QAAQ,KAAK,IAAI;AAMnE,WAAO,KAAK,MAAM,sBAAsB,YAAY;AAClD,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,SAAU,MAAM,KAAK,MAAM,QAAQ,IAAI,SAAS;AAKtD,UAAI,UAAU,OAAO,UAAU,KAAK;AAElC,cAAM,UAAU,EAAE,OAAO,OAAO,QAAQ,GAAG,SAAS,OAAO,QAAQ;AACnE,cAAM,KAAK,MAAM,QAAQ,IAAI,WAAW,OAAO;AAC/C,eAAO,SAAS,KAAK,OAAO;AAAA,MAC9B;AAGA,YAAM,UAAU,MAAM,gBAAgB;AACtC,YAAM,QAAQ,EAAE,OAAO,GAAG,QAAQ;AAClC,YAAM,KAAK,MAAM,QAAQ,IAAI,WAAW,KAAK;AAE7C,YAAM,KAAK,MAAM,QAAQ,SAAS,OAAO;AACzC,aAAO,SAAS,KAAK,KAAK;AAAA,IAC5B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAuB;AAE3B,UAAM,KAAK,MAAM,QAAQ,OAAO,SAAS;AAAA,EAC3C;AACF;AAMA,MAAM,YAAY;AAiBX,MAAM,4BAAsD;AAAA,EACjE,YAAoB,WAAmC;AAAnC;AAAA,EAAoC;AAAA,EAExD,MAAM,UACJ,KACA,eAC6C;AAC7C,UAAM,KAAK,KAAK,UAAU,WAAW,GAAG;AACxC,UAAM,OAAO,KAAK,UAAU,IAAI,EAAE;AAClC,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B,IAAI,QAAQ,GAAG,SAAS,qBAAqB,aAAa,EAAE;AAAA,IAC9D;AACA,WAAO,SAAS,KAAK;AAAA,EACvB;AACF;","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { bunAdapter } from './bun.js';
|
|
2
|
+
export { CacheApiCacheStore, CloudflareAdapterBindings, KVRateLimitStore, cloudflareAdapter } from './cloudflare.js';
|
|
3
|
+
export { denoAdapter } from './deno.js';
|
|
4
|
+
export { DurableObjectRateLimitStore, RateLimiterDO } from './durable-object.js';
|
|
5
|
+
export { memoryAdapter } from './memory.js';
|
|
6
|
+
export { nodeAdapter } from './node.js';
|
|
7
|
+
export { POSTGRES_SCHEMA_SQL, PostgresAdapterConfig, PostgresCacheStore, PostgresCircuitBreakerStore, PostgresClient, PostgresRateLimitStore, postgresAdapter } from './postgres.js';
|
|
8
|
+
export { RedisAdapterConfig, RedisCacheStore, RedisCircuitBreakerStore, RedisClient, RedisRateLimitStore, redisAdapter } from './redis.js';
|
|
9
|
+
export { TestAdapter, createTestAdapter } from './testing.js';
|
|
10
|
+
export { G as GatewayAdapter } from '../protocol-2fD3DJrL.js';
|
|
11
|
+
import 'hono';
|
|
12
|
+
import '../policies/sdk/trace.js';
|
|
13
|
+
import '@vivero/stoma-core';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { bunAdapter } from "./bun";
|
|
2
|
+
import {
|
|
3
|
+
CacheApiCacheStore,
|
|
4
|
+
cloudflareAdapter,
|
|
5
|
+
KVRateLimitStore
|
|
6
|
+
} from "./cloudflare";
|
|
7
|
+
import { denoAdapter } from "./deno";
|
|
8
|
+
import {
|
|
9
|
+
DurableObjectRateLimitStore,
|
|
10
|
+
RateLimiterDO
|
|
11
|
+
} from "./durable-object";
|
|
12
|
+
import { memoryAdapter } from "./memory";
|
|
13
|
+
import { nodeAdapter } from "./node";
|
|
14
|
+
import {
|
|
15
|
+
POSTGRES_SCHEMA_SQL,
|
|
16
|
+
PostgresCacheStore,
|
|
17
|
+
PostgresCircuitBreakerStore,
|
|
18
|
+
PostgresRateLimitStore,
|
|
19
|
+
postgresAdapter
|
|
20
|
+
} from "./postgres";
|
|
21
|
+
import {
|
|
22
|
+
RedisCacheStore,
|
|
23
|
+
RedisCircuitBreakerStore,
|
|
24
|
+
RedisRateLimitStore,
|
|
25
|
+
redisAdapter
|
|
26
|
+
} from "./redis";
|
|
27
|
+
import {
|
|
28
|
+
createTestAdapter,
|
|
29
|
+
TestAdapter
|
|
30
|
+
} from "./testing";
|
|
31
|
+
export {
|
|
32
|
+
CacheApiCacheStore,
|
|
33
|
+
DurableObjectRateLimitStore,
|
|
34
|
+
KVRateLimitStore,
|
|
35
|
+
POSTGRES_SCHEMA_SQL,
|
|
36
|
+
PostgresCacheStore,
|
|
37
|
+
PostgresCircuitBreakerStore,
|
|
38
|
+
PostgresRateLimitStore,
|
|
39
|
+
RateLimiterDO,
|
|
40
|
+
RedisCacheStore,
|
|
41
|
+
RedisCircuitBreakerStore,
|
|
42
|
+
RedisRateLimitStore,
|
|
43
|
+
TestAdapter,
|
|
44
|
+
bunAdapter,
|
|
45
|
+
cloudflareAdapter,
|
|
46
|
+
createTestAdapter,
|
|
47
|
+
denoAdapter,
|
|
48
|
+
memoryAdapter,
|
|
49
|
+
nodeAdapter,
|
|
50
|
+
postgresAdapter,
|
|
51
|
+
redisAdapter
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/index.ts"],"sourcesContent":["/**\n * Runtime adapters for stoma - plug in platform-specific stores and capabilities.\n *\n * Stoma runs on any runtime that supports Hono (Cloudflare Workers, Deno, Bun,\n * Node.js, and more). Adapters provide the bridge between the gateway's\n * pluggable store interfaces (rate limiting, circuit breaker, cache) and each\n * runtime's native APIs.\n *\n * | Adapter | Rate Limit Store | Circuit Breaker Store | Cache Store | `waitUntil` | `dispatchBinding` |\n * |----------------------|--------------------------|-----------------------|-------------------|-------------|-------------------|\n * | `cloudflareAdapter` | KV or Durable Objects | In-memory | Cache API | Yes | Yes |\n * | `redisAdapter` | Redis (Lua) | Redis (JSON) | Redis (JSON) | Optional | No |\n * | `postgresAdapter` | PostgreSQL (upsert) | PostgreSQL (upsert) | PostgreSQL | Optional | No |\n * | `memoryAdapter` | In-memory | In-memory | In-memory | No | No |\n * | `denoAdapter` | (use memoryAdapter) | (use memoryAdapter) | (use memoryAdapter)| No | No |\n * | `bunAdapter` | (use memoryAdapter) | (use memoryAdapter) | (use memoryAdapter)| No | No |\n * | `nodeAdapter` | (use memoryAdapter) | (use memoryAdapter) | (use memoryAdapter)| No | No |\n *\n * For testing, use `createTestAdapter()` which collects `waitUntil` promises\n * so tests can `await adapter.waitAll()` before asserting.\n *\n * @example\n * ```ts\n * // Cloudflare Workers\n * import { cloudflareAdapter } from \"@vivero/stoma/adapters\";\n * createGateway({ adapter: cloudflareAdapter({ rateLimitKv: env.RATE_LIMIT, executionCtx: ctx }), ... });\n *\n * // Redis (Node.js / Bun / Deno)\n * import { redisAdapter } from \"@vivero/stoma/adapters\";\n * createGateway({ adapter: redisAdapter({ client: redis }), ... });\n *\n * // PostgreSQL (Node.js / Bun / Deno)\n * import { postgresAdapter } from \"@vivero/stoma/adapters\";\n * createGateway({ adapter: postgresAdapter({ client: pool }), ... });\n *\n * // Development / testing\n * import { memoryAdapter } from \"@vivero/stoma/adapters\";\n * createGateway({ adapter: memoryAdapter(), ... });\n * ```\n *\n * @module adapters\n */\n\n/** Create an adapter for Bun - marker/extension point for Bun-specific capabilities. */\nexport { bunAdapter } from \"./bun\";\n/** Bindings accepted by `cloudflareAdapter()` - KV, DO, Cache, ExecutionContext, and env. */\nexport type { CloudflareAdapterBindings } from \"./cloudflare\";\n/** Create a Cloudflare-native adapter with KV/DO rate limiting, Cache API caching, waitUntil, and service bindings. */\nexport {\n /** Response cache backed by the Cloudflare Cache API. */\n CacheApiCacheStore,\n cloudflareAdapter,\n /** Rate limit store backed by Cloudflare Workers KV (eventually consistent). */\n KVRateLimitStore,\n} from \"./cloudflare\";\n/** Create an adapter for Deno Deploy - marker/extension point for Deno-specific capabilities. */\nexport { denoAdapter } from \"./deno\";\n/** Durable Object class for atomic rate limit counters - export from your Worker entry and reference in wrangler.jsonc. */\nexport {\n /** Rate limit store backed by Cloudflare Durable Objects (strongly consistent). */\n DurableObjectRateLimitStore,\n RateLimiterDO,\n} from \"./durable-object\";\n/** Create an adapter using in-memory stores - suitable for development, demos, and testing. */\nexport { memoryAdapter } from \"./memory\";\n\n/** Create an adapter for Node.js (via `@hono/node-server`) - marker/extension point for Node-specific capabilities. */\nexport { nodeAdapter } from \"./node\";\n/** Config accepted by `postgresAdapter()` - client, table prefix, store toggles. */\nexport type { PostgresAdapterConfig, PostgresClient } from \"./postgres\";\n/** Create a PostgreSQL-backed adapter with upsert-based rate limiting, circuit breaker, and cache stores. */\nexport {\n /** Exported SQL to create the required stoma tables. Run once against your database. */\n POSTGRES_SCHEMA_SQL,\n /** Response cache backed by PostgreSQL with base64-encoded body. */\n PostgresCacheStore,\n /** Circuit breaker state store backed by PostgreSQL. */\n PostgresCircuitBreakerStore,\n /** Rate limit store backed by PostgreSQL using atomic upsert. */\n PostgresRateLimitStore,\n postgresAdapter,\n} from \"./postgres\";\n/** Config accepted by `redisAdapter()` - client, prefix, setWithTTL override, store toggles. */\nexport type { RedisAdapterConfig, RedisClient } from \"./redis\";\n/** Create a Redis-backed adapter with Lua-based rate limiting, JSON circuit breaker, and JSON cache stores. */\nexport {\n /** Response cache backed by Redis with base64-encoded body. */\n RedisCacheStore,\n /** Circuit breaker state store backed by Redis JSON strings. */\n RedisCircuitBreakerStore,\n /** Rate limit store backed by Redis with atomic Lua script. */\n RedisRateLimitStore,\n redisAdapter,\n} from \"./redis\";\n/** Test adapter that collects `waitUntil` promises for assertion in unit tests. */\nexport {\n /** Factory function to create a new TestAdapter instance. */\n createTestAdapter,\n TestAdapter,\n} from \"./testing\";\n/** Runtime adapter interface - bag of optional store implementations and platform capabilities. */\nexport type { GatewayAdapter } from \"./types\";\n"],"mappings":"AA4CA,SAAS,kBAAkB;AAI3B;AAAA,EAEE;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AAEP,SAAS,mBAAmB;AAE5B;AAAA,EAEE;AAAA,EACA;AAAA,OACK;AAEP,SAAS,qBAAqB;AAG9B,SAAS,mBAAmB;AAI5B;AAAA,EAEE;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AAIP;AAAA,EAEE;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EAEE;AAAA,EACA;AAAA,OACK;","names":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { G as GatewayAdapter } from '../protocol-2fD3DJrL.js';
|
|
2
|
+
import 'hono';
|
|
3
|
+
import '../policies/sdk/trace.js';
|
|
4
|
+
import '@vivero/stoma-core';
|
|
5
|
+
|
|
6
|
+
/** Create a GatewayAdapter using in-memory stores. Suitable for dev/demo/testing. */
|
|
7
|
+
declare function memoryAdapter(): GatewayAdapter;
|
|
8
|
+
|
|
9
|
+
export { memoryAdapter };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { InMemoryCircuitBreakerStore } from "../policies/resilience/circuit-breaker";
|
|
2
|
+
import { InMemoryCacheStore } from "../policies/traffic/cache";
|
|
3
|
+
import { InMemoryRateLimitStore } from "../policies/traffic/rate-limit";
|
|
4
|
+
function memoryAdapter() {
|
|
5
|
+
return {
|
|
6
|
+
rateLimitStore: new InMemoryRateLimitStore(),
|
|
7
|
+
circuitBreakerStore: new InMemoryCircuitBreakerStore(),
|
|
8
|
+
cacheStore: new InMemoryCacheStore()
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
memoryAdapter
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/memory.ts"],"sourcesContent":["import { InMemoryCircuitBreakerStore } from \"../policies/resilience/circuit-breaker\";\nimport { InMemoryCacheStore } from \"../policies/traffic/cache\";\nimport { InMemoryRateLimitStore } from \"../policies/traffic/rate-limit\";\nimport type { GatewayAdapter } from \"./types\";\n\n/** Create a GatewayAdapter using in-memory stores. Suitable for dev/demo/testing. */\nexport function memoryAdapter(): GatewayAdapter {\n return {\n rateLimitStore: new InMemoryRateLimitStore(),\n circuitBreakerStore: new InMemoryCircuitBreakerStore(),\n cacheStore: new InMemoryCacheStore(),\n };\n}\n"],"mappings":"AAAA,SAAS,mCAAmC;AAC5C,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAIhC,SAAS,gBAAgC;AAC9C,SAAO;AAAA,IACL,gBAAgB,IAAI,uBAAuB;AAAA,IAC3C,qBAAqB,IAAI,4BAA4B;AAAA,IACrD,YAAY,IAAI,mBAAmB;AAAA,EACrC;AACF;","names":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { G as GatewayAdapter } from '../protocol-2fD3DJrL.js';
|
|
2
|
+
import 'hono';
|
|
3
|
+
import '../policies/sdk/trace.js';
|
|
4
|
+
import '@vivero/stoma-core';
|
|
5
|
+
|
|
6
|
+
/** Create a GatewayAdapter for Node.js. Delegates to memoryAdapter() for in-memory stores. */
|
|
7
|
+
declare function nodeAdapter(): GatewayAdapter;
|
|
8
|
+
|
|
9
|
+
export { nodeAdapter };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/node.ts"],"sourcesContent":["/**\n * Node.js adapter for stoma.\n *\n * Node.js (via `@hono/node-server`) doesn't provide `waitUntil` or\n * service bindings natively. Use `memoryAdapter()` for stores and this\n * adapter as a marker/extension point for Node-specific capabilities.\n *\n * @example\n * ```ts\n * import { createGateway } from \"@vivero/stoma\";\n * import { nodeAdapter } from \"@vivero/stoma/adapters/node\";\n * import { serve } from \"@hono/node-server\";\n *\n * const gateway = createGateway({\n * adapter: nodeAdapter(),\n * routes: [...]\n * });\n *\n * serve(gateway.app);\n * ```\n *\n * @module node-adapter\n */\nimport { memoryAdapter } from \"./memory\";\nimport type { GatewayAdapter } from \"./types\";\n\n/** Create a GatewayAdapter for Node.js. Delegates to memoryAdapter() for in-memory stores. */\nexport function nodeAdapter(): GatewayAdapter {\n return memoryAdapter();\n}\n"],"mappings":"AAuBA,SAAS,qBAAqB;AAIvB,SAAS,cAA8B;AAC5C,SAAO,cAAc;AACvB;","names":[]}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { C as CacheStore, b as CircuitBreakerStore, a as CircuitBreakerSnapshot, c as CircuitState, R as RateLimitStore, G as GatewayAdapter } from '../protocol-2fD3DJrL.js';
|
|
2
|
+
import 'hono';
|
|
3
|
+
import '../policies/sdk/trace.js';
|
|
4
|
+
import '@vivero/stoma-core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PostgreSQL-backed adapter for stoma - production-ready stores for Node.js, Bun, and Deno.
|
|
8
|
+
*
|
|
9
|
+
* Zero dependencies: define a minimal {@link PostgresClient} interface that any Postgres
|
|
10
|
+
* library (pg, postgres.js, etc.) satisfies, then pass it to {@link postgresAdapter}.
|
|
11
|
+
*
|
|
12
|
+
* @module adapters/postgres
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Minimal PostgreSQL client interface - satisfied by `pg`, `postgres.js`, and most
|
|
17
|
+
* Postgres libraries. Only a single `query` method is required.
|
|
18
|
+
*/
|
|
19
|
+
interface PostgresClient {
|
|
20
|
+
query(text: string, params?: unknown[]): Promise<{
|
|
21
|
+
rows: Record<string, unknown>[];
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
interface PostgresAdapterConfig {
|
|
25
|
+
/** PostgreSQL client instance (pg Pool, postgres.js, etc.). */
|
|
26
|
+
client: PostgresClient;
|
|
27
|
+
/** Table name prefix. Default: `"stoma_"`. */
|
|
28
|
+
tablePrefix?: string;
|
|
29
|
+
/** Selectively enable/disable individual stores. All enabled by default. */
|
|
30
|
+
stores?: {
|
|
31
|
+
rateLimit?: boolean;
|
|
32
|
+
circuitBreaker?: boolean;
|
|
33
|
+
cache?: boolean;
|
|
34
|
+
};
|
|
35
|
+
/** Schedule background work that outlives the response. */
|
|
36
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* SQL to create the three stoma tables. Run this once against your database
|
|
40
|
+
* before using the Postgres adapter. All statements use `IF NOT EXISTS` so
|
|
41
|
+
* they're safe to re-run.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { POSTGRES_SCHEMA_SQL } from "@vivero/stoma/adapters/postgres";
|
|
46
|
+
* await pool.query(POSTGRES_SCHEMA_SQL);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare const POSTGRES_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS stoma_rate_limits (\n key TEXT PRIMARY KEY,\n count INTEGER NOT NULL DEFAULT 0,\n reset_at BIGINT NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_stoma_rate_limits_reset_at ON stoma_rate_limits (reset_at);\n\nCREATE TABLE IF NOT EXISTS stoma_circuit_breakers (\n key TEXT PRIMARY KEY,\n state TEXT NOT NULL DEFAULT 'closed',\n failure_count INTEGER NOT NULL DEFAULT 0,\n success_count INTEGER NOT NULL DEFAULT 0,\n last_failure_time BIGINT NOT NULL DEFAULT 0,\n last_state_change BIGINT NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS stoma_cache (\n key TEXT PRIMARY KEY,\n status INTEGER NOT NULL,\n headers JSONB NOT NULL DEFAULT '[]',\n body TEXT NOT NULL DEFAULT '',\n expires_at BIGINT NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_stoma_cache_expires_at ON stoma_cache (expires_at);\n";
|
|
50
|
+
/**
|
|
51
|
+
* Rate limit store backed by PostgreSQL using atomic upsert.
|
|
52
|
+
*
|
|
53
|
+
* Uses `INSERT ... ON CONFLICT DO UPDATE` with a `CASE` expression to
|
|
54
|
+
* atomically reset or increment the counter in a single query.
|
|
55
|
+
*/
|
|
56
|
+
declare class PostgresRateLimitStore implements RateLimitStore {
|
|
57
|
+
private client;
|
|
58
|
+
private table;
|
|
59
|
+
constructor(client: PostgresClient, table: string);
|
|
60
|
+
increment(key: string, windowSeconds: number): Promise<{
|
|
61
|
+
count: number;
|
|
62
|
+
resetAt: number;
|
|
63
|
+
}>;
|
|
64
|
+
/** Remove expired entries. Call periodically (e.g. via cron or waitUntil). */
|
|
65
|
+
cleanup(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
/** Circuit breaker state store backed by PostgreSQL. */
|
|
68
|
+
declare class PostgresCircuitBreakerStore implements CircuitBreakerStore {
|
|
69
|
+
private client;
|
|
70
|
+
private table;
|
|
71
|
+
constructor(client: PostgresClient, table: string);
|
|
72
|
+
getState(key: string): Promise<CircuitBreakerSnapshot>;
|
|
73
|
+
recordSuccess(key: string): Promise<CircuitBreakerSnapshot>;
|
|
74
|
+
recordFailure(key: string): Promise<CircuitBreakerSnapshot>;
|
|
75
|
+
transition(key: string, to: CircuitState): Promise<CircuitBreakerSnapshot>;
|
|
76
|
+
reset(key: string): Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
/** Response cache backed by PostgreSQL with base64-encoded body and expiry timestamp. */
|
|
79
|
+
declare class PostgresCacheStore implements CacheStore {
|
|
80
|
+
private client;
|
|
81
|
+
private table;
|
|
82
|
+
constructor(client: PostgresClient, table: string);
|
|
83
|
+
get(key: string): Promise<Response | null>;
|
|
84
|
+
put(key: string, response: Response, ttlSeconds: number): Promise<void>;
|
|
85
|
+
delete(key: string): Promise<boolean>;
|
|
86
|
+
/** Remove expired entries. Call periodically (e.g. via cron or waitUntil). */
|
|
87
|
+
cleanup(): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Create a {@link GatewayAdapter} using PostgreSQL-backed stores.
|
|
91
|
+
*
|
|
92
|
+
* Before using, create the required tables by running {@link POSTGRES_SCHEMA_SQL}
|
|
93
|
+
* against your database.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* import { Pool } from "pg";
|
|
98
|
+
* import { postgresAdapter, POSTGRES_SCHEMA_SQL } from "@vivero/stoma/adapters/postgres";
|
|
99
|
+
*
|
|
100
|
+
* const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
101
|
+
* await pool.query(POSTGRES_SCHEMA_SQL); // one-time setup
|
|
102
|
+
*
|
|
103
|
+
* const adapter = postgresAdapter({ client: pool });
|
|
104
|
+
* createGateway({ adapter, ... });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function postgresAdapter(config: PostgresAdapterConfig): GatewayAdapter;
|
|
108
|
+
|
|
109
|
+
export { POSTGRES_SCHEMA_SQL, type PostgresAdapterConfig, PostgresCacheStore, PostgresCircuitBreakerStore, type PostgresClient, PostgresRateLimitStore, postgresAdapter };
|