cfw-graphql-bootstrap 1.0.0 → 1.0.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 CHANGED
@@ -6,7 +6,7 @@ Enterprise-grade GraphQL server framework for Cloudflare Workers with built-in s
6
6
 
7
7
  - 🚀 **Optimized for Cloudflare Workers** - Built specifically for edge runtime with minimal bundle size
8
8
  - 🔒 **Enterprise Security** - HMAC signature verification, CORS, rate limiting, and security headers
9
- - 💾 **Multi-tier Caching** - Support for Cloudflare KV and Redis with cache-aside pattern
9
+ - 💾 **Built-in Caching** - Native Cloudflare KV support with cache-aside pattern
10
10
  - 📊 **Observability** - Distributed tracing, structured logging with Pino, and health checks
11
11
  - 🛡️ **Type Safety** - Full TypeScript support with proper type exports
12
12
  - ⚡ **High Performance** - Lightweight environment parsing, efficient middleware chain
@@ -56,15 +56,20 @@ const schema = createSchemaModule({
56
56
  },
57
57
  });
58
58
 
59
- // Create the server
59
+ // Create the server (cache is automatically configured)
60
60
  const server = createGraphQLServer({
61
61
  name: 'my-graphql-api',
62
62
  version: '1.0.0',
63
63
  config: {
64
64
  apollo: {
65
65
  schema: schema.schema,
66
+ // Cache is automatically configured from KV binding
66
67
  },
67
- datasources: (cache, context) => ({}),
68
+ // Optional: Define data sources (cache is provided)
69
+ datasources: (cache, context) => ({
70
+ // cache is Apollo's KeyValueCache<string> from KV
71
+ users: new UserDataSource(cache),
72
+ }),
68
73
  },
69
74
  });
70
75
 
