crelte 0.5.15 → 0.6.0-alpha.2

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 (101) hide show
  1. package/dist/blocks/Blocks.svelte +5 -4
  2. package/dist/blocks/Blocks.svelte.d.ts +4 -19
  3. package/dist/blocks/Blocks.svelte.d.ts.map +1 -1
  4. package/dist/crelte.d.ts +2 -2
  5. package/dist/crelte.d.ts.map +1 -1
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/init/InternalApp.d.ts.map +1 -1
  9. package/dist/init/InternalApp.js +2 -2
  10. package/dist/init/server.js +4 -4
  11. package/dist/loadData/loadData.d.ts +1 -1
  12. package/dist/loadData/loadData.d.ts.map +1 -1
  13. package/dist/node/index.d.ts.map +1 -1
  14. package/dist/node/index.js +15 -11
  15. package/dist/queries/Queries.d.ts +16 -27
  16. package/dist/queries/Queries.d.ts.map +1 -1
  17. package/dist/queries/Queries.js +13 -20
  18. package/dist/queries/gql.d.ts +2 -2
  19. package/dist/queries/gql.d.ts.map +1 -1
  20. package/dist/queries/gql.js +3 -2
  21. package/dist/queries/index.d.ts +13 -13
  22. package/dist/queries/index.d.ts.map +1 -1
  23. package/dist/queries/index.js +2 -2
  24. package/dist/queries/vars.d.ts +3 -3
  25. package/dist/queries/vars.d.ts.map +1 -1
  26. package/dist/queries/vars.js +3 -3
  27. package/dist/routing/route/BaseRoute.d.ts +4 -0
  28. package/dist/routing/route/BaseRoute.d.ts.map +1 -1
  29. package/dist/routing/route/BaseRoute.js +6 -0
  30. package/dist/routing/router/BaseRouter.d.ts.map +1 -1
  31. package/dist/routing/router/BaseRouter.js +4 -3
  32. package/dist/routing/router/ClientRouter.d.ts +1 -0
  33. package/dist/routing/router/ClientRouter.d.ts.map +1 -1
  34. package/dist/routing/router/ClientRouter.js +22 -7
  35. package/dist/server/CrelteServer.d.ts +1 -1
  36. package/dist/server/CrelteServer.d.ts.map +1 -1
  37. package/dist/server/ServerRouter.d.ts +1 -1
  38. package/dist/server/ServerRouter.d.ts.map +1 -1
  39. package/dist/server/queries/QueriesCaching.d.ts +0 -1
  40. package/dist/server/queries/QueriesCaching.d.ts.map +1 -1
  41. package/dist/server/queries/QueriesCaching.js +0 -2
  42. package/dist/server/queries/QueryGqlRoute.d.ts +2 -0
  43. package/dist/server/queries/QueryGqlRoute.d.ts.map +1 -1
  44. package/dist/server/queries/QueryGqlRoute.js +22 -14
  45. package/dist/server/queries/QueryHandleRoute.d.ts +1 -2
  46. package/dist/server/queries/QueryHandleRoute.d.ts.map +1 -1
  47. package/dist/server/queries/QueryHandleRoute.js +2 -2
  48. package/dist/server/queries/queries.d.ts.map +1 -1
  49. package/dist/server/queries/queries.js +2 -1
  50. package/dist/server/queries/routes.d.ts +3 -4
  51. package/dist/server/queries/routes.d.ts.map +1 -1
  52. package/dist/server/queries/routes.js +3 -3
  53. package/dist/server/shared.d.ts +5 -4
  54. package/dist/server/shared.d.ts.map +1 -1
  55. package/dist/server/shared.js +26 -16
  56. package/dist/ssr/SsrCache.d.ts +2 -2
  57. package/dist/ssr/SsrCache.d.ts.map +1 -1
  58. package/dist/ssr/SsrCache.js +10 -16
  59. package/dist/std/encdec/escaping.d.ts +2 -0
  60. package/dist/std/encdec/escaping.d.ts.map +1 -0
  61. package/dist/std/encdec/escaping.js +41 -0
  62. package/dist/std/encdec/index.d.ts +13 -0
  63. package/dist/std/encdec/index.d.ts.map +1 -0
  64. package/dist/std/encdec/index.js +15 -0
  65. package/dist/std/index.d.ts.map +1 -1
  66. package/dist/utils.d.ts +1 -0
  67. package/dist/utils.d.ts.map +1 -1
  68. package/dist/utils.js +17 -0
  69. package/dist/vite/index.d.ts +1 -5
  70. package/dist/vite/index.d.ts.map +1 -1
  71. package/dist/vite/index.js +39 -79
  72. package/package.json +12 -7
  73. package/src/blocks/Blocks.svelte +4 -3
  74. package/src/crelte.ts +2 -2
  75. package/src/index.ts +2 -2
  76. package/src/init/InternalApp.ts +3 -4
  77. package/src/init/server.ts +4 -4
  78. package/src/loadData/loadData.ts +0 -2
  79. package/src/node/index.ts +24 -9
  80. package/src/queries/Queries.ts +31 -36
  81. package/src/queries/gql.ts +7 -3
  82. package/src/queries/index.ts +19 -16
  83. package/src/queries/vars.ts +5 -7
  84. package/src/routing/route/BaseRoute.ts +8 -7
  85. package/src/routing/router/BaseRouter.ts +6 -3
  86. package/src/routing/router/ClientRouter.ts +29 -8
  87. package/src/server/CrelteServer.ts +1 -1
  88. package/src/server/ServerRouter.ts +1 -1
  89. package/src/server/queries/QueriesCaching.ts +0 -2
  90. package/src/server/queries/QueryGqlRoute.ts +24 -20
  91. package/src/server/queries/QueryHandleRoute.ts +2 -6
  92. package/src/server/queries/queries.ts +2 -3
  93. package/src/server/queries/routes.ts +9 -6
  94. package/src/server/shared.ts +34 -16
  95. package/src/ssr/SsrCache.ts +13 -23
  96. package/src/std/encdec/escaping.ts +47 -0
  97. package/src/std/encdec/index.ts +16 -0
  98. package/src/std/index.ts +1 -6
  99. package/src/utils.ts +21 -0
  100. package/src/vite/index.ts +51 -87
  101. package/types.d.ts +1 -1
