crelte 0.5.10 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/init/client.d.ts +1 -8
  2. package/dist/init/client.d.ts.map +1 -1
  3. package/dist/init/client.js +3 -10
  4. package/dist/init/server.js +1 -1
  5. package/dist/plugins/Events.d.ts +6 -6
  6. package/dist/plugins/Events.d.ts.map +1 -1
  7. package/dist/queries/Queries.d.ts +30 -5
  8. package/dist/queries/Queries.d.ts.map +1 -1
  9. package/dist/queries/Queries.js +19 -2
  10. package/dist/queries/gql.d.ts +2 -2
  11. package/dist/queries/gql.d.ts.map +1 -1
  12. package/dist/queries/index.d.ts +47 -2
  13. package/dist/queries/index.d.ts.map +1 -1
  14. package/dist/queries/index.js +2 -2
  15. package/dist/queries/vars.d.ts +2 -0
  16. package/dist/queries/vars.d.ts.map +1 -1
  17. package/dist/queries/vars.js +10 -0
  18. package/dist/routing/router/BaseRouter.d.ts +2 -1
  19. package/dist/routing/router/BaseRouter.d.ts.map +1 -1
  20. package/dist/routing/router/BaseRouter.js +4 -2
  21. package/dist/routing/router/ClientRouter.d.ts.map +1 -1
  22. package/dist/routing/router/ClientRouter.js +3 -4
  23. package/dist/routing/router/Router.d.ts +2 -1
  24. package/dist/routing/router/Router.d.ts.map +1 -1
  25. package/dist/routing/router/Router.js +2 -1
  26. package/dist/routing/utils.d.ts +1 -0
  27. package/dist/routing/utils.d.ts.map +1 -1
  28. package/dist/routing/utils.js +1 -1
  29. package/dist/server/ServerRouter.d.ts.map +1 -1
  30. package/dist/server/ServerRouter.js +17 -7
  31. package/dist/server/queries/QueryGqlRoute.d.ts +28 -0
  32. package/dist/server/queries/QueryGqlRoute.d.ts.map +1 -0
  33. package/dist/server/queries/QueryGqlRoute.js +194 -0
  34. package/dist/server/queries/QueryHandleRoute.d.ts +12 -0
  35. package/dist/server/queries/QueryHandleRoute.d.ts.map +1 -0
  36. package/dist/server/queries/QueryHandleRoute.js +24 -0
  37. package/dist/server/queries/queries.d.ts.map +1 -1
  38. package/dist/server/queries/queries.js +42 -19
  39. package/dist/server/queries/routes.d.ts +7 -30
  40. package/dist/server/queries/routes.d.ts.map +1 -1
  41. package/dist/server/queries/routes.js +13 -199
  42. package/package.json +1 -1
  43. package/src/init/client.ts +3 -10
  44. package/src/init/server.ts +1 -1
  45. package/src/plugins/Events.ts +10 -6
  46. package/src/queries/Queries.ts +47 -14
  47. package/src/queries/gql.ts +2 -2
  48. package/src/queries/index.ts +71 -0
  49. package/src/queries/vars.ts +13 -0
  50. package/src/routing/router/BaseRouter.ts +4 -2
  51. package/src/routing/router/ClientRouter.ts +3 -4
  52. package/src/routing/router/Router.ts +2 -1
  53. package/src/routing/utils.ts +1 -1
  54. package/src/server/ServerRouter.ts +18 -7
  55. package/src/server/queries/QueryGqlRoute.ts +224 -0
  56. package/src/server/queries/QueryHandleRoute.ts +37 -0
  57. package/src/server/queries/queries.ts +57 -21
  58. package/src/server/queries/routes.ts +25 -229
@@ -40,7 +40,7 @@ export default class Events {
40
40
  ): () => void;
41
41
  on(
42
42
  ev: 'loadGlobalData',
43
- fn: (cr: CrelteRequest) => Promise<any>,
43
+ fn: (cr: CrelteRequest) => Promise<any> | any,
44
44
  ): () => void;