@@ -105,28 +110,78 @@ const server = createGraphQLServer<any, MyContext>({
105
110
 
106
111
  ### Caching
107
112
 
108
- Enable KV caching by binding a KV namespace:
113
+ The framework **requires** a KV namespace binding named `KV` for caching. The framework provides two cache interfaces that both use this KV storage:
114
+
115
+ 1. **Apollo's KeyValueCache** - Automatically configured for Apollo Server features (response caching, APQ)
116
+ 2. **KVCache** - Available in `context.cache` for application-level caching with enhanced features
117
+
118
+ **Required Configuration:**
109
119
 
110
120
  ```toml
111
- # wrangler.toml
121
+ # wrangler.toml (REQUIRED)
112
122
  [[kv_namespaces]]
113
- binding = "CACHE_KV"
123
+ binding = "KV" # Must be named "KV"
114
124
  id = "your-kv-namespace-id"
115
125
  ```
116
126
 
117
- Use the cache decorator in resolvers:
127
+ If the KV binding is missing, the server will throw an error on startup.
118
128
 
119
129
  ```typescript
130
+ // 1. Apollo cache is automatically configured from KV binding
131
+ // No need to manually pass cache!
132
+
133
+ // 2. Application cache in resolvers (context.cache is KVCache)
134
+ const resolvers = {
135
+ Query: {
136
+ user: async (_, {id}, context) => {
137
+ // Use KVCache's memoize for auto key generation
138
+ return context.cache.memoize('user', {id}, () => fetchUserFromDB(id), {
139
+ ttl: 300,
140
+ });
141
+ },
142
+ },
143
+ };
144
+
145
+ // 3. Cache decorator for class-based resolvers
120
146
  import {cached} from 'cfw-graphql-bootstrap';
121
147
 
122
148
  class UserResolver {
123
- @cached({ttl: 300}) // Cache for 5 minutes
149
+ @cached({ttl: 300}) // Uses KVCache
124
150
  async getUser(_, {id}, context) {
125
151
  return fetchUserFromDB(id);
126
152
  }
127
153
  }
128
154
  ```
129
155
 
156
+ ### Batching Data Sources
157
+
158
+ Create efficient batched data sources with automatic caching:
159
+
160
+ ```typescript
161
+ import {BatchingDataSource, ensureBatchOrder} from 'cfw-graphql-bootstrap';
162
+
163
+ export class UserDataSource extends BatchingDataSource<
164
+ BaseContext,
165
+ string,
166
+ User
167
+ > {
168
+ constructor() {
169
+ super(
170
+ async (ids: readonly string[]) => {
171
+ const users = await fetchUsersFromDB(ids);
172
+ return ensureBatchOrder(ids, users, 'id');
173
+ },
174
+ {cache: true}, // DataLoader options
175
+ 'users' // KV cache namespace
176
+ );
177
+ }
178
+ }
179
+
180
+ // In your resolver
181
+ const user = await context.dataSources.users.load(userId);
182
+ const users = await context.dataSources.users.loadMany([id1, id2, id3]);
183
+ ```
184
+
130
185
  ### Environment Configuration
131
186
 
132
187
  The framework automatically validates and parses environment variables:
@@ -201,12 +256,18 @@ Built-in security features:
201
256
  - `envLoader` - Environment configuration loader
202
257
  - `EnvConfig` - Environment configuration type
203
258
 
204
- ### Cache
259
+ ### Cache & Data Loading
205
260
 
206
- - `createCache(env)` - Create cache instance
207
- - `KVCache` - Cloudflare KV cache implementation
208
- - `RedisCache` - Redis cache implementation
209
- - `@cached(options)` - Cache decorator
261
+ - `createCache(env)` - Create KV cache instance
262
+ - `KVCache` - Cloudflare KV cache implementation with memoization
263
+ - `ApolloKVAdapter` - Apollo KeyValueCache adapter for KV
264
+ - `createApolloCache(kv)` - Create Apollo-compatible cache
265
+ - `InMemoryCache` - Simple in-memory cache for development
266
+ - `@cached(options)` - Cache decorator for resolver methods
267
+ - `CachedDataLoader` - DataLoader with caching support
268
+ - `BatchingDataSource` - Base class for batched data fetching
269
+ - `createBatchingDataSource` - Factory for batching data sources
270
+ - `ensureBatchOrder` - Helper to maintain batch order
210
271
 
211
272
  ### Middleware
212
273
 
@@ -240,7 +301,7 @@ vars = {
240
301
  }
241
302
 
242
303
  [[kv_namespaces]]
243
- binding = "CACHE_KV"
304
+ binding = "KV"
244
305
  id = "your-kv-namespace-id"
245
306
 
246
307
  [[kv_namespaces]]
@@ -260,7 +321,7 @@ wrangler deploy --env production
260
321
 
261
322
  ## Performance
262
323
 
263
- - **Bundle Size**: ~30KB (ESM, before compression)
324
+ - **Bundle Size**: ~32KB (ESM, before compression)
264
325
  - **Cold Start**: < 10ms typical
265
326
  - **Request Overhead**: < 2ms for middleware chain
266
327
  - **Memory Usage**: Optimized for Workers' 128MB limit
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { BaseContext as BaseContext$1, ApolloServerOptions, ApolloServerPlugin } from '@apollo/server';
2
- import { KVNamespace, ExecutionContext } from '@cloudflare/workers-types';
2
+ import { ExecutionContext } from '@cloudflare/workers-types';
3
3
  import { CloudflareContextFunctionArgument } from '@as-integrations/cloudflare-workers';
4
+ import { KeyValueCache, KeyValueCacheSetOptions } from '@apollo/utils.keyvaluecache';
4
5
  import { GraphQLSchema, GraphQLError, DocumentNode } from 'graphql';
5
6
  import { Hono, MiddlewareHandler } from 'hono';
6
7
  import { ApolloServerErrorCode } from '@apollo/server/errors';
8
+ import DataLoader, { BatchLoadFn, Options } from 'dataloader';
7
9
  export { gql } from 'graphql-tag';
8
10
 
9
11
  declare const logger: any;
@@ -29,20 +31,22 @@ interface ContextUser {
29
31
  * KV Cache implementation for Cloudflare Workers
30
32
  * Provides caching capabilities for GraphQL resolvers using Cloudflare KV
31
33
  */
32
-
33
34
  interface CacheOptions {
34
35
  ttl?: number;
36
+ namespace?: string;
35
37
  cacheKey?: string;
36
38
  }
37
39
  declare class KVCache {
38
40
  private kv;
39
- constructor(kv: KVNamespace);
41
+ private defaultTTL;
42
+ private defaultNamespace;
43
+ constructor(kv: KVNamespace, defaultNamespace?: string);
40
44
  /**
41
45
  * Get a value from cache
42
46
  */
43
47
  get<T = any>(key: string): Promise<T | null>;
44
48
  /**
45
- * Set a value in cache
49
+ * Set a value in cache with metadata
46
50
  */
47
51
  set(key: string, value: any, options?: CacheOptions): Promise<void>;
48
52
  /**
@@ -56,47 +60,205 @@ declare class KVCache {
56
60
  /**
57
61
  * Clear multiple keys matching a prefix
58
62
  */
59
- clearPrefix(prefix: string): Promise<void>;
63
+ clearPrefix(prefix: string): Promise<number>;
64
+ /**
65
+ * Invalidate cache by pattern
66
+ */
67
+ invalidate(pattern: string): Promise<number>;
68
+ /**
69
+ * Clear all cache in a namespace
70
+ */
71
+ clearNamespace(namespace?: string): Promise<number>;
60
72
  /**
61
73
  * Get or set a value with a factory function
62
74
  * Useful for cache-aside pattern
63
75
  */
64
76
  getOrSet<T>(key: string, factory: () => Promise<T>, options?: CacheOptions): Promise<T>;
77
+ /**
78
+ * Memoize function with automatic key generation
79
+ */
80
+ memoize<T>(cacheKey: string, params: any, fetchFn: () => Promise<T>, options?: CacheOptions): Promise<T>;
81
+ /**
82
+ * Generate cache key with namespace and params
83
+ */
84
+ private generateKey;
85
+ /**
86
+ * Simple hash function for generating cache keys
87
+ */
88
+ private simpleHash;
65
89
  }
66
90
 
67
91
  /**
68
- * Redis Cache implementation for Cloudflare Workers
69
- * Alternative to KV for more advanced caching needs
92
+ * Ensures that output items has the same order as input keys (dataloader requirement).
93
+ * Returns error objects for non-existent ids.
94
+ *
95
+ * @param keys list of input keys
96
+ * @param items list of output items
97
+ * @param primaryKey name of the key-holding property
98
+ */
99
+ declare function ensureBatchOrder<K extends string | number, T extends Record<string, any>>(keys: readonly K[], items: T[], primaryKey?: string): Array<T | Error>;
100
+ /**
101
+ * Ensures that output items has the same order as input keys (dataloader requirement).
102
+ * Returns null for non-existent ids.
103
+ *
104
+ * @param keys list of input keys
105
+ * @param items list of output items
106
+ * @param primaryKey name of the key-holding property
107
+ */
108
+ declare function ensureBatchOrderNullable<K extends string | number, T extends Record<string, any>>(keys: readonly K[], items: T[], primaryKey?: string): Array<T | null>;
109
+ /**
110
+ * Base class for batching data sources with caching support.
111
+ * Uses DataLoader for request batching and deduplication.
112
+ *
113
+ * @typeParam TContext - Apollo context type
114
+ * @typeParam K - Primary key type
115
+ * @typeParam V - Output item type
116
+ * @typeParam CK - Cache key type (same as K by default)
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * export class UserBatchingDataSource extends BatchingDataSource<BaseContext, string, User> {
121
+ * constructor() {
122
+ * super(async (ids: readonly string[]) => {
123
+ * const users = await fetchUsersFromDB(ids);
124
+ * return ensureBatchOrder(ids, users);
125
+ * });
126
+ * }
127
+ * }
128
+ * ```
70
129
  */
130
+ declare class BatchingDataSource<TContext extends BaseContext, K, V, CK = K> {
131
+ protected cacheNamespace?: string;
132
+ protected context: TContext;
133
+ protected loader: DataLoader<K, V, CK>;
134
+ protected cache?: KVCache;
135
+ /**
136
+ * Creates a new batching data source
137
+ *
138
+ * @param resolveBatch - Batch resolving function
139
+ * @param options - DataLoader options
140
+ * @param cacheNamespace - Optional namespace for KV caching
141
+ */
142
+ constructor(resolveBatch: BatchLoadFn<K, V>, options?: Options<K, V, CK>, cacheNamespace?: string);
143
+ /**
144
+ * Initialize with Apollo context
145
+ */
146
+ initialize(config: {
147
+ contextValue: TContext;
148
+ }): void;
149
+ /**
150
+ * Load a single item by key
151
+ */
152
+ load(id: K): Promise<V>;
153
+ /**
154
+ * Load multiple items by keys
155
+ */
156
+ loadMany(ids: readonly K[]): Promise<Array<V | Error>>;
157
+ /**
158
+ * Clear a single item from the DataLoader cache
159
+ */
160
+ clear(id: K): this;
161
+ /**
162
+ * Clear all items from the DataLoader cache
163
+ */
164
+ clearAll(): this;
165
+ /**
166
+ * Prime the cache with a specific value
167
+ */
168
+ prime(key: K, value: V): this;
169
+ }
170
+ /**
171
+ * Factory function for creating batching data sources
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * const userDataSource = createBatchingDataSource<BaseContext, string, User>(
176
+ * async (ids) => {
177
+ * const users = await fetchUsersFromDB(ids);
178
+ * return ensureBatchOrder(ids, users);
179
+ * },
180
+ * { cache: true },
181
+ * 'users'
182
+ * );
183
+ * ```
184
+ */
185
+ declare function createBatchingDataSource<TContext extends BaseContext, K, V, CK = K>(resolveBatch: BatchLoadFn<K, V>, options?: Options<K, V, CK>, cacheNamespace?: string): BatchingDataSource<TContext, K, V, CK>;
71
186
 
72
- declare class RedisCache {
73
- private redis;
74
- constructor(config?: {
75
- url?: string;
76
- token?: string;
77
- });
78
- private initUpstash;
79
- private createMockRedis;
80
- get<T = any>(key: string): Promise<T | null>;
81
- set(key: string, value: any, options?: CacheOptions): Promise<void>;
82
- delete(key: string): Promise<void>;
83
- has(key: string): Promise<boolean>;
84
- clearPrefix(prefix: string): Promise<void>;
85
- getOrSet<T>(key: string, factory: () => Promise<T>, options?: CacheOptions): Promise<T>;
86
- increment(key: string, by?: number): Promise<number>;
87
- expire(key: string, seconds: number): Promise<boolean>;
88
- ttl(key: string): Promise<number>;
187
+ /**
188
+ * Apollo KeyValueCache adapter for Cloudflare KV
189
+ *
190
+ * Implements the Apollo Server KeyValueCache interface to work with
191
+ * Cloudflare Workers KV storage.
192
+ */
193
+
194
+ /**
195
+ * Adapter that makes our KVCache compatible with Apollo's KeyValueCache interface
196
+ *
197
+ * Apollo Server uses this for:
198
+ * - Response caching
199
+ * - APQ (Automatic Persisted Queries)
200
+ * - DataSource caching
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * const apolloCache = new ApolloKVAdapter(new KVCache(env.KV));
205
+ *
206
+ * const server = new ApolloServer({
207
+ * cache: apolloCache,
208
+ * // ... other options
209
+ * });
210
+ * ```
211
+ */
212
+ declare class ApolloKVAdapter implements KeyValueCache<string> {
213
+ private kvCache;
214
+ constructor(kvCache: KVCache);
215
+ /**
216
+ * Get a value from cache
217
+ * Apollo expects undefined for cache misses
218
+ */
219
+ get(key: string): Promise<string | undefined>;
220
+ /**
221
+ * Set a value in cache with TTL support
222
+ * Apollo passes TTL in seconds via options
223
+ */
224
+ set(key: string, value: string, options?: KeyValueCacheSetOptions): Promise<void>;
225
+ /**
226
+ * Delete a value from cache
227
+ * Apollo expects boolean or void return
228
+ */
229
+ delete(key: string): Promise<boolean | void>;
230
+ }
231
+ /**
232
+ * Factory function to create an Apollo-compatible cache from KV namespace
233
+ *
234
+ * @param kv - Cloudflare KV namespace binding
235
+ * @returns Apollo-compatible KeyValueCache
236
+ */
237
+ declare function createApolloCache(kv: KVNamespace): KeyValueCache<string>;
238
+ /**
239
+ * In-memory LRU cache for development/testing
240
+ * Falls back to simple Map-based implementation
241
+ */
242
+ declare class InMemoryCache implements KeyValueCache<string> {
243
+ private cache;
244
+ get(key: string): Promise<string | undefined>;
245
+ set(key: string, value: string, options?: KeyValueCacheSetOptions): Promise<void>;
246
+ delete(key: string): Promise<boolean>;
247
+ /**
248
+ * Clear all cache entries
249
+ */
250
+ clear(): void;
89
251
  }
90
252
 
91
253
  /**
92
254
  * Cache module for GraphQL resolvers
93
- * Provides unified caching interface with multiple backends
255
+ * Provides caching interface for Cloudflare Workers KV
94
256
  */
95
257
 
96
258
  /**
97
- * Cache factory - creates appropriate cache based on environment
259
+ * Cache factory - creates KV cache if available
98
260
  */
99
- declare function createCache(env: any): KVCache | RedisCache | null;
261
+ declare function createCache(env: Env): KVCache | null;
100
262
  /**
101
263
  * Decorator for caching resolver results
102
264
  */
@@ -110,7 +272,7 @@ declare class CachedDataLoader<K, V> {
110
272
  private options;
111
273
  private loader;
112
274
  private cache;
113
- constructor(batchFn: (keys: K[]) => Promise<V[]>, cache: KVCache | RedisCache, options?: CacheOptions);
275
+ constructor(batchFn: (keys: K[]) => Promise<V[]>, cache: KVCache, options?: CacheOptions);
114
276
  load(key: K): Promise<V>;
115
277
  loadMany(keys: K[]): Promise<V[]>;
116
278
  clear(key: K): Promise<void>;
@@ -133,8 +295,6 @@ interface EnvConfig {
133
295
  MAX_QUERY_DEPTH: number;
134
296
  MAX_QUERY_COMPLEXITY: number;
135
297
  INTROSPECTION_ENABLED: boolean;
136
- REDIS_URL?: string;
137
- REDIS_TOKEN?: string;
138
298
  LOG_SERVICE_URL?: string;
139
299
  LOG_SERVICE_TOKEN?: string;
140
300
  }
@@ -185,7 +345,7 @@ type Runner = {
185
345
  type ServerConfig<TDatasource, TContext extends BaseContext> = {
186
346
  path?: string;
187
347
  apollo: Partial<ApolloServerOptions<BaseContext>>;
188
- datasources: (cache: any, context: TContext) => TDatasource;
348
+ datasources?: (cache: KeyValueCache<string>, context: TContext) => TDatasource;
189
349
  extendContext?: ContextExtendFunction<TContext>;
190
350
  };
191
351
  declare class ServerBuilder<TDatasource, TContext extends BaseContext> {
@@ -275,4 +435,13 @@ type SchemaModule = ReturnType<typeof createSchemaModule>;
275
435
 
276
436
  declare const getNonUserInputErrors: (errors: readonly GraphQLError[]) => GraphQLError[];
277
437
 
278
- export { AppError, type BaseContext, type BaseServerOptions, type CacheOptions, CachedDataLoader, type ContextExtendFunction, type ContextUser, type EnvConfig, ErrorCode, KVCache, RedisCache, type Runner, type SchemaModule, ServerBuilder, type ServerConfig, type ServerOptions, cached, configureHealthChecks, createApolloLoggingPlugin, createCache, createContextFunction, createGraphQLServer, createRateLimitMiddleware, createSchemaModule, createTracingMiddleware, createValidationMiddleware, envLoader, getNonUserInputErrors, logger };
438
+ type DataSources = Record<string, any>;
439
+ type DataSourcesFn = <TContext extends BaseContext = BaseContext>(cache: KeyValueCache<string>, contextValue: TContext) => DataSources;
440
+ type ComputedContext<TDatasource, TContext extends BaseContext> = TContext & {
441
+ datasources: ReturnType<NonNullable<ServerConfig<TDatasource, TContext>['datasources']>>;
442
+ };
443
+ declare const ApolloDataSources: <TDatasource, TContext extends BaseContext>(options: {
444
+ datasources: DataSourcesFn;
445
+ }) => ApolloServerPlugin<ComputedContext<TDatasource, TContext>>;
446
+
447
+ export { ApolloDataSources, ApolloKVAdapter, AppError, type BaseContext, type BaseServerOptions, BatchingDataSource, type CacheOptions, CachedDataLoader, type ComputedContext, type ContextExtendFunction, type ContextUser, type EnvConfig, ErrorCode, InMemoryCache, KVCache, type Runner, type SchemaModule, ServerBuilder, type ServerConfig, type ServerOptions, cached, configureHealthChecks, createApolloCache, createApolloLoggingPlugin, createBatchingDataSource, createCache, createContextFunction, createGraphQLServer, createRateLimitMiddleware, createSchemaModule, createTracingMiddleware, createValidationMiddleware, ensureBatchOrder, ensureBatchOrderNullable, envLoader, getNonUserInputErrors, logger };