@@ -17,44 +17,28 @@ export type QueriesOptions = {
17
17
  /**
18
18
  * A GraphQL query
19
19
  *
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}.
20
+ * When importing a graphql file you will get a string with the name of the
21
+ * query or you can use the {@link gql} template function to create
22
+ * an {@link GqlQuery}.
23
+ * Alternatively you can use the name of the query directly, note thought
24
+ * that this will not reload when you're query changes.
27
25
  */
28
- export type Query = InlineQuery | NamedQuery;
26
+ export type Query = string | GqlQuery;
29
27
 
30
28
  /**
31
29
  * You should never create this type directly. It is returned from
32
30
  * the {@link gql} template function.
33
31
  */
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.
45
- *
46
- * #### Note
47
- * This needs to match the name of the js/ts file.
48
- */
49
- export function namedQuery(name: string): NamedQuery {
50
- return { queryName: name };
51
- }
32
+ export type GqlQuery = { path?: string; query: string };
52
33
 
53
- /** Returns true if the passed object is a GraphQlQuery */
34
+ /** Returns true if the passed object is a Query */
54
35
  export function isQuery(obj: any): obj is Query {
55
- if (typeof obj !== 'object' || obj === null) return false;
56
-
57
- return typeof obj.query === 'string' || typeof obj.queryName === 'string';
36
+ return (
37
+ typeof obj === 'string' ||
38
+ (typeof obj === 'object' &&
39
+ obj !== null &&
40
+ typeof obj.query === 'string')
41
+ );
58
42
  }
59
43
 
60
44
  /**
@@ -74,11 +58,18 @@ export type QueryOptions = {
74
58
  bearerToken?: string;
75
59
 
76
60
  /**
77
- * Configure caching
61
+ * Configure Crelte's local query caching.
78
62
  * @default true
79
63
  */
80
64
  caching?: boolean;
81
65
 
66
+ /**
67
+ * Allow Craft's configured GraphQL caching. Set false to bypass it.
68
+ * Does not affect Crelte's caching.
69
+ * @default true
70
+ */
71
+ craftCaching?: boolean;
72
+
82
73
  /**
83
74
  * Usually automatically set
84
75
  */
@@ -135,7 +126,7 @@ export default class Queries {
135
126
  }
136
127
 
137
128
  /**
138
- * Create a new Queries instance that always intented to call an
129
+ * Create a new Queries instance that is always intented to call an
139
130
  * external GraphQl endpoint.
140
131
  */
141
132
  static newExternal(endpoint: string, bearerToken?: string): Queries {
@@ -181,6 +172,7 @@ export default class Queries {
181
172
  bearerToken,
182
173
  xCraftSiteId,
183
174
  caching: opts.caching ?? true,
175
+ craftCaching: opts.craftCaching ?? true,
184
176
  });
185
177
  }