45
45
  on(
46
46
  ev: 'loadEntry',
@@ -52,11 +52,11 @@ export default class Events {
52
52
  ): () => void;
53
53
  on(
54
54
  ev: 'afterLoadEntry',
55
- fn: (cr: CrelteRequest) => Promise<any>,
55
+ fn: (cr: CrelteRequest) => Promise<any> | any,
56
56
  ): () => void;
57
57
  on(
58
58
  ev: 'loadData',
59
- fn: (cr: CrelteRequest, entry: Entry) => Promise<any>,
59
+ fn: (cr: CrelteRequest, entry: Entry) => Promise<any> | any,
60
60
  ): () => void;
61
61
  on(ev: 'beforeRender', fn: (cr: CrelteRequest) => void): () => void;
62
62
  on(ev: string, fn: (...args: any[]) => any): () => void {
@@ -95,14 +95,18 @@ export default class Events {
95
95
  * Trigger an event
96
96
  */
97
97
  trigger(ev: 'beforeRequest', cr: CrelteRequest): (Promise<void> | void)[];
98
- trigger(ev: 'loadGlobalData', cr: CrelteRequest): Promise<any>[];
98
+ trigger(ev: 'loadGlobalData', cr: CrelteRequest): (Promise<any> | any)[];
99
99
  trigger(
100
100
  ev: 'beforeQueryEntry',
101
101
  cr: CrelteRequest,
102
102
  vars: EntryQueryVars,
103
103
  ): (Promise<void> | void)[];
104
- trigger(ev: 'afterLoadEntry', cr: CrelteRequest): Promise<any>[];
105
- trigger(ev: 'loadData', cr: CrelteRequest, entry: Entry): Promise<any>[];
104
+ trigger(ev: 'afterLoadEntry', cr: CrelteRequest): (Promise<any> | any)[];
105
+ trigger(
106
+ ev: 'loadData',
107
+ cr: CrelteRequest,
108
+ entry: Entry,
109
+ ): (Promise<any> | any)[];
106
110
  trigger(ev: 'beforeRender', cr: CrelteRequest, route: Route): void[];
107
111
  trigger(ev: string, ...args: any[]): any[] {
108
112
  const set = this.inner.get(ev);
@@ -17,17 +17,35 @@ export type QueriesOptions = {
17
17
  /**
18
18
  * A GraphQL query
19
19
  *
20
- * You should almost never create this object directly
21
- * but instead import a graphql file or use the gql template.
20
+ * **You should almost never**
21
+ *
22
+ * When importing a graphql file you will get a {@link NamedQuery}
23
+ * or use the {@link gql} template function to create
24
+ * an {@link InlineQuery}.
25
+ * Alternatively you can use the {@link namedQuery} function to create
26
+ * a {@link NamedQuery}.
27
+ */
28
+ export type Query = InlineQuery | NamedQuery;
29
+
30
+ /**
31
+ * You should never create this type directly. It is returned from
32
+ * the {@link gql} template function.
33
+ */
34
+ export type InlineQuery = { path?: string; query: string };
35
+
36
+ /**
37
+ * Always create this object via the {@link namedQuery} function
38
+ */
39
+ export type NamedQuery = { queryName: string };
40
+
41
+ /**
42
+ * Create a NamedQuery for the given server query name.
43
+ *
44
+ * Prefer importing a graphql file instead.
22
45
  */
23
- export type Query =
24
- | {
25
- path?: string;
26
- query: string;
27
- }
28
- | {
29
- queryName: string;
30
- };
46
+ export function namedQuery(name: string): NamedQuery {
47
+ return { queryName: name };
48
+ }
31
49
 
32
50
  /** Returns true if the passed object is a GraphQlQuery */
33
51
  export function isQuery(obj: any): obj is Query {
@@ -102,8 +120,8 @@ export default class Queries {
102
120
  */
103
121
  static new(
104
122
  endpoint: string,
105
- frontend: string,
106
- ssrCache: SsrCache,
123
+ frontend: string | null = null,
124
+ ssrCache: SsrCache = new SsrCache(),
107
125
  opts: QueriesOptions = {},
108
126
  ): Queries {
109
127
  return new Queries(
@@ -113,6 +131,18 @@ export default class Queries {
113
131
  );
114
132
  }
115
133
 
134
+ /**
135
+ * Create a new Queries instance that always intented to call an
136
+ * external GraphQl endpoint.
137
+ */
138
+ static newExternal(endpoint: string, bearerToken?: string): Queries {
139
+ return new Queries(
140
+ new Inner(endpoint, null, new SsrCache(), { bearerToken }),
141
+ null,
142
+ null,
143
+ );
144
+ }
145
+
116
146
  /**
117
147
  * Run a GraphQl Query
118
148
  *
@@ -170,7 +200,7 @@ type InnerQueryOptions = {
170
200
 
171
201
  class Inner {
172
202
  endpoint: string;
173
- frontend: string;
203
+ frontend: string | null;
174
204
  ssrCache: SsrCache;
175
205
  private listeners: Map<
176
206
  string,
@@ -184,7 +214,7 @@ class Inner {
184
214
 
185
215
  constructor(
186
216
  endpoint: string,
187
- frontend: string,
217
+ frontend: string | null,
188
218
  ssrCache: SsrCache,
189
219
  opts: QueriesOptions = {},
190
220
  ) {
@@ -264,6 +294,9 @@ class Inner {
264
294
  let logName: string, url: URL;
265
295
 
266
296
  if ('queryName' in query) {
297
+ if (!this.frontend)
298
+ throw new Error('only inline queries supported');
299
+
267
300
  logName = `query (server: ${query.queryName})`;
268
301
  url = new URL(this.frontend);
269
302
  url.pathname = '/queries/' + query.queryName;
@@ -1,4 +1,4 @@
1
- import { isQuery, Query } from './Queries.js';
1
+ import { InlineQuery, isQuery } from './Queries.js';
2
2
 
3
3
  /**
4
4
  * Create a GraphQL query string with variables.
@@ -13,7 +13,7 @@ import { isQuery, Query } from './Queries.js';
13
13
  export function gql(
14
14
  strings: TemplateStringsArray | string[] | string,
15
15
  ...keys: unknown[]
16
- ): Query {
16
+ ): InlineQuery {
17
17
  if (typeof strings === 'string') strings = [strings];
18
18
 
19
19
  let query = '';
@@ -1,9 +1,13 @@
1
1
  import Queries, {
2
+ InlineQuery,
2
3
  isQuery,
4
+ namedQuery,
5
+ NamedQuery,
3
6
  QueriesOptions,
4
7
  Query,
5
8
  QueryOptions,
6
9
  } from '../queries/Queries.js';
10
+ import type CrelteServerRequest from '../server/CrelteServer.js';
7
11
  import { gql } from './gql.js';
8
12
  import QueryError, { QueryErrorResponse } from './QueryError.js';
9
13
  import { QueryVar, ValidIf, vars, varsIdsEqual } from './vars.js';
@@ -13,6 +17,9 @@ export {
13
17
  type QueriesOptions,
14
18
  type QueryOptions,
15
19
  type Query,
20
+ type InlineQuery as GqlQuery,
21
+ type NamedQuery,
22
+ namedQuery,
16
23
  isQuery,
17
24
  QueryError,
18
25
  type QueryErrorResponse,
@@ -32,6 +39,8 @@ export type InferVariableTypes<T> = {
32
39
  };
33
40
 
34
41
  /**
42
+ * Defines when a query can safely be cached.
43
+ *
35
44
  * #### Example
36
45
  * ```ts
37
46
  * import { vars, Caching } from 'crelte/queries';
@@ -48,3 +57,65 @@ export type InferVariableTypes<T> = {
48
57
  export type Caching<
49
58
  T extends Record<string, QueryVar<any>> = Record<string, QueryVar<any>>,
50
59
  > = boolean | ((response: any, vars: InferVariableTypes<T>) => boolean);
60
+
61
+ /** use {@link Transfrom} */
62
+ export type TransformFn<
63
+ T extends Record<string, QueryVar<any>> = Record<string, QueryVar<any>>,
64
+ > = (
65
+ response: any,
66
+ vars: InferVariableTypes<T>,
67
+ ) => void | any | Promise<void | any>;
68
+
69
+ /**
70
+ * Transforms the query response before it is returned or cached.
71
+ *
72
+ * #### Example
73
+ * ```ts
74
+ * export const transform: Transform<typeof variables> = (response, vars) => {
75
+ * for (const entry of response.entries) {
76
+ * entry.title = entry.title.toUpperCase();
77
+ * }
78
+ * };
79
+ * ```
80
+ */
81
+ export type Transform<
82
+ T extends Record<string, QueryVar<any>> = Record<string, QueryVar<any>>,
83
+ F extends TransformFn<T> = TransformFn<T>,
84
+ > = (response: any, vars: InferVariableTypes<T>) => Awaited<ReturnType<F>>;
85
+
86
+ /** use {@link Handle} */
87
+ export type HandleFn<
88
+ T extends Record<string, QueryVar<any>> = Record<string, QueryVar<any>>,
89
+ > = (csr: CrelteServerRequest, vars: InferVariableTypes<T>) => any;
90
+
91
+ /**
92
+ * Handles a query request.
93
+ *
94
+ * #### Example
95
+ * ```ts
96
+ * // queries/custom.ts
97
+ * import { vars, type Handle, gql, namedQuery } from 'crelte/queries';
98
+ *
99
+ * // It is good practice to have the query name inside the file
100
+ * export const customQuery = namedQuery('custom');
101
+ *
102
+ * export const variables = {
103
+ * name: vars.string(),
104
+ * };
105
+ *
106
+ * export const handle: Handle<typeof variables> = async (csr, vars) => {
107
+ * if (vars.name === 'demo') {
108
+ * throw new Response('not allowed', { status: 400 });
109
+ * }
110
+ *
111
+ * return { name: vars.name };
112
+ * };
113
+ * ```
114
+ */
115
+ export type Handle<
116
+ T extends Record<string, QueryVar<any>> = Record<string, QueryVar<any>>,
117
+ F extends HandleFn<T> = HandleFn<T>,
118
+ > = (
119
+ csr: CrelteServerRequest,
120
+ vars: InferVariableTypes<T>,
121
+ ) => Awaited<ReturnType<F>>;
@@ -53,12 +53,14 @@ export class QueryVar<T = any> {
53
53
  private name: string | null;
54
54
  private type: 'any' | 'string' | 'number' | 'id' | 'ids';
55
55
  private flagNullable: boolean;
56
+ private defaultValue: T | undefined;
56
57
  private validIfFn: ValidIf<T>;
57
58
 
58
59
  constructor() {
59
60
  this.name = null;
60
61
  this.type = 'any';
61
62
  this.flagNullable = false;
63
+ this.defaultValue = undefined;
62
64
  this.validIfFn = () => true;
63
65
  }
64
66
 
@@ -87,6 +89,11 @@ export class QueryVar<T = any> {
87
89
  return this as QueryVar<T | null>;
88
90
  }
89
91
 
92
+ default(value: T): QueryVar<T> {
93
+ this.defaultValue = value;
94
+ return this;
95
+ }
96
+
90
97
  /**
91
98
  * Set a validation function for this variable
92
99
  *
@@ -103,6 +110,8 @@ export class QueryVar<T = any> {
103
110
  if (typeof v === 'undefined') v = null;
104
111
 
105
112
  if (v === null) {
113
+ if (this.defaultValue !== undefined) return this.defaultValue;
114
+
106
115
  if (!this.flagNullable)
107
116
  throw new Error(`variable ${this.name} cannot be null`);
108
117
 
@@ -139,7 +148,11 @@ export class QueryVar<T = any> {
139
148
  );
140
149
 
141
150
  if (v.length <= 0) {
151
+ if (this.defaultValue !== undefined)
152
+ return this.defaultValue;
153
+
142
154
  if (this.flagNullable) return null;
155
+
143
156
  throw new Error(
144
157
  `variable ${this.name} is not allowed to be empty`,
145
158
  );
@@ -123,8 +123,10 @@ export default class BaseRouter {
123
123
  }
124
124
 
125
125
  /**
126
- * todo check that the router uses the correct sites for each function
126
+ * Returns the default site tries to use a site which matches the preferred language
127
+ * else returns the primary site
127
128
  */
129
+ // todo check that the router uses the correct sites for each function
128
130
  defaultSite(): Site {
129
131
  return this.preferredSite() ?? this.primarySite();
130
132
  }
@@ -233,7 +235,7 @@ export default class BaseRouter {
233
235
  const req = this.targetToRequest(target, { origin: 'preload' });
234
236
  const current = this.route.get();
235
237
 
236
- // if the origin matches, the route will be able to be load
238
+ // if the origin matches, the route will be able to be loaded
237
239
  // so let's preload it
238
240
  if (current && current.url.origin === req.url.origin) {
239
241
  // todo i don't wan't to send a CrelteRequest?
@@ -202,11 +202,10 @@ export default class ClientRouter extends BaseRouter {
202
202
  window.addEventListener('scroll', () => this.onScroll());
203
203
 
204
204
  window.addEventListener('popstate', async e => {
205
- if (!e.state?.route) return;
206
-
207
- const req = this.targetToRequest(window.location.href);
205
+ const req = this.targetToRequest(window.location.href, {
206
+ origin: 'pop',
207
+ });
208
208
  req.z_fillFromState(e.state);
209
- req.origin = 'pop';
210
209
 
211
210
  // todo handle errors
212
211
  this.handleRequest(req, () => {});
@@ -409,7 +409,8 @@ export default class Router {
409
409
  return nRouter;
410
410
  }
411
411
 
412
- _requestCompleted() {
412
+ /** @hidden */
413
+ z_requestCompleted() {
413
414
  this._request = null;
414
415
  }
415
416
  }
@@ -5,7 +5,7 @@ export function trimSlashEnd(str: string) {
5
5
  return str.endsWith('/') ? str.substring(0, str.length - 1) : str;
6
6
  }
7
7
 
8
- // same as ?? but only for undefined
8
+ /** same as ?? but only for undefined */
9
9
  export function orDef<T>(a: T | undefined, def: T): T {
10
10
  return a === undefined ? def : a;
11
11
  }
@@ -139,9 +139,7 @@ export default class ServerRouter {
139
139
  this.endpointUrl,
140
140
  this.frontendUrl,
141
141
  new SsrCache(),
142
- {
143
- bearerToken: this.endpointToken,
144
- },
142
+ { bearerToken: this.endpointToken },
145
143
  );
146
144
 
147
145
  const csr = new CrelteServerRequest(nReq, {
@@ -152,12 +150,25 @@ export default class ServerRouter {
152
150
  queries,
153
151
  });
154
152
 
153
+ let resp: Response | null = null;
155
154
  for (const handler of handlers) {
156
- const res = await handler(csr);
157
- if (res) {
158
- csr.z_finishResponse(res);
159
- return res;
155
+ try {
156
+ const res = await handler(csr);
157
+ if (!res) continue;
158
+
159
+ resp = res;
160
+ } catch (e) {
161
+ if (!(e instanceof Response)) throw e;
162
+
163
+ resp = e;
160
164
  }
165
+
166
+ break;
167
+ }
168
+
169
+ if (resp) {
170
+ csr.z_finishResponse(resp);
171
+ return resp;
161
172
  }
162
173
 
163
174
  return null;
@@ -0,0 +1,224 @@
1
+ import CrelteServerRequest from '../CrelteServer.js';
2
+ import QueriesCaching from './QueriesCaching.js';
3
+ import { QueryVar, vars } from '../../queries/vars.js';
4
+ import { extractEntry } from '../../loadData/index.js';
5
+ import { calcKey } from '../../ssr/index.js';
6
+ import { CacheIfFn, newError, TransformFn, validateVars } from './routes.js';
7
+
8
+ export type QueryGqlArgs = {
9
+ vars: Record<string, QueryVar> | null;
10
+ cacheIfFn: CacheIfFn | null;
11
+ preventCaching: boolean;
12
+ transformFn: TransformFn | null;
13
+ };
14
+
15
+ // only internal
16
+ export default class QueryGqlRoute {
17
+ name: string;
18
+ query: string;
19
+ vars: Record<string, QueryVar> | null;
20
+ cacheIfFn: CacheIfFn | null;
21
+ transformFn: TransformFn | null;
22
+
23
+ constructor(name: string, query: string, args: QueryGqlArgs) {
24
+ if (args.cacheIfFn && !vars)
25
+ throw new Error(
26
+ 'queryRoute: ' +
27
+ name +
28
+ ' cannot have caching function if there are no ' +
29
+ 'variables defined',
30
+ );
31
+
32
+ this.name = name;
33
+ this.query = query;
34
+ this.vars = args.vars;
35
+ this.cacheIfFn = args.cacheIfFn;
36
+ this.transformFn = args.transformFn;
37
+
38
+ if (args.preventCaching) {
39
+ if (this.cacheIfFn) throw new Error('unreachable');
40
+ // prevent filling defaults
41
+ return;
42
+ }
43
+
44
+ // add default vars and cacheIfFn if we know the route
45
+ if (this.name === 'entry') this.fillEntryDefaults();
46
+ else if (this.name === 'global') this.fillGlobalDefaults();
47
+ else this.fillBasicDefaults();
48
+ }
49
+
50
+ private fillEntryDefaults() {
51
+ if (this.vars) return;
52
+
53
+ // the _setName step happens in parseVars which happens before setting
54
+ // the defaults, so since we're adding vars here we need to set the name
55
+ // manually
56
+ this.vars = {
57
+ siteId: vars.siteId().z_setName('siteId'),
58
+ uri: vars.string().z_setName('uri'),
59
+ };
60
+ this.cacheIfFn = res => !!extractEntry(res);
61
+ }
62
+
63
+ private fillGlobalDefaults() {
64
+ if (this.vars) return;
65
+
66
+ this.vars = { siteId: vars.siteId().z_setName('siteId') };
67
+ this.cacheIfFn = () => true;
68
+ }
69
+
70
+ /**
71
+ * This adds caching to queries containing `query {` or
72
+ * `query ($siteId: [QueryArgument) {` without any additional vars
73
+ */
74
+ private fillBasicDefaults() {
75
+ if (this.vars) return;
76
+
77
+ const NO_VAR_TEST = /(^|\s)query\s*{/;
78
+ const SITE_ID_VAR_TEST =
79
+ /(^|\s)query\s*\(\s*\$siteId\s*:\s*\[\s*QueryArgument\s*\]\s*\)\s*{/;
80
+
81
+ if (NO_VAR_TEST.test(this.query)) {
82
+ this.vars = {};
83
+ this.cacheIfFn = () => true;
84
+ } else if (SITE_ID_VAR_TEST.test(this.query)) {
85
+ this.vars = { siteId: vars.siteId().z_setName('siteId') };
86
+ this.cacheIfFn = () => true;
87
+ } else if (!this.query.includes('query')) {
88
+ // this warning might be shown to mutation queries or subscriptions
89
+ // in that case, the user should explicitly set caching to false
90
+ console.warn(
91
+ `cannot determine if query (${this.name}) is cacheable, see` +
92
+ ' https://github.com/crelte/crelte/issues/114 for infos',
93
+ );
94
+ }
95
+ }
96
+
97
+ private async transform(
98
+ jsonResp: Record<string, any>,
99
+ vars: Record<string, any>,
100
+ ): Promise<void> {
101
+ if (!this.transformFn || !jsonResp.data) return;
102
+
103
+ const transformed = await this.transformFn(jsonResp.data, vars);
104
+ if (typeof transformed !== 'undefined') jsonResp.data = transformed;
105
+ }
106
+
107
+ async handle(
108
+ caching: QueriesCaching,
109
+ csr: CrelteServerRequest,
110
+ ): Promise<Response> {
111
+ let vars: Record<string, any>;
112
+ try {
113
+ const reqVars = await csr.req.json();
114
+ vars = validateVars(this.vars, reqVars, caching.router);
115
+ if ('qName' in vars || 'xCraftSite' in vars)
116
+ throw new Error(
117
+ 'qName and xCraftSite are reserved variable names',
118
+ );
119
+ } catch (e) {
120
+ return newError(e, 400);
121
+ }
122
+
123
+ let logInfo: string | null = null;
124
+ if (caching.debug) {
125
+ logInfo = `[queries: ${this.name}] vars: ${JSON.stringify(vars)}`;
126
+ }
127
+
128
+ let previewToken: string | null = null;
129
+ let siteToken: string | null = null;
130
+
131
+ const reqSearch = new URL(csr.req.url).searchParams;
132
+
133
+ if (reqSearch.has('token')) {
134
+ previewToken = reqSearch.get('token');
135
+ } else if (reqSearch.has('siteToken')) {
136
+ siteToken = reqSearch.get('siteToken');
137
+ }
138
+
139
+ // check for x-craft-site header and pass it on
140
+ const xCraftSite = csr.req.headers.get('X-Craft-Site');
141
+
142
+ let cacheKey: string | null = null;
143
+ const useCache = !previewToken && caching.isEnabled();
144
+ if (useCache) {
145
+ cacheKey = await calcKey({ ...vars, qName: this.name, xCraftSite });
146
+ const cached = await caching.getCache(cacheKey);
147
+
148
+ if (logInfo) console.log(`${logInfo} ${cached ? 'hit' : 'miss'}`);
149
+
150
+ // we found something in the cache
151
+ if (cached) return Response.json(cached);
152
+ }
153
+
154
+ const headers: Record<string, string> = {
155
+ 'Content-Type': 'application/json',
156
+ };
157
+
158
+ const auth = csr.getEnv('ENDPOINT_TOKEN');
159
+ if (auth) headers['Authorization'] = 'Bearer ' + auth;
160
+
161
+ const url = new URL(csr.getEnv('ENDPOINT_URL'));
162
+ if (previewToken) url.searchParams.set('token', previewToken);
163
+ if (siteToken) url.searchParams.set('siteToken', siteToken);
164
+
165
+ const xDebug = csr.req.headers.get('X-Debug');
166
+ if (xDebug) headers['X-Debug'] = xDebug;
167
+
168
+ if (xCraftSite) headers['X-Craft-Site'] = xCraftSite;
169
+
170
+ // now execute the gql request
171
+ let resp: Response;
172
+ try {
173
+ resp = await fetch(url, {
174
+ method: 'POST',
175
+ headers,
176
+ body: JSON.stringify({
177
+ query: this.query,
178
+ variables: vars,
179
+ }),
180
+ });
181
+
182
+ // if the response is not ok we don't cache anything
183
+ // and just return the response
184
+ if (!resp.ok) return resp;
185
+ } catch (e) {
186
+ return newError(e, 500);
187
+ }
188
+
189
+ const respHeaders: Record<string, string> = {};
190
+ const xDebugLink = resp.headers.get('X-Debug-Link');
191
+ if (xDebugLink) respHeaders['X-Debug'] = xDebugLink;
192
+
193
+ let jsonResp: Record<string, any>;
194
+ try {
195
+ jsonResp = await resp.json();
196
+ if (!jsonResp || typeof jsonResp !== 'object')
197
+ throw new Error('invalid json response');
198
+ await this.transform(jsonResp, vars);
199
+ } catch (e) {
200
+ return newError(e, 500);
201
+ }
202
+
203
+ // also no caching for errors
204
+ if (jsonResp.errors) {
205
+ return Response.json(jsonResp, { headers: respHeaders });
206
+ }
207
+
208
+ // now we have a valid json resp.
209
+ // should we cache it?
210
+ if (cacheKey && this.cacheIfFn?.(jsonResp.data, vars)) {
211
+ try {
212
+ await caching.setCache(cacheKey, jsonResp);
213
+ if (logInfo) console.log(logInfo + ' set cache');
214
+ } catch (e) {
215
+ console.error('could not cache gql response', e);
216
+ }
217
+ // if caching is enabled but not used we warn
218
+ } else if (cacheKey && logInfo) {
219
+ console.warn('!! ' + logInfo + ' caching not allowed');
220
+ }
221
+
222
+ return Response.json(jsonResp, { headers: respHeaders });
223
+ }
224
+ }
@@ -0,0 +1,37 @@
1
+ import { QueryVar } from '../../queries/vars.js';
2
+ import CrelteServerRequest from '../CrelteServer.js';
3
+ import ServerRouter from '../ServerRouter.js';
4
+ import { HandleFn, newError, validateVars } from './routes.js';
5
+
6
+ // only internal
7
+ export default class QueryHandleRoute {
8
+ name: string;
9
+ handleFn: HandleFn;
10
+ vars: Record<string, QueryVar> | null;
11
+
12
+ constructor(
13
+ name: string,
14
+ handleFn: HandleFn,
15
+ vars: Record<string, QueryVar> | null,
16
+ ) {
17
+ this.name = name;
18
+ this.handleFn = handleFn;
19
+ this.vars = vars;
20
+ }
21
+
22
+ async handle(
23
+ cs: ServerRouter,
24
+ csr: CrelteServerRequest,
25
+ ): Promise<Response> {
26
+ let vars: Record<string, any>;
27
+ try {
28
+ const reqVars = await csr.req.json();
29
+ vars = validateVars(this.vars, reqVars, cs);
30
+ } catch (e) {
31
+ return newError(e, 400);
32
+ }
33
+
34
+ const res = await this.handleFn(csr, vars);
35
+ return Response.json({ data: res });
36
+ }
37
+ }