cfw-graphql-bootstrap 1.0.0
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 +278 -0
- package/dist/index.d.ts +278 -0
- package/dist/index.js +1052 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
// src/logger/index.ts
|
|
2
|
+
import pino from "pino/browser";
|
|
3
|
+
var isDev = process.env.NODE_ENV !== "production";
|
|
4
|
+
var logLevel = process.env.LOG_LEVEL || (isDev ? "debug" : "info");
|
|
5
|
+
var serializers = {
|
|
6
|
+
err: (err) => {
|
|
7
|
+
if (!err) return err;
|
|
8
|
+
return {
|
|
9
|
+
type: err.constructor?.name || err.name,
|
|
10
|
+
message: err.message,
|
|
11
|
+
stack: err.stack,
|
|
12
|
+
code: err.code,
|
|
13
|
+
statusCode: err.statusCode
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
req: (req) => {
|
|
17
|
+
if (!req) return req;
|
|
18
|
+
return {
|
|
19
|
+
method: req.method,
|
|
20
|
+
url: req.url,
|
|
21
|
+
headers: Object.fromEntries(req.headers.entries())
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
res: (res) => {
|
|
25
|
+
if (!res) return res;
|
|
26
|
+
return {
|
|
27
|
+
statusCode: res.status,
|
|
28
|
+
headers: Object.fromEntries(res.headers.entries())
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var logger = pino({
|
|
33
|
+
browser: {
|
|
34
|
+
asObject: true,
|
|
35
|
+
write: {
|
|
36
|
+
info: (o) => {
|
|
37
|
+
if (!isDev && globalThis.logshipper) {
|
|
38
|
+
globalThis.logshipper.log(o);
|
|
39
|
+
}
|
|
40
|
+
console.log(JSON.stringify(o));
|
|
41
|
+
},
|
|
42
|
+
error: (o) => {
|
|
43
|
+
if (!isDev && globalThis.logshipper) {
|
|
44
|
+
globalThis.logshipper.log(o);
|
|
45
|
+
}
|
|
46
|
+
console.error(JSON.stringify(o));
|
|
47
|
+
},
|
|
48
|
+
debug: (o) => {
|
|
49
|
+
if (isDev) {
|
|
50
|
+
console.debug(JSON.stringify(o));
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
warn: (o) => {
|
|
54
|
+
if (!isDev && globalThis.logshipper) {
|
|
55
|
+
globalThis.logshipper.log(o);
|
|
56
|
+
}
|
|
57
|
+
console.warn(JSON.stringify(o));
|
|
58
|
+
},
|
|
59
|
+
fatal: (o) => {
|
|
60
|
+
if (!isDev && globalThis.logshipper) {
|
|
61
|
+
globalThis.logshipper.log(o);
|
|
62
|
+
}
|
|
63
|
+
console.error(JSON.stringify(o));
|
|
64
|
+
},
|
|
65
|
+
trace: (o) => {
|
|
66
|
+
if (isDev) {
|
|
67
|
+
console.trace(JSON.stringify(o));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
level: logLevel,
|
|
73
|
+
base: {
|
|
74
|
+
env: process.env.NODE_ENV,
|
|
75
|
+
service: "graphql-subgraph",
|
|
76
|
+
runtime: "cloudflare-workers"
|
|
77
|
+
},
|
|
78
|
+
serializers,
|
|
79
|
+
timestamp: () => `,"timestamp":"${(/* @__PURE__ */ new Date()).toISOString()}"`,
|
|
80
|
+
formatters: {
|
|
81
|
+
level: (label) => {
|
|
82
|
+
return { level: label.toUpperCase() };
|
|
83
|
+
},
|
|
84
|
+
log: (object) => {
|
|
85
|
+
if (globalThis.requestContext) {
|
|
86
|
+
object.traceId = globalThis.requestContext.traceId;
|
|
87
|
+
object.spanId = globalThis.requestContext.spanId;
|
|
88
|
+
object.userId = globalThis.requestContext.userId;
|
|
89
|
+
}
|
|
90
|
+
return object;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
function createLogger(context) {
|
|
95
|
+
return logger.child(context);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/context/constants.ts
|
|
99
|
+
var Headers = {
|
|
100
|
+
GATEWAY_USER: "x-gateway-user",
|
|
101
|
+
GATEWAY_USER_SIGNATURE: "x-gateway-user-signature",
|
|
102
|
+
AUTH: "authorization",
|
|
103
|
+
CLIENT_ID: "x-client-id"
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// src/context/auth.ts
|
|
107
|
+
async function verifyAndParseUser(encodedUser, signature, secret) {
|
|
108
|
+
if (!encodedUser || !signature) {
|
|
109
|
+
return void 0;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const encoder = new TextEncoder();
|
|
113
|
+
const key = await crypto.subtle.importKey(
|
|
114
|
+
"raw",
|
|
115
|
+
encoder.encode(secret),
|
|
116
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
117
|
+
false,
|
|
118
|
+
["verify"]
|
|
119
|
+
);
|
|
120
|
+
const signatureBuffer = hexToBuffer(signature);
|
|
121
|
+
const dataBuffer = encoder.encode(encodedUser);
|
|
122
|
+
const isValid = await crypto.subtle.verify(
|
|
123
|
+
"HMAC",
|
|
124
|
+
key,
|
|
125
|
+
signatureBuffer,
|
|
126
|
+
dataBuffer
|
|
127
|
+
);
|
|
128
|
+
if (!isValid) {
|
|
129
|
+
console.error("Invalid user signature");
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
const user = JSON.parse(atob(encodedUser));
|
|
133
|
+
return user;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error("Error verifying user:", error);
|
|
136
|
+
return void 0;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function hexToBuffer(hex) {
|
|
140
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
141
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
142
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
143
|
+
}
|
|
144
|
+
return bytes.buffer;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/cache/kv-cache.ts
|
|
148
|
+
var KVCache = class {
|
|
149
|
+
constructor(kv) {
|
|
150
|
+
this.kv = kv;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get a value from cache
|
|
154
|
+
*/
|
|
155
|
+
async get(key) {
|
|
156
|
+
try {
|
|
157
|
+
const value = await this.kv.get(key);
|
|
158
|
+
if (!value) return null;
|
|
159
|
+
try {
|
|
160
|
+
return JSON.parse(value);
|
|
161
|
+
} catch {
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error(`Cache get error for key ${key}:`, error);
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Set a value in cache
|
|
171
|
+
*/
|
|
172
|
+
async set(key, value, options = {}) {
|
|
173
|
+
try {
|
|
174
|
+
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
175
|
+
const kvOptions = {};
|
|
176
|
+
if (options.ttl) {
|
|
177
|
+
kvOptions.expirationTtl = options.ttl;
|
|
178
|
+
}
|
|
179
|
+
await this.kv.put(key, serialized, kvOptions);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error(`Cache set error for key ${key}:`, error);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Delete a value from cache
|
|
186
|
+
*/
|
|
187
|
+
async delete(key) {
|
|
188
|
+
try {
|
|
189
|
+
await this.kv.delete(key);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(`Cache delete error for key ${key}:`, error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Check if a key exists in cache
|
|
196
|
+
*/
|
|
197
|
+
async has(key) {
|
|
198
|
+
try {
|
|
199
|
+
const value = await this.kv.get(key);
|
|
200
|
+
return value !== null;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(`Cache has error for key ${key}:`, error);
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Clear multiple keys matching a prefix
|
|
208
|
+
*/
|
|
209
|
+
async clearPrefix(prefix) {
|
|
210
|
+
try {
|
|
211
|
+
const list = await this.kv.list({ prefix });
|
|
212
|
+
const promises = list.keys.map((key) => this.kv.delete(key.name));
|
|
213
|
+
await Promise.all(promises);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error(`Cache clear prefix error for ${prefix}:`, error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get or set a value with a factory function
|
|
220
|
+
* Useful for cache-aside pattern
|
|
221
|
+
*/
|
|
222
|
+
async getOrSet(key, factory, options = {}) {
|
|
223
|
+
const cached2 = await this.get(key);
|
|
224
|
+
if (cached2 !== null) {
|
|
225
|
+
return cached2;
|
|
226
|
+
}
|
|
227
|
+
const fresh = await factory();
|
|
228
|
+
await this.set(key, fresh, options);
|
|
229
|
+
return fresh;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
function createCacheKey(...parts) {
|
|
233
|
+
return parts.filter((part) => part !== void 0).map((part) => String(part)).join(":");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/cache/redis-cache.ts
|
|
237
|
+
var RedisCache = class {
|
|
238
|
+
// Use Bun.redis or @upstash/redis
|
|
239
|
+
constructor(config = {}) {
|
|
240
|
+
if (config.url && config.token) {
|
|
241
|
+
this.initUpstash({ url: config.url, token: config.token });
|
|
242
|
+
} else if (typeof globalThis !== "undefined" && globalThis.Bun) {
|
|
243
|
+
this.redis = globalThis.Bun.redis;
|
|
244
|
+
} else {
|
|
245
|
+
this.redis = this.createMockRedis();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async initUpstash(config) {
|
|
249
|
+
this.redis = this.createMockRedis();
|
|
250
|
+
}
|
|
251
|
+
createMockRedis() {
|
|
252
|
+
const store = /* @__PURE__ */ new Map();
|
|
253
|
+
return {
|
|
254
|
+
get: async (key) => store.get(key),
|
|
255
|
+
set: async (key, value) => {
|
|
256
|
+
store.set(key, value);
|
|
257
|
+
return "OK";
|
|
258
|
+
},
|
|
259
|
+
setex: async (key, ttl, value) => {
|
|
260
|
+
store.set(key, value);
|
|
261
|
+
return "OK";
|
|
262
|
+
},
|
|
263
|
+
del: async (...keys) => {
|
|
264
|
+
keys.forEach((k) => store.delete(k));
|
|
265
|
+
return keys.length;
|
|
266
|
+
},
|
|
267
|
+
exists: async (key) => store.has(key) ? 1 : 0,
|
|
268
|
+
keys: async (pattern) => {
|
|
269
|
+
const regex = new RegExp(pattern.replace("*", ".*"));
|
|
270
|
+
return Array.from(store.keys()).filter((k) => regex.test(k));
|
|
271
|
+
},
|
|
272
|
+
incrby: async (key, by) => {
|
|
273
|
+
const val = store.get(key) || 0;
|
|
274
|
+
const newVal = val + by;
|
|
275
|
+
store.set(key, newVal);
|
|
276
|
+
return newVal;
|
|
277
|
+
},
|
|
278
|
+
expire: async (key, seconds) => true,
|
|
279
|
+
ttl: async (key) => -1
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
async get(key) {
|
|
283
|
+
try {
|
|
284
|
+
const value = await this.redis.get(key);
|
|
285
|
+
if (!value) return null;
|
|
286
|
+
return value;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error(`Redis get error for key ${key}:`, error);
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async set(key, value, options = {}) {
|
|
293
|
+
try {
|
|
294
|
+
if (options.ttl) {
|
|
295
|
+
await this.redis.setex(key, options.ttl, value);
|
|
296
|
+
} else {
|
|
297
|
+
await this.redis.set(key, value);
|
|
298
|
+
}
|
|
299
|
+
} catch (error) {
|
|
300
|
+
console.error(`Redis set error for key ${key}:`, error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async delete(key) {
|
|
304
|
+
try {
|
|
305
|
+
await this.redis.del(key);
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error(`Redis delete error for key ${key}:`, error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async has(key) {
|
|
311
|
+
try {
|
|
312
|
+
const exists = await this.redis.exists(key);
|
|
313
|
+
return exists === 1;
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error(`Redis has error for key ${key}:`, error);
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async clearPrefix(prefix) {
|
|
320
|
+
try {
|
|
321
|
+
const keys = await this.redis.keys(`${prefix}*`);
|
|
322
|
+
if (keys.length > 0) {
|
|
323
|
+
await this.redis.del(...keys);
|
|
324
|
+
}
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error(`Redis clear prefix error for ${prefix}:`, error);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async getOrSet(key, factory, options = {}) {
|
|
330
|
+
const cached2 = await this.get(key);
|
|
331
|
+
if (cached2 !== null) {
|
|
332
|
+
return cached2;
|
|
333
|
+
}
|
|
334
|
+
const fresh = await factory();
|
|
335
|
+
await this.set(key, fresh, options);
|
|
336
|
+
return fresh;
|
|
337
|
+
}
|
|
338
|
+
// Additional Redis-specific methods
|
|
339
|
+
async increment(key, by = 1) {
|
|
340
|
+
return this.redis.incrby(key, by);
|
|
341
|
+
}
|
|
342
|
+
async expire(key, seconds) {
|
|
343
|
+
return this.redis.expire(key, seconds);
|
|
344
|
+
}
|
|
345
|
+
async ttl(key) {
|
|
346
|
+
return this.redis.ttl(key);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
// src/cache/index.ts
|
|
351
|
+
function createCache(env) {
|
|
352
|
+
if (env.CACHE_KV) {
|
|
353
|
+
return new KVCache(env.CACHE_KV);
|
|
354
|
+
}
|
|
355
|
+
if (env.REDIS_URL && env.REDIS_TOKEN) {
|
|
356
|
+
return new RedisCache({
|
|
357
|
+
url: env.REDIS_URL,
|
|
358
|
+
token: env.REDIS_TOKEN
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
function cached(options = {}) {
|
|
364
|
+
return function(target, propertyKey, descriptor) {
|
|
365
|
+
const originalMethod = descriptor.value;
|
|
366
|
+
descriptor.value = async function(...args) {
|
|
367
|
+
const context = args[2];
|
|
368
|
+
const cache = context?.cache;
|
|
369
|
+
if (!cache) {
|
|
370
|
+
return originalMethod.apply(this, args);
|
|
371
|
+
}
|
|
372
|
+
const key = options.keyGenerator ? options.keyGenerator(...args) : createCacheKey(propertyKey, JSON.stringify(args[1]));
|
|
373
|
+
return cache.getOrSet(
|
|
374
|
+
key,
|
|
375
|
+
() => originalMethod.apply(this, args),
|
|
376
|
+
options
|
|
377
|
+
);
|
|
378
|
+
};
|
|
379
|
+
return descriptor;
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
var CachedDataLoader = class {
|
|
383
|
+
constructor(batchFn, cache, options = {}) {
|
|
384
|
+
this.options = options;
|
|
385
|
+
this.cache = cache;
|
|
386
|
+
}
|
|
387
|
+
async load(key) {
|
|
388
|
+
const cacheKey = createCacheKey("loader", String(key));
|
|
389
|
+
return this.cache.getOrSet(
|
|
390
|
+
cacheKey,
|
|
391
|
+
async () => {
|
|
392
|
+
return this.loader ? this.loader.load(key) : null;
|
|
393
|
+
},
|
|
394
|
+
this.options
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
async loadMany(keys) {
|
|
398
|
+
return Promise.all(keys.map((key) => this.load(key)));
|
|
399
|
+
}
|
|
400
|
+
async clear(key) {
|
|
401
|
+
const cacheKey = createCacheKey("loader", String(key));
|
|
402
|
+
await this.cache.delete(cacheKey);
|
|
403
|
+
}
|
|
404
|
+
async clearAll() {
|
|
405
|
+
await this.cache.clearPrefix("loader:");
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// src/env/config.ts
|
|
410
|
+
var EnvParser = class {
|
|
411
|
+
constructor(env) {
|
|
412
|
+
this.env = env;
|
|
413
|
+
}
|
|
414
|
+
string(key, defaultValue) {
|
|
415
|
+
const value = this.env[key];
|
|
416
|
+
return value !== void 0 && value !== "" ? String(value) : defaultValue;
|
|
417
|
+
}
|
|
418
|
+
int(key, defaultValue) {
|
|
419
|
+
const value = this.env[key];
|
|
420
|
+
if (value === void 0 || value === "") return defaultValue;
|
|
421
|
+
const parsed = parseInt(String(value), 10);
|
|
422
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
423
|
+
}
|
|
424
|
+
bool(key, defaultValue) {
|
|
425
|
+
const value = this.env[key];
|
|
426
|
+
if (value === void 0 || value === "") return defaultValue;
|
|
427
|
+
const str = String(value).toLowerCase();
|
|
428
|
+
return str === "true" || str === "1";
|
|
429
|
+
}
|
|
430
|
+
array(key, defaultValue) {
|
|
431
|
+
const value = this.env[key];
|
|
432
|
+
if (value === void 0 || value === "") return defaultValue;
|
|
433
|
+
if (Array.isArray(value)) return value;
|
|
434
|
+
return String(value).split(",").map((s) => s.trim()).filter(Boolean);
|
|
435
|
+
}
|
|
436
|
+
enum(key, validValues, defaultValue) {
|
|
437
|
+
const value = this.string(key);
|
|
438
|
+
if (value && validValues.includes(value)) {
|
|
439
|
+
return value;
|
|
440
|
+
}
|
|
441
|
+
return defaultValue;
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
var EnvironmentLoader = class {
|
|
445
|
+
constructor() {
|
|
446
|
+
this.config = null;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Load environment from Cloudflare Workers env object
|
|
450
|
+
*/
|
|
451
|
+
load(env) {
|
|
452
|
+
const parser = new EnvParser(env);
|
|
453
|
+
this.config = {
|
|
454
|
+
NODE_ENV: parser.enum(
|
|
455
|
+
"NODE_ENV",
|
|
456
|
+
["development", "test", "production"],
|
|
457
|
+
"production"
|
|
458
|
+
),
|
|
459
|
+
PORT: parser.int("PORT", 3344),
|
|
460
|
+
GATEWAY_SECRET: parser.string("GATEWAY_SECRET"),
|
|
461
|
+
ALLOWED_ORIGINS: parser.array("ALLOWED_ORIGINS", [
|
|
462
|
+
"http://localhost:3000"
|
|
463
|
+
]),
|
|
464
|
+
LOG_LEVEL: parser.enum(
|
|
465
|
+
"LOG_LEVEL",
|
|
466
|
+
["trace", "debug", "info", "warn", "error", "fatal"],
|
|
467
|
+
"info"
|
|
468
|
+
),
|
|
469
|
+
RATE_LIMIT_WINDOW_MS: parser.int("RATE_LIMIT_WINDOW_MS", 6e4),
|
|
470
|
+
RATE_LIMIT_MAX: parser.int("RATE_LIMIT_MAX", 100),
|
|
471
|
+
ENABLE_METRICS: parser.bool("ENABLE_METRICS", true),
|
|
472
|
+
ENABLE_TRACING: parser.bool("ENABLE_TRACING", true),
|
|
473
|
+
MAX_QUERY_DEPTH: parser.int("MAX_QUERY_DEPTH", 10),
|
|
474
|
+
MAX_QUERY_COMPLEXITY: parser.int("MAX_QUERY_COMPLEXITY", 1e3),
|
|
475
|
+
INTROSPECTION_ENABLED: parser.bool("INTROSPECTION_ENABLED", false),
|
|
476
|
+
REDIS_URL: parser.string("REDIS_URL"),
|
|
477
|
+
REDIS_TOKEN: parser.string("REDIS_TOKEN"),
|
|
478
|
+
LOG_SERVICE_URL: parser.string("LOG_SERVICE_URL"),
|
|
479
|
+
LOG_SERVICE_TOKEN: parser.string("LOG_SERVICE_TOKEN")
|
|
480
|
+
};
|
|
481
|
+
return this.config;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Get current configuration
|
|
485
|
+
*/
|
|
486
|
+
getConfig() {
|
|
487
|
+
if (!this.config) {
|
|
488
|
+
throw new Error("Environment not loaded. Call load() first.");
|
|
489
|
+
}
|
|
490
|
+
return this.config;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Helper getters for common checks
|
|
494
|
+
*/
|
|
495
|
+
get isDev() {
|
|
496
|
+
return this.config?.NODE_ENV === "development";
|
|
497
|
+
}
|
|
498
|
+
get isTest() {
|
|
499
|
+
return this.config?.NODE_ENV === "test";
|
|
500
|
+
}
|
|
501
|
+
get isProd() {
|
|
502
|
+
return this.config?.NODE_ENV === "production";
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
var envLoader = new EnvironmentLoader();
|
|
506
|
+
|
|
507
|
+
// src/context/index.ts
|
|
508
|
+
async function createBaseContext(honoContext) {
|
|
509
|
+
const request = honoContext.request;
|
|
510
|
+
const rawEnv = honoContext.env;
|
|
511
|
+
const env = envLoader.load(rawEnv);
|
|
512
|
+
let user;
|
|
513
|
+
const encodedUser = request.headers.get(Headers.GATEWAY_USER);
|
|
514
|
+
if (encodedUser) {
|
|
515
|
+
if (env.GATEWAY_SECRET) {
|
|
516
|
+
const signature = request.headers.get(Headers.GATEWAY_USER_SIGNATURE);
|
|
517
|
+
user = await verifyAndParseUser(
|
|
518
|
+
encodedUser,
|
|
519
|
+
signature,
|
|
520
|
+
env.GATEWAY_SECRET
|
|
521
|
+
);
|
|
522
|
+
} else if (env.NODE_ENV !== "production") {
|
|
523
|
+
try {
|
|
524
|
+
user = JSON.parse(atob(encodedUser));
|
|
525
|
+
} catch (e) {
|
|
526
|
+
console.error("Failed to parse user in dev mode:", e);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const cache = createCache(rawEnv);
|
|
531
|
+
if (cache) {
|
|
532
|
+
logger.info("KV cache initialized for GraphQL resolvers");
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
user,
|
|
536
|
+
logger,
|
|
537
|
+
cache,
|
|
538
|
+
env,
|
|
539
|
+
// Pass validated env config
|
|
540
|
+
executionCtx: honoContext.ctx,
|
|
541
|
+
authorization: request.headers.get(Headers.AUTH) || "",
|
|
542
|
+
clientId: request.headers.get(Headers.CLIENT_ID) || ""
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function createContextFunction(extendContext) {
|
|
546
|
+
if (!extendContext) {
|
|
547
|
+
return (honoContext) => createBaseContext(honoContext);
|
|
548
|
+
}
|
|
549
|
+
return async (honoContext) => {
|
|
550
|
+
const baseContext = await createBaseContext(honoContext);
|
|
551
|
+
return extendContext(baseContext, honoContext);
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/utils/builder.util.ts
|
|
556
|
+
function assertDefined(t, msg) {
|
|
557
|
+
if (t === void 0) {
|
|
558
|
+
throw new Error("Assertion failed when configuring server: " + msg);
|
|
559
|
+
}
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/server/server.builder.ts
|
|
564
|
+
import { ApolloServer } from "@apollo/server";
|
|
565
|
+
import { startServerAndCreateCloudflareWorkersHandler } from "@as-integrations/cloudflare-workers";
|
|
566
|
+
|
|
567
|
+
// src/server/hono.configure.ts
|
|
568
|
+
import { Hono } from "hono";
|
|
569
|
+
import { cors } from "hono/cors";
|
|
570
|
+
import { compress } from "hono/compress";
|
|
571
|
+
|
|
572
|
+
// src/middleware/validation.ts
|
|
573
|
+
function createValidationMiddleware(options = {}) {
|
|
574
|
+
return async (c, next) => {
|
|
575
|
+
try {
|
|
576
|
+
const env = c.get("env");
|
|
577
|
+
const maxQueryDepth = options.maxQueryDepth || env?.MAX_QUERY_DEPTH || 10;
|
|
578
|
+
const maxQueryComplexity = options.maxQueryComplexity || env?.MAX_QUERY_COMPLEXITY || 1e3;
|
|
579
|
+
const maxAliasCount = options.maxAliasCount || 15;
|
|
580
|
+
const contentType = c.req.header("content-type");
|
|
581
|
+
if (c.req.method === "POST" && !contentType?.includes("application/json")) {
|
|
582
|
+
return c.json(
|
|
583
|
+
{
|
|
584
|
+
errors: [
|
|
585
|
+
{
|
|
586
|
+
message: "Content-Type must be application/json",
|
|
587
|
+
extensions: { code: "BAD_REQUEST" }
|
|
588
|
+
}
|
|
589
|
+
]
|
|
590
|
+
},
|
|
591
|
+
400
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
const clientId = c.req.header("x-client-id");
|
|
595
|
+
if (!clientId && env?.NODE_ENV === "production") {
|
|
596
|
+
return c.json(
|
|
597
|
+
{
|
|
598
|
+
errors: [
|
|
599
|
+
{
|
|
600
|
+
message: "Client identification required",
|
|
601
|
+
extensions: { code: "BAD_REQUEST" }
|
|
602
|
+
}
|
|
603
|
+
]
|
|
604
|
+
},
|
|
605
|
+
400
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
await next();
|
|
609
|
+
} catch (error) {
|
|
610
|
+
console.error("Validation middleware error:", error);
|
|
611
|
+
return c.json(
|
|
612
|
+
{
|
|
613
|
+
errors: [
|
|
614
|
+
{
|
|
615
|
+
message: "Request validation failed",
|
|
616
|
+
extensions: { code: "INTERNAL_SERVER_ERROR" }
|
|
617
|
+
}
|
|
618
|
+
]
|
|
619
|
+
},
|
|
620
|
+
500
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/middleware/rate-limit.ts
|
|
627
|
+
function createRateLimitMiddleware(options = {}) {
|
|
628
|
+
const {
|
|
629
|
+
keyGenerator = (c) => c.req.header("x-client-id") || c.req.header("cf-connecting-ip") || "anonymous"
|
|
630
|
+
} = options;
|
|
631
|
+
return async (c, next) => {
|
|
632
|
+
const env = c.get("env");
|
|
633
|
+
const windowMs = options.windowMs || env?.RATE_LIMIT_WINDOW_MS || 60 * 1e3;
|
|
634
|
+
const max = options.max || env?.RATE_LIMIT_MAX || 100;
|
|
635
|
+
const key = keyGenerator(c);
|
|
636
|
+
const rateLimitKey = `rate_limit:${key}`;
|
|
637
|
+
if (c.env?.RATE_LIMIT_KV) {
|
|
638
|
+
const current = await c.env.RATE_LIMIT_KV.get(rateLimitKey);
|
|
639
|
+
const count = current ? parseInt(current, 10) : 0;
|
|
640
|
+
if (count >= max) {
|
|
641
|
+
return c.json(
|
|
642
|
+
{
|
|
643
|
+
errors: [
|
|
644
|
+
{
|
|
645
|
+
message: "Too many requests",
|
|
646
|
+
extensions: {
|
|
647
|
+
code: "RATE_LIMITED",
|
|
648
|
+
retryAfter: windowMs / 1e3
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
]
|
|
652
|
+
},
|
|
653
|
+
429,
|
|
654
|
+
{
|
|
655
|
+
"Retry-After": String(windowMs / 1e3),
|
|
656
|
+
"X-RateLimit-Limit": String(max),
|
|
657
|
+
"X-RateLimit-Remaining": "0",
|
|
658
|
+
"X-RateLimit-Reset": String(Date.now() + windowMs)
|
|
659
|
+
}
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
await c.env.RATE_LIMIT_KV.put(rateLimitKey, String(count + 1), {
|
|
663
|
+
expirationTtl: windowMs / 1e3
|
|
664
|
+
});
|
|
665
|
+
c.header("X-RateLimit-Limit", String(max));
|
|
666
|
+
c.header("X-RateLimit-Remaining", String(max - count - 1));
|
|
667
|
+
c.header("X-RateLimit-Reset", String(Date.now() + windowMs));
|
|
668
|
+
}
|
|
669
|
+
await next();
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/middleware/tracing.ts
|
|
674
|
+
function createTracingMiddleware() {
|
|
675
|
+
return async (c, next) => {
|
|
676
|
+
const env = c.get("env");
|
|
677
|
+
const traceId = c.req.header("x-trace-id") || generateTraceId();
|
|
678
|
+
const spanId = generateSpanId();
|
|
679
|
+
const parentSpanId = c.req.header("x-parent-span-id");
|
|
680
|
+
const userId = c.get("userId");
|
|
681
|
+
globalThis.requestContext = {
|
|
682
|
+
traceId,
|
|
683
|
+
spanId,
|
|
684
|
+
userId
|
|
685
|
+
};
|
|
686
|
+
c.set("traceId", traceId);
|
|
687
|
+
c.set("spanId", spanId);
|
|
688
|
+
c.header("x-trace-id", traceId);
|
|
689
|
+
c.header("x-span-id", spanId);
|
|
690
|
+
const startTime = Date.now();
|
|
691
|
+
try {
|
|
692
|
+
await next();
|
|
693
|
+
} finally {
|
|
694
|
+
const duration = Date.now() - startTime;
|
|
695
|
+
logger.info(
|
|
696
|
+
{
|
|
697
|
+
type: "request",
|
|
698
|
+
traceId,
|
|
699
|
+
spanId,
|
|
700
|
+
parentSpanId,
|
|
701
|
+
method: c.req.method,
|
|
702
|
+
path: c.req.path,
|
|
703
|
+
status: c.res.status,
|
|
704
|
+
duration,
|
|
705
|
+
userAgent: c.req.header("user-agent"),
|
|
706
|
+
ip: c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for")
|
|
707
|
+
},
|
|
708
|
+
`${c.req.method} ${c.req.path} - ${c.res.status} (${duration}ms)`
|
|
709
|
+
);
|
|
710
|
+
c.header("Server-Timing", `total;dur=${duration}`);
|
|
711
|
+
delete globalThis.requestContext;
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function generateTraceId() {
|
|
716
|
+
return Array.from(crypto.getRandomValues(new Uint8Array(16))).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
717
|
+
}
|
|
718
|
+
function generateSpanId() {
|
|
719
|
+
return Array.from(crypto.getRandomValues(new Uint8Array(8))).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/middleware/health.ts
|
|
723
|
+
function configureHealthChecks(app, options = {}) {
|
|
724
|
+
app.get("/health", (c) => {
|
|
725
|
+
return c.json({
|
|
726
|
+
status: "ok",
|
|
727
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
728
|
+
uptime: process.uptime ? process.uptime() : 0
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
app.get("/ready", async (c) => {
|
|
732
|
+
const checks = options.checks || {};
|
|
733
|
+
const results = {};
|
|
734
|
+
let allHealthy = true;
|
|
735
|
+
for (const [name, check] of Object.entries(checks)) {
|
|
736
|
+
try {
|
|
737
|
+
results[name] = await check();
|
|
738
|
+
if (!results[name]) allHealthy = false;
|
|
739
|
+
} catch (error) {
|
|
740
|
+
results[name] = false;
|
|
741
|
+
allHealthy = false;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
const status = allHealthy ? 200 : 503;
|
|
745
|
+
return c.json(
|
|
746
|
+
{
|
|
747
|
+
status: allHealthy ? "ready" : "not_ready",
|
|
748
|
+
checks: results,
|
|
749
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
750
|
+
},
|
|
751
|
+
status
|
|
752
|
+
);
|
|
753
|
+
});
|
|
754
|
+
app.get("/metrics", (c) => {
|
|
755
|
+
return c.text(`# HELP graphql_requests_total Total number of GraphQL requests
|
|
756
|
+
# TYPE graphql_requests_total counter
|
|
757
|
+
graphql_requests_total 0
|
|
758
|
+
|
|
759
|
+
# HELP graphql_errors_total Total number of GraphQL errors
|
|
760
|
+
# TYPE graphql_errors_total counter
|
|
761
|
+
graphql_errors_total 0
|
|
762
|
+
|
|
763
|
+
# HELP graphql_duration_seconds GraphQL request duration
|
|
764
|
+
# TYPE graphql_duration_seconds histogram
|
|
765
|
+
graphql_duration_seconds_bucket{le="0.1"} 0
|
|
766
|
+
graphql_duration_seconds_bucket{le="0.5"} 0
|
|
767
|
+
graphql_duration_seconds_bucket{le="1"} 0
|
|
768
|
+
graphql_duration_seconds_bucket{le="+Inf"} 0
|
|
769
|
+
graphql_duration_seconds_sum 0
|
|
770
|
+
graphql_duration_seconds_count 0`);
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/server/hono.configure.ts
|
|
775
|
+
function configureHono(builder) {
|
|
776
|
+
const app = new Hono();
|
|
777
|
+
app.use("*", async (c, next) => {
|
|
778
|
+
const rawEnv = c.env || {};
|
|
779
|
+
const env = typeof rawEnv.NODE_ENV !== "undefined" ? rawEnv : {};
|
|
780
|
+
c.set("env", env);
|
|
781
|
+
await next();
|
|
782
|
+
});
|
|
783
|
+
app.use("*", createTracingMiddleware());
|
|
784
|
+
app.use("*", compress());
|
|
785
|
+
app.use("*", createValidationMiddleware());
|
|
786
|
+
app.use("*", createRateLimitMiddleware());
|
|
787
|
+
configureHealthChecks(app, {
|
|
788
|
+
checks: {
|
|
789
|
+
graphql: async () => true
|
|
790
|
+
// Check GraphQL schema is loaded
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
app.use(
|
|
794
|
+
"/*",
|
|
795
|
+
cors({
|
|
796
|
+
origin: (origin, c) => {
|
|
797
|
+
const env = c.env;
|
|
798
|
+
const isProd = env.NODE_ENV === "production";
|
|
799
|
+
const allowedOrigins = env.ALLOWED_ORIGINS ? typeof env.ALLOWED_ORIGINS === "string" ? env.ALLOWED_ORIGINS.split(",").map((s) => s.trim()) : env.ALLOWED_ORIGINS : ["http://localhost:3000"];
|
|
800
|
+
if (!isProd) {
|
|
801
|
+
return origin || "*";
|
|
802
|
+
}
|
|
803
|
+
return allowedOrigins.includes(origin) ? origin : allowedOrigins[0];
|
|
804
|
+
},
|
|
805
|
+
allowHeaders: [
|
|
806
|
+
"Content-Type",
|
|
807
|
+
"Authorization",
|
|
808
|
+
"x-apollo-operation-name",
|
|
809
|
+
"apollo-require-preflight"
|
|
810
|
+
],
|
|
811
|
+
allowMethods: ["POST", "GET", "OPTIONS"],
|
|
812
|
+
credentials: true,
|
|
813
|
+
maxAge: 86400
|
|
814
|
+
})
|
|
815
|
+
);
|
|
816
|
+
app.use("*", async (c, next) => {
|
|
817
|
+
await next();
|
|
818
|
+
const env = c.get("env");
|
|
819
|
+
const isProd = env?.NODE_ENV === "production";
|
|
820
|
+
c.header("X-Content-Type-Options", "nosniff");
|
|
821
|
+
c.header("X-Frame-Options", "DENY");
|
|
822
|
+
c.header("X-XSS-Protection", "1; mode=block");
|
|
823
|
+
c.header("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
824
|
+
c.header("Server", "GraphQL");
|
|
825
|
+
if (isProd) {
|
|
826
|
+
c.header(
|
|
827
|
+
"Content-Security-Policy",
|
|
828
|
+
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
|
|
829
|
+
);
|
|
830
|
+
c.header(
|
|
831
|
+
"Strict-Transport-Security",
|
|
832
|
+
"max-age=31536000; includeSubDomains; preload"
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
return app;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/utils/graphql.util.ts
|
|
840
|
+
import { ApolloServerErrorCode } from "@apollo/server/errors";
|
|
841
|
+
var BAD_USER_INPUT_CODE = ApolloServerErrorCode.BAD_USER_INPUT;
|
|
842
|
+
var getNonUserInputErrors = (errors) => errors.filter((e) => e.extensions?.code !== BAD_USER_INPUT_CODE);
|
|
843
|
+
|
|
844
|
+
// src/utils/tracing.util.ts
|
|
845
|
+
var getOpsName = (gqlCtx) => gqlCtx.contextValue.rootSpan?.getBaggageItem(
|
|
846
|
+
"root_op_name" /* ROOT_OP_NAME */
|
|
847
|
+
) || gqlCtx.request.operationName || gqlCtx.operationName || "UNKNOWN";
|
|
848
|
+
|
|
849
|
+
// src/logger/apollo.logger.ts
|
|
850
|
+
var getUserId = (gqlCtx) => gqlCtx.user?.id ?? "UNKNOWN";
|
|
851
|
+
function createApolloLoggingPlugin() {
|
|
852
|
+
return {
|
|
853
|
+
async requestDidStart(requestContext) {
|
|
854
|
+
const start = Date.now();
|
|
855
|
+
const operationName = requestContext.request.operationName;
|
|
856
|
+
const requestLogger = createLogger({
|
|
857
|
+
source: "apollo",
|
|
858
|
+
operationName
|
|
859
|
+
});
|
|
860
|
+
return {
|
|
861
|
+
async didResolveOperation(gqlCtx) {
|
|
862
|
+
const userId = getUserId(gqlCtx.contextValue);
|
|
863
|
+
const operation = getOpsName(gqlCtx);
|
|
864
|
+
requestLogger.info(
|
|
865
|
+
{
|
|
866
|
+
event: "operation_resolved",
|
|
867
|
+
operation,
|
|
868
|
+
userId,
|
|
869
|
+
operationName: gqlCtx.operationName,
|
|
870
|
+
variables: gqlCtx.request.variables
|
|
871
|
+
},
|
|
872
|
+
`Operation [${operation}] resolved for user [${userId}]`
|
|
873
|
+
);
|
|
874
|
+
},
|
|
875
|
+
async willSendResponse(gqlCtx) {
|
|
876
|
+
const errors = getNonUserInputErrors(gqlCtx.errors || []);
|
|
877
|
+
const duration = Date.now() - start;
|
|
878
|
+
const userId = getUserId(gqlCtx.contextValue);
|
|
879
|
+
const operation = getOpsName(gqlCtx);
|
|
880
|
+
if (errors.length > 0) {
|
|
881
|
+
requestLogger.error(
|
|
882
|
+
{
|
|
883
|
+
event: "operation_error",
|
|
884
|
+
operation,
|
|
885
|
+
userId,
|
|
886
|
+
duration,
|
|
887
|
+
errors: errors.map((e) => ({
|
|
888
|
+
message: e.message,
|
|
889
|
+
path: e.path,
|
|
890
|
+
extensions: e.extensions,
|
|
891
|
+
stack: e.stack
|
|
892
|
+
}))
|
|
893
|
+
},
|
|
894
|
+
`Operation [${operation}] failed with ${errors.length} error(s)`
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
requestLogger.info(
|
|
898
|
+
{
|
|
899
|
+
event: "operation_complete",
|
|
900
|
+
operation,
|
|
901
|
+
userId,
|
|
902
|
+
duration,
|
|
903
|
+
success: errors.length === 0,
|
|
904
|
+
errorCount: errors.length
|
|
905
|
+
},
|
|
906
|
+
`Operation [${operation}] completed in ${duration}ms${errors.length > 0 ? " with errors" : " successfully"}`
|
|
907
|
+
);
|
|
908
|
+
},
|
|
909
|
+
async didEncounterErrors(gqlCtx) {
|
|
910
|
+
const errors = gqlCtx.errors || [];
|
|
911
|
+
const userId = getUserId(gqlCtx.contextValue);
|
|
912
|
+
requestLogger.error(
|
|
913
|
+
{
|
|
914
|
+
event: "graphql_errors",
|
|
915
|
+
userId,
|
|
916
|
+
errors: errors.map((e) => ({
|
|
917
|
+
message: e.message,
|
|
918
|
+
path: e.path,
|
|
919
|
+
extensions: e.extensions
|
|
920
|
+
}))
|
|
921
|
+
},
|
|
922
|
+
`GraphQL execution encountered ${errors.length} error(s)`
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// src/server/plugin.configure.ts
|
|
931
|
+
import { ApolloServerPluginLandingPageDisabled } from "@apollo/server/plugin/disabled";
|
|
932
|
+
function configurePlugins(builder) {
|
|
933
|
+
builder.plugins.push(ApolloServerPluginLandingPageDisabled());
|
|
934
|
+
builder.plugins.push(createApolloLoggingPlugin());
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// src/server/server.builder.ts
|
|
938
|
+
var ServerBuilder = class {
|
|
939
|
+
constructor(config) {
|
|
940
|
+
this.config = config;
|
|
941
|
+
this.plugins = [];
|
|
942
|
+
this.assert();
|
|
943
|
+
this.app = configureHono(this);
|
|
944
|
+
this.schema = this.config.apollo.schema;
|
|
945
|
+
}
|
|
946
|
+
configure() {
|
|
947
|
+
const honoApp = this.app;
|
|
948
|
+
configurePlugins(this);
|
|
949
|
+
const server = new ApolloServer({
|
|
950
|
+
schema: this.schema,
|
|
951
|
+
plugins: this.plugins
|
|
952
|
+
});
|
|
953
|
+
const cfHandler = startServerAndCreateCloudflareWorkersHandler(
|
|
954
|
+
server,
|
|
955
|
+
{
|
|
956
|
+
context: async (c) => createContextFunction(this.config.extendContext)(c)
|
|
957
|
+
}
|
|
958
|
+
);
|
|
959
|
+
honoApp.all(this.config.path ?? "/", async (c) => {
|
|
960
|
+
let executionCtx;
|
|
961
|
+
try {
|
|
962
|
+
executionCtx = c.executionCtx;
|
|
963
|
+
} catch (e) {
|
|
964
|
+
executionCtx = {
|
|
965
|
+
waitUntil: (promise) => promise,
|
|
966
|
+
passThroughOnException: () => {
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
return cfHandler(c.req.raw, c.env, executionCtx);
|
|
971
|
+
});
|
|
972
|
+
return {
|
|
973
|
+
// fetch: app.fetch,
|
|
974
|
+
async fetch(request, env, ctx) {
|
|
975
|
+
await Promise.resolve();
|
|
976
|
+
return honoApp.fetch(request, env, ctx);
|
|
977
|
+
},
|
|
978
|
+
port: 3344
|
|
979
|
+
// Only used in local development
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
assert() {
|
|
983
|
+
assertDefined(
|
|
984
|
+
this.config.apollo.schema,
|
|
985
|
+
"Apollo server config must have a schema"
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
// src/server/index.ts
|
|
991
|
+
var SERVICE_NAME_REGEX = /^[a-z0-9\-_]+$/i;
|
|
992
|
+
function createGraphQLServer(options) {
|
|
993
|
+
if (!SERVICE_NAME_REGEX.test(options.name)) {
|
|
994
|
+
throw new Error("Service name must be alphanumeric, hyphen, or underscore");
|
|
995
|
+
}
|
|
996
|
+
logger.info(`Creating GraphQL server ${options.name} v${options.version}`);
|
|
997
|
+
return new ServerBuilder(options.config);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// src/utils/error.util.ts
|
|
1001
|
+
import { GraphQLError } from "graphql";
|
|
1002
|
+
import { ApolloServerErrorCode as ApolloServerErrorCode2 } from "@apollo/server/errors";
|
|
1003
|
+
var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
1004
|
+
ErrorCode2["UNAUTHENTICATED"] = "UNAUTHENTICATED";
|
|
1005
|
+
ErrorCode2["FORBIDDEN"] = "FORBIDDEN";
|
|
1006
|
+
ErrorCode2["VALIDATION_FAILED"] = "VALIDATION_FAILED";
|
|
1007
|
+
ErrorCode2["RATE_LIMITED"] = "RATE_LIMITED";
|
|
1008
|
+
ErrorCode2["EXTERNAL_SERVICE_ERROR"] = "EXTERNAL_SERVICE_ERROR";
|
|
1009
|
+
ErrorCode2["NOT_FOUND"] = "NOT_FOUND";
|
|
1010
|
+
ErrorCode2["CONFLICT"] = "CONFLICT";
|
|
1011
|
+
return ErrorCode2;
|
|
1012
|
+
})(ErrorCode || {});
|
|
1013
|
+
var AppError = class extends GraphQLError {
|
|
1014
|
+
constructor(message, code, extensions) {
|
|
1015
|
+
super(message, {
|
|
1016
|
+
extensions: {
|
|
1017
|
+
code,
|
|
1018
|
+
...extensions
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
// src/utils/schema.util.ts
|
|
1025
|
+
import { gql } from "graphql-tag";
|
|
1026
|
+
var createSchemaModule = (typeDefs, resolvers) => ({
|
|
1027
|
+
typeDefs,
|
|
1028
|
+
resolvers
|
|
1029
|
+
});
|
|
1030
|
+
export {
|
|
1031
|
+
AppError,
|
|
1032
|
+
CachedDataLoader,
|
|
1033
|
+
ErrorCode,
|
|
1034
|
+
KVCache,
|
|
1035
|
+
RedisCache,
|
|
1036
|
+
ServerBuilder,
|
|
1037
|
+
cached,
|
|
1038
|
+
configureHealthChecks,
|
|
1039
|
+
createApolloLoggingPlugin,
|
|
1040
|
+
createCache,
|
|
1041
|
+
createContextFunction,
|
|
1042
|
+
createGraphQLServer,
|
|
1043
|
+
createRateLimitMiddleware,
|
|
1044
|
+
createSchemaModule,
|
|
1045
|
+
createTracingMiddleware,
|
|
1046
|
+
createValidationMiddleware,
|
|
1047
|
+
envLoader,
|
|
1048
|
+
getNonUserInputErrors,
|
|
1049
|
+
gql,
|
|
1050
|
+
logger
|
|
1051
|
+
};
|
|
1052
|
+
//# sourceMappingURL=index.js.map
|