186
178
 
@@ -199,6 +191,7 @@ type InnerQueryOptions = {
199
191
  bearerToken: string | null;
200
192
  xCraftSiteId: number | null;
201
193
  caching: boolean;
194
+ craftCaching: boolean;
202
195
  };
203
196
 
204
197
  class Inner {
@@ -296,13 +289,13 @@ class Inner {
296
289
  ): Promise<unknown> {
297
290
  let logName: string, url: URL;
298
291
 
299
- if ('queryName' in query) {
292
+ if (typeof query === 'string') {
300
293
  if (!this.frontend)
301
294
  throw new Error('only inline queries supported');
302
295
 
303
- logName = `query (server: ${query.queryName})`;
296
+ logName = `query (server: ${query})`;
304
297
  url = new URL(this.frontend);
305
- url.pathname = '/queries/' + query.queryName;
298
+ url.pathname = '/queries/' + query;
306
299
  } else {
307
300
  logName = `query (${query.path ?? 'unknown'})`;
308
301
  url = new URL(this.endpoint);
@@ -320,6 +313,8 @@ class Inner {
320
313
  if (opts.xCraftSiteId)
321
314
  headers['X-Craft-Site'] = opts.xCraftSiteId.toString();
322
315
 
316
+ if (!opts.craftCaching) headers['X-Craft-GQL-Cache'] = 'no-cache';
317
+
323
318
  if (this.loggingRequests) {
324
319
  console.log(logName + ' to:', url.toString(), vars, headers);
325
320
  headers['X-Debug'] = 'enable';
@@ -329,7 +324,7 @@ class Inner {
329
324
  if (this.loggingTiming) timing = Date.now();
330
325
 
331
326
  let body: any;
332
- if ('queryName' in query) {
327
+ if (typeof query === 'string') {
333
328
  body = vars;
334
329
  } else {
335
330
  body = { query: query.query, variables: vars };
@@ -1,4 +1,4 @@
1
- import { InlineQuery, isQuery } from './Queries.js';
1
+ import { GqlQuery } from './Queries.js';
2
2
 
3
3
  /**
4
4
  * Create a GraphQL query string with variables.
@@ -13,7 +13,7 @@ import { InlineQuery, isQuery } from './Queries.js';
13
13
  export function gql(
14
14
  strings: TemplateStringsArray | string[] | string,
15
15
  ...keys: unknown[]
16
- ): InlineQuery {
16
+ ): GqlQuery {
17
17
  if (typeof strings === 'string') strings = [strings];
18
18
 
19
19
  let query = '';
@@ -24,7 +24,11 @@ export function gql(
24
24
  const variable = keys[i];
25
25
 
26
26
  // nesting support
27
- if (isQuery(variable) && 'query' in variable) {
27
+ if (
28
+ typeof variable === 'object' &&
29
+ variable !== null &&
30
+ 'query' in variable
31
+ ) {
28
32
  query += variable.query;
29
33
  } else if (typeof variable === 'string') {
30
34
  query += variable;
@@ -1,14 +1,11 @@
1
1
  import Queries, {
2
- InlineQuery,
2
+ GqlQuery,
3
3
  isQuery,
4
- namedQuery,
5
- NamedQuery,
6
4
  QueriesOptions,
7
5
  Query,
8
6
  QueryOptions,
9
7
  } from '../queries/Queries.js';
10
8
  import type CrelteServerRequest from '../server/CrelteServer.js';
11
- import ServerRouter from '../server/ServerRouter.js';
12
9
  import { gql } from './gql.js';
13
10
  import QueryError, { QueryErrorResponse } from './QueryError.js';
14
11
  import { QueryVar, ValidIf, vars, varsIdsEqual } from './vars.js';
@@ -18,9 +15,7 @@ export {
18
15
  type QueriesOptions,
19
16
  type QueryOptions,
20
17
  type Query,
21
- type InlineQuery as GqlQuery,
22
- type NamedQuery,
23
- namedQuery,
18
+ type GqlQuery,
24
19
  isQuery,
25
20
  QueryError,
26
21
  type QueryErrorResponse,
@@ -53,14 +48,14 @@ export type InferVariableTypes<T> = {
53
48
  * category: vars.id()
54
49
  * };
55
50
  *
56
- * export const validVars: ValidVars<typeof variables> = (vars, sr) => {
51
+ * export const validVars: ValidVars<typeof variables> = (vars, csr) => {
57
52
  * if (!vars.category === 5) throw new Error('category needs to be 5');
58
53
  * };
59
54
  * ```
60
55
  */
61
56
  export type ValidVars<T extends Record<string, QueryVar<any>>> = (
62
57
  vars: InferVariableTypes<T>,
63
- sr: ServerRouter,
58
+ csr: CrelteServerRequest,
64
59
  ) => void | boolean;
65
60
 
66
61
  /**
@@ -76,12 +71,18 @@ export type ValidVars<T extends Record<string, QueryVar<any>>> = (
76
71
  * };
77
72
  *
78
73
  * export const caching: Caching<typeof variables> =
79
- * (queryResponse, variables) => !!queryResponse.entries.length;
74
+ * (queryResponse, variables, csr) => !!queryResponse.entries.length;
80
75
  * ```
81
76
  */
82
77
  export type Caching<
83
78
  T extends Record<string, QueryVar<any>> = Record<string, QueryVar<any>>,
84
- > = boolean | ((response: any, vars: InferVariableTypes<T>) => boolean);
79
+ > =
80
+ | boolean
81
+ | ((
82
+ response: any,
83
+ vars: InferVariableTypes<T>,
84
+ csr: CrelteServerRequest,
85
+ ) => boolean);
85
86
 
86
87
  /** use {@link Transfrom} */
87
88
  export type TransformFn<
@@ -89,6 +90,7 @@ export type TransformFn<
89
90
  > = (
90
91
  response: any,
91
92
  vars: InferVariableTypes<T>,
93
+ csr: CrelteServerRequest,
92
94
  ) => void | any | Promise<void | any>;
93
95
 
94
96
  /**
@@ -96,11 +98,12 @@ export type TransformFn<
96
98
  *
97
99
  * #### Example
98
100
  * ```ts
99
- * export const transform: Transform<typeof variables> = (response, vars) => {
100
- * for (const entry of response.entries) {
101
- * entry.title = entry.title.toUpperCase();
102
- * }
103
- * };
101
+ * export const transform: Transform<typeof variables> =
102
+ * (response, vars, csr) => {
103
+ * for (const entry of response.entries) {
104
+ * entry.title = entry.title.toUpperCase();
105
+ * }
106
+ * };
104
107
  * ```
105
108
  */
106
109
  export type Transform<
@@ -1,4 +1,4 @@
1
- import ServerRouter from '../server/ServerRouter.js';
1
+ import CrelteServerRequest from '../server/CrelteServer.js';
2
2
 
3
3
  export const vars = {
4
4
  /**
@@ -65,14 +65,12 @@ export const vars = {
65
65
  siteId: (): QueryVar<number> =>
66
66
  new QueryVar()
67
67
  .number()
68
- .validIf(
69
- (num, router) => !!router.sites.find(site => site.id === num),
70
- ),
68
+ .validIf((num, csr) => !!csr.sites.find(site => site.id === num)),
71
69
  };
72
70
 
73
71
  /// either throw with an error which will get returned in a 400 response or
74
72
  // return false if the value is not valid
75
- export type ValidIf<T> = (v: T, sr: ServerRouter) => boolean;
73
+ export type ValidIf<T> = (v: T, csr: CrelteServerRequest) => boolean;
76
74
 
77
75
  export class QueryVar<T = any> {
78
76
  private name: string | null;
@@ -135,7 +133,7 @@ export class QueryVar<T = any> {
135
133
  return this;
136
134
  }
137
135
 
138
- validValue(v: any, cs: ServerRouter): T | null {
136
+ validValue(v: any, csr: CrelteServerRequest): T | null {
139
137
  // undefined is treated as null
140
138
  if (typeof v === 'undefined') v = null;
141
139
 
@@ -229,7 +227,7 @@ export class QueryVar<T = any> {
229
227
  throw new Error('uknown type ' + this.type);
230
228
  }
231
229
 
232
- if (!this.validIfFn(v, cs))
230
+ if (!this.validIfFn(v, csr))
233
231
  throw new Error(`variable ${this.name} is not valid`);
234
232
 
235
233
  return v;
@@ -31,13 +31,7 @@ export type BaseRouteOptions = {
31
31
  * `replace` and `push` will not call loadData
32
32
  */
33
33
  export type RouteOrigin =
34
- | 'init'
35
- | 'manual'
36
- | 'preload'
37
- | 'click'
38
- | 'pop'
39
- | 'replace'
40
- | 'push';
34
+ 'init' | 'manual' | 'preload' | 'click' | 'pop' | 'replace' | 'push';
41
35
 
42
36
  /**
43
37
  * A Route contains information about the current page for example the url and
@@ -298,6 +292,13 @@ export default class BaseRoute {
298
292
  this.z_context[key] = value;
299
293
  }
300
294
  }
295
+ /**
296
+ * Returns the site preview token, or null if it is missing or empty.
297
+ */
298
+ getSiteToken(): string | null {
299
+ return this.search.get('siteToken') || null;
300
+ }
301
+
301
302
  /**
302
303
  * Returns true if the route is in live preview mode
303
304
  */
@@ -235,9 +235,12 @@ export default class BaseRouter {
235
235
  const req = this.targetToRequest(target, { origin: 'preload' });
236
236
  const current = this.route.get();
237
237
 
238
- // if the origin matches, the route will be able to be loaded
239
- // so let's preload it
240
- if (current && current.url.origin === req.url.origin) {
238
+ // Preload only within the current origin and site preview context.
239
+ if (
240
+ current &&
241
+ current.url.origin === req.url.origin &&
242
+ current.getSiteToken() === req.getSiteToken()
243
+ ) {
241
244
  // todo i don't wan't to send a CrelteRequest?
242
245
  // todo, it would be nice if this could not have any side effects
243
246
  // but at the moment cr.router.open will cause a redirect
@@ -20,6 +20,7 @@ export default class ClientRouter extends BaseRouter {
20
20
 
21
21
  this.scrollDebounceTimeout = null;
22
22
  this.preloadOnMouseOver = opts.preloadOnMouseOver;
23
+
23
24
  this.onError = () => {};
24
25
  }
25
26
 
@@ -82,13 +83,7 @@ export default class ClientRouter extends BaseRouter {
82
83
  }
83
84
  }
84
85
 
85
- // if the domain of the current site is different than the domain of the
86
- // new site we need to do a window.location.href call
87
- if (
88
- (current && current.url.origin !== req.url.origin) ||
89
- // @ts-ignore
90
- import.meta.env.SSR
91
- ) {
86
+ if (this.requiresPageLoad(req)) {
92
87
  window.location.href = req.url.href;
93
88
  return;
94
89
  }
@@ -107,6 +102,11 @@ export default class ClientRouter extends BaseRouter {
107
102
  async pushRequest(req: Request, _opts: RequestOptions = {}) {
108
103
  const url = req.url;
109
104
 
105
+ if (this.requiresPageLoad(req)) {
106
+ window.location.href = url.href;
107
+ return;
108
+ }
109
+
110
110
  return this.handleRequest(req, route => {
111
111
  window.history.pushState(
112
112
  route.z_toState(),
@@ -119,6 +119,11 @@ export default class ClientRouter extends BaseRouter {
119
119
  async replaceRequest(req: Request, _opts: RequestOptions = {}) {
120
120
  const url = req.url;
121
121
 
122
+ if (this.requiresPageLoad(req)) {
123
+ window.location.replace(url.href);
124
+ return;
125
+ }
126
+
122
127
  return this.handleRequest(req, () => {
123
128
  window.history.replaceState(
124
129
  req.z_toState(),
@@ -132,6 +137,15 @@ export default class ClientRouter extends BaseRouter {
132
137
  window.history.back();
133
138
  }
134
139
 
140
+ private requiresPageLoad(req: Request): boolean {
141
+ const current = this.route.get();
142
+ return (
143
+ !!current &&
144
+ (current.url.origin !== req.url.origin ||
145
+ current.getSiteToken() !== req.getSiteToken())
146
+ );
147
+ }
148
+
135
149
  /**
136
150
  * This returns a route if it was handled else if an error occured
137
151
  * or the request was cancelled returns void
@@ -205,6 +219,11 @@ export default class ClientRouter extends BaseRouter {
205
219
  });
206
220
  req.z_fillFromState(e.state);
207
221
 
222
+ if (this.requiresPageLoad(req)) {
223
+ window.location.reload();
224
+ return;
225
+ }
226
+
208
227
  // todo handle errors
209
228
  this.handleRequest(req, () => {});
210
229
  });
@@ -249,7 +268,9 @@ export default class ClientRouter extends BaseRouter {
249
268
  updateScroll(cr: CrelteRequest, _route: Route): void {
250
269
  const req = cr.req;
251
270
 
252
- if (req.disableScroll) return;
271
+ if (req.disableScroll) {
272
+ return this.onScroll(); // make sure the current scrollY get's stored to the history
273
+ }
253
274
 
254
275
  // scroll to target
255
276
  let scrollTo:
@@ -86,7 +86,7 @@ export default class CrelteServerRequest {
86
86
  * returns an env variable from the craft/.env file.
87
87
  */
88
88
  getEnv(name: 'ENDPOINT_URL'): string;
89
- getEnv(name: 'CRAFT_WEB_URL'): string;
89
+ getEnv(name: 'CRAFT_BASE_CP_URL'): string;
90
90
  getEnv(name: 'FRONTEND_URL'): string;
91
91
  getEnv(name: string): string | null;
92
92
  getEnv(name: string): string | null {
@@ -81,7 +81,7 @@ export default class ServerRouter {
81
81
  * returns an env variable from the craft/.env file.
82
82
  */
83
83
  getEnv(name: 'ENDPOINT_URL'): string;
84
- getEnv(name: 'CRAFT_WEB_URL'): string;
84
+ getEnv(name: 'CRAFT_BASE_CP_URL'): string;
85
85
  getEnv(name: string): string | null;
86
86
  getEnv(name: string): string | null {
87
87
  return this.env.get(name) ?? null;
@@ -13,7 +13,6 @@ export default class QueriesCaching {
13
13
  private cacheDir: string | null;
14
14
  private validBearerToken: string | null;
15
15
  debug: boolean;
16
- router: ServerRouter;
17
16
 
18
17
  constructor(
19
18
  platform: Platform,
@@ -21,7 +20,6 @@ export default class QueriesCaching {
21
20
  opts?: QueriesCachingOptions,
22
21
  ) {
23
22
  this.platform = platform;
24
- this.router = cs;
25
23
  this.debug = !!opts?.debug;
26
24
 
27
25
  const enableCaching = cs.getEnv('CACHING') === 'true';
@@ -10,6 +10,7 @@ import {
10
10
  validateVars,
11
11
  ValidIfFn,
12
12
  } from './routes.js';
13
+ import { cryptoSha1Hash } from '../../utils.js';
13
14
 
14
15
  export type QueryGqlArgs = {
15
16
  vars: Record<string, QueryVar> | null;
@@ -23,6 +24,7 @@ export type QueryGqlArgs = {
23
24
  export default class QueryGqlRoute {
24
25
  name: string;
25
26
  query: string;
27
+ queryHash: string;
26
28
  vars: Record<string, QueryVar> | null;
27
29
  validIfFn: ValidIfFn | null;
28
30
  cacheIfFn: CacheIfFn | null;
@@ -39,6 +41,7 @@ export default class QueryGqlRoute {
39
41
 
40
42
  this.name = name;
41
43
  this.query = query;
44
+ this.queryHash = query;
42
45
  this.vars = args.vars;
43
46
  this.validIfFn = args.validIfFn;
44
47
  this.cacheIfFn = args.cacheIfFn;
@@ -56,6 +59,10 @@ export default class QueryGqlRoute {
56
59
  else this.fillBasicDefaults();
57
60
  }
58
61
 
62
+ async init() {
63
+ this.queryHash = await cryptoSha1Hash(this.query);
64
+ }
65
+
59
66
  private fillEntryDefaults() {
60
67
  if (this.vars) return;
61
68
 
@@ -121,16 +128,11 @@ export default class QueryGqlRoute {
121
128
  let vars: Record<string, any>;
122
129
  try {
123
130
  const reqVars = await csr.req.json();
124
- vars = validateVars(
125
- this.vars,
126
- reqVars,
127
- this.validIfFn,
128
- caching.router,
129
- );
131
+ vars = validateVars(this.vars, reqVars, this.validIfFn, csr);
130
132
 
131
- if ('qName' in vars || 'xCraftSite' in vars)
133
+ if (['qName', 'qHash', 'xCraftSite'].some(name => name in vars))
132
134
  throw new Error(
133
- 'qName and xCraftSite are reserved variable names',
135
+ 'qName, qHash and xCraftSite are reserved variable names',
134
136
  );
135
137
  } catch (e) {
136
138
  return newError(e, 400);
@@ -141,24 +143,22 @@ export default class QueryGqlRoute {
141
143
  logInfo = `[queries: ${this.name}] vars: ${JSON.stringify(vars)}`;
142
144
  }
143
145
 
144
- let previewToken: string | null = null;
145
- let siteToken: string | null = null;
146
-
147
146
  const reqSearch = new URL(csr.req.url).searchParams;
148
-
149
- if (reqSearch.has('token')) {
150
- previewToken = reqSearch.get('token');
151
- } else if (reqSearch.has('siteToken')) {
152
- siteToken = reqSearch.get('siteToken');
153
- }
147
+ const previewToken = reqSearch.get('token');
148
+ const siteToken = reqSearch.get('siteToken');
154
149
 
155
150
  // check for x-craft-site header and pass it on
156
151
  const xCraftSite = csr.req.headers.get('X-Craft-Site');
157
152
 
158
153
  let cacheKey: string | null = null;
159
- const useCache = !previewToken && caching.isEnabled();
154
+ const useCache = !previewToken && !siteToken && caching.isEnabled();
160
155
  if (useCache) {
161
- cacheKey = await calcKey({ ...vars, qName: this.name, xCraftSite });
156
+ cacheKey = await calcKey({
157
+ ...vars,
158
+ qName: this.name,
159
+ qHash: this.queryHash,
160
+ xCraftSite,
161
+ });
162
162
  const cached = await caching.getCache(cacheKey);
163
163
 
164
164
  if (logInfo) console.log(`${logInfo} ${cached ? 'hit' : 'miss'}`);
@@ -183,6 +183,10 @@ export default class QueryGqlRoute {
183
183
 
184
184
  if (xCraftSite) headers['X-Craft-Site'] = xCraftSite;
185
185
 
186
+ if (csr.req.headers.get('X-Craft-GQL-Cache') === 'no-cache') {
187
+ headers['X-Craft-GQL-Cache'] = 'no-cache';
188
+ }
189
+
186
190
  // now execute the gql request
187
191
  let resp: Response;
188
192
  try {
@@ -223,7 +227,7 @@ export default class QueryGqlRoute {
223
227
 
224
228
  // now we have a valid json resp.
225
229
  // should we cache it?
226
- if (cacheKey && this.cacheIfFn?.(jsonResp.data, vars)) {
230
+ if (cacheKey && this.cacheIfFn?.(jsonResp.data, vars, csr)) {
227
231
  try {
228
232
  await caching.setCache(cacheKey, jsonResp);
229
233
  if (logInfo) console.log(logInfo + ' set cache');
@@ -1,6 +1,5 @@
1
1
  import { QueryVar } from '../../queries/vars.js';
2
2
  import CrelteServerRequest from '../CrelteServer.js';
3
- import ServerRouter from '../ServerRouter.js';
4
3
  import { HandleFn, newError, validateVars, ValidIfFn } from './routes.js';
5
4
 
6
5
  // only internal
@@ -22,14 +21,11 @@ export default class QueryHandleRoute {
22
21
  this.vars = vars;
23
22
  }
24
23
 
25
- async handle(
26
- cs: ServerRouter,
27
- csr: CrelteServerRequest,
28
- ): Promise<Response> {
24
+ async handle(csr: CrelteServerRequest): Promise<Response> {
29
25
  let vars: Record<string, any>;
30
26
  try {
31
27
  const reqVars = await csr.req.json();
32
- vars = validateVars(this.vars, reqVars, this.validIfFn, cs);
28
+ vars = validateVars(this.vars, reqVars, this.validIfFn, csr);
33
29
  } catch (e) {
34
30
  return newError(e, 400);
35
31
  }
@@ -132,6 +132,7 @@ export async function initQueryRoutes(
132
132
  );
133
133
 
134
134
  const route = new QueryGqlRoute(name, rb.query, rb);
135
+ await route.init();
135
136
 
136
137
  router.post('/queries/' + route.name, csr =>
137
138
  route.handle(caching, csr),
@@ -147,9 +148,7 @@ export async function initQueryRoutes(
147
148
  rb.vars,
148
149
  );
149
150
 
150
- router.post('/queries/' + route.name, csr =>
151
- route.handle(caching.router, csr),
152
- );
151
+ router.post('/queries/' + route.name, csr => route.handle(csr));
153
152
  } else {
154
153
  throw new Error(
155
154
  `query js/ts ${name} file needs to either have ` +
@@ -1,13 +1,16 @@
1
1
  import { QueryVar } from '../../queries/vars.js';
2
2
  import CrelteServerRequest from '../CrelteServer.js';
3
- import ServerRouter from '../ServerRouter.js';
4
3
 
5
- export type CacheIfFn = (response: any, vars: Record<string, any>) => boolean;
4
+ export type CacheIfFn = (
5
+ response: any,
6
+ vars: Record<string, any>,
7
+ csr: CrelteServerRequest,
8
+ ) => boolean;
6
9
 
7
10
  /// Either throw or return a boolean
8
11
  export type ValidIfFn = (
9
12
  vars: Record<string, any>,
10
- sr: ServerRouter,
13
+ csr: CrelteServerRequest,
11
14
  ) => void | boolean;
12
15
 
13
16
  /// Anything other than returning undefined will replace the response
@@ -33,7 +36,7 @@ export function validateVars(
33
36
  qvars: Record<string, QueryVar> | null,
34
37
  vars: any,
35
38
  validIfFn: ValidIfFn | null,
36
- sr: ServerRouter,
39
+ csr: CrelteServerRequest,
37
40
  ): Record<string, any> {
38
41
  if (!vars || typeof vars !== 'object')
39
42
  throw new Error('expected an object as vars');
@@ -43,12 +46,12 @@ export function validateVars(
43
46
  const nVars: Record<string, any> = {};
44
47
 
45
48
  for (const [k, v] of Object.entries(qvars)) {
46
- nVars[k] = v.validValue(vars[k], sr);
49
+ nVars[k] = v.validValue(vars[k], csr);
47
50
  }
48
51
 
49
52
  if (validIfFn) {
50
53
  // or throw
51
- const valid = validIfFn(nVars, sr);
54
+ const valid = validIfFn(nVars, csr);
52
55
  if (typeof valid === 'boolean' && !valid)
53
56
  throw new Error('invalid variables for query');
54
57
  }