-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathextension.js
148 lines (124 loc) · 3.65 KB
/
extension.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { readFileSync } from 'node:fs';
import { join, posix } from 'node:path';
import { pathToFileURL } from 'node:url';
import { ApolloServer, HeaderMap } from '@apollo/server';
import fastGlob from 'fast-glob';
const { GraphQL } = databases.cache;
const BASE_SCHEMA = `#graphql
enum CacheControlScope {
PUBLIC
PRIVATE
}
directive @cacheControl(
maxAge: Int
scope: CacheControlScope
inheritMaxAge: Boolean
) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
directive @table(
database: String
table: String
expiration: Int
audit: Boolean
) on OBJECT
directive @export(
name: String
) on OBJECT
directive @sealed on OBJECT
directive @primaryKey on FIELD_DEFINITION
directive @indexed on FIELD_DEFINITION
directive @updatedTime on FIELD_DEFINITION
directive @relationship(
to: String
from: String
) on FIELD_DEFINITION
scalar Long
scalar BigInt
scalar Date
scalar Any
`;
export function start(options = {}) {
const config = {
cache: options.cache,
port: options.port,
resolvers: options.resolvers ?? './resolvers.js',
schemas: options.schemas ?? './schemas.graphql',
securePort: options.securePort,
};
logger.debug('@harperdb/apollo extension configuration:\n' + JSON.stringify(config, null, 2));
return {
async handleDirectory(_, componentPath) {
// Load the resolvers
const resolversPath = join(componentPath, config.resolvers);
const resolvers = await import(pathToFileURL(resolversPath));
// Load the schemas
// posix.join is necessary so that `/` are retained, otherwise they get normalized to the platform
const schemasPath = posix.join(componentPath, config.schemas);
let typeDefs = BASE_SCHEMA;
for (const filePath of fastGlob.sync(schemasPath, { onlyFiles: true })) {
typeDefs += '\n' + readFileSync(filePath, 'utf-8');
}
// Get the custom cache or use the default
const Cache = config.cache ? await import(pathToFileURL(join(componentPath, config.cache))) : HarperDBCache;
// Set up Apollo Server
const apollo = new ApolloServer({
typeDefs,
resolvers: resolvers.default || resolvers,
cache: new Cache(),
});
await apollo.start();
server.http(
async (request, next) => {
// Parse the incoming request url so that the `pathname` and `search` can be used
const url = new URL(request.url, `http://${process.env.HOST ?? 'localhost'}`);
if (url.pathname === '/graphql') {
const body = await streamToBuffer(request.body);
const httpGraphQLRequest = {
method: request.method,
headers: new HeaderMap(request.headers),
body: JSON.parse(body),
search: url.search,
};
const response = await apollo.executeHTTPGraphQLRequest({
httpGraphQLRequest: httpGraphQLRequest,
context: () => httpGraphQLRequest,
});
response.body = response.body.string;
return response;
} else {
return next(request);
}
},
{ port: config.port, securePort: config.securePort }
);
return true;
},
};
}
function streamToBuffer(stream) {
return new Promise((resolve, reject) => {
const buffers = [];
stream.on('data', (data) => buffers.push(data));
stream.on('end', () => resolve(Buffer.concat(buffers)));
stream.on('error', reject);
});
}
class HarperDBCache extends Resource {
async get(key) {
let data = await GraphQL.get(key);
return data?.get('query');
}
async set(key, value, options) {
let context = this.getContext();
if (options?.ttl) {
if (!context) {
context = {};
}
//the ttl is in seconds
context.expiresAt = Date.now() + options.ttl * 1000;
}
await GraphQL.put({ id: key, query: value }, context);
}
async delete(key) {
await GraphQL.delete(key);
}
}