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
@@ -1,8 +1,10 @@
1
1
  import ServerRouter from '../ServerRouter.js';
2
2
  import QueriesCaching from './QueriesCaching.js';
3
- import { CacheIfFn, QueryRoute, TransformFn } from './routes.js';
3
+ import { CacheIfFn, HandleFn, TransformFn } from './routes.js';
4
4
  import { isQueryVar, QueryVar } from '../../queries/vars.js';
5
5
  import { Platform } from '../platform.js';
6
+ import QueryHandleRoute from './QueryHandleRoute.js';
7
+ import QueryGqlRoute from './QueryGqlRoute.js';
6
8
 
7
9
  type ModQuery = {
8
10
  default: { name: string };
@@ -13,17 +15,20 @@ type ModTs = {
13
15
  variables?: any;
14
16
  caching?: any;
15
17
  transform?: any;
18
+ handle?: any;
16
19
  };
17
20
 
18
21
  type ModQueries = Record<string, ModQuery | ModTs>;
19
22
 
20
- type PreRoute = {
23
+ type RouteBuilder = {
21
24
  query: string | null;
22
25
  jsFile: string | null;
23
26
  vars: Record<string, QueryVar> | null;
27
+ /** corresponds to the caching export */
24
28
  cacheIfFn: CacheIfFn | null;
25
29
  preventCaching: boolean;
26
30
  transformFn: TransformFn | null;
31
+ handleFn: HandleFn | null;
27
32
  };
28
33
 
29
34
  export async function initQueryRoutes(
@@ -33,59 +38,67 @@ export async function initQueryRoutes(
33
38
  ): Promise<void> {
34
39
  if (typeof mod.queries !== 'object') {
35
40
  throw new Error(
36
- "expected `export const queries = import.meta.glob('@/queries/*', { eager: true });` in server.js",
41
+ 'expected `export const queries = import.meta.glob(' +
42
+ "'@/queries/*', { eager: true });` in server.js",
37
43
  );
38
44
  }
39
45
 
40
46
  const debugCaching = !!mod?.debugCaching;
41
47
  const modQueries: ModQueries = mod.queries;
42
48
 
43
- const preRoutes: Map<string, PreRoute> = new Map();
49
+ const routeBuilders: Map<string, RouteBuilder> = new Map();
44
50
 
45
51
  for (const [file, mq] of Object.entries(modQueries)) {
46
52
  const filename = file.split('/').pop()!;
47
53
  const dotPos = filename.lastIndexOf('.');
48
54
  const name = filename.substring(0, dotPos);
49
55
 
50
- let preRoute = preRoutes.get(name);
51
- if (!preRoute) {
52
- preRoute = {
56
+ let routeBuilder = routeBuilders.get(name);
57
+ if (!routeBuilder) {
58
+ routeBuilder = {
53
59
  query: null,
54
60
  jsFile: null,
55
61
  vars: null,
56
62
  cacheIfFn: null,
57
63
  preventCaching: false,
58
64
  transformFn: null,
65
+ handleFn: null,
59
66
  };
60
- preRoutes.set(name, preRoute);
67
+ routeBuilders.set(name, routeBuilder);
61
68
  }
62
69
 
63
70
  // set the gql query (this can only happen once)
64
71
  if (filename.endsWith('.graphql')) {
65
- preRoute.query = (mq as ModQuery).query.query;
72
+ routeBuilder.query = (mq as ModQuery).query.query;
66
73
  continue;
67
74
  }
68
75
 
69
76
  // now check that only one file matches
70
- if (preRoute.jsFile) {
77
+ if (routeBuilder.jsFile) {
71
78
  throw new Error(
72
- `cannot have two files for the same query ${preRoute.jsFile} and ${filename}`,
79
+ `cannot have two files for the same query ${
80
+ routeBuilder.jsFile
81
+ } and ${filename}`,
73
82
  );
74
83
  }
75
84
 
76
85
  const mts = mq as ModTs;
77
86
  if (mts.variables) {
78
- preRoute.vars = parseVars(mts.variables);
87
+ routeBuilder.vars = parseVars(mts.variables);
79
88
  }
80
89
 
81
90
  if (mts.caching) {
82
- preRoute.cacheIfFn = parseCaching(mts.caching);
91
+ routeBuilder.cacheIfFn = parseCaching(mts.caching);
83
92
  } else if (typeof mts.caching === 'boolean') {
84
- preRoute.preventCaching = true;
93
+ routeBuilder.preventCaching = true;
85
94
  }
86
95
 
87
96
  if (mts.transform) {
88
- preRoute.transformFn = parseTransform(mts.transform);
97
+ routeBuilder.transformFn = parseTransform(mts.transform);
98
+ }
99
+
100
+ if (mts.handle) {
101
+ routeBuilder.handleFn = parseHandle(mts.handle);
89
102
  }
90
103
  }
91
104
 
@@ -93,14 +106,30 @@ export async function initQueryRoutes(
93
106
  debug: debugCaching,
94
107
  });
95
108
 
96
- for (const [name, pr] of preRoutes.entries()) {
97
- if (!pr.query) throw new Error(`no .graphql file for query ${name}`);
109
+ for (const [name, rb] of routeBuilders.entries()) {
110
+ if (rb.query) {
111
+ if (rb.handleFn) throw new Error('handle function not supported');
98
112
 
99
- const route = new QueryRoute(name, pr.query, pr);
113
+ const route = new QueryGqlRoute(name, rb.query, rb);
100
114
 
101
- router.post('/queries/' + route.name, async csr =>
102
- route.handle(caching, csr),
103
- );
115
+ router.post('/queries/' + route.name, csr =>
116
+ route.handle(caching, csr),
117
+ );
118
+ } else if (rb.handleFn) {
119
+ if (rb.cacheIfFn || rb.transformFn || rb.preventCaching)
120
+ throw new Error('caching or transform not supported');
121
+
122
+ const route = new QueryHandleRoute(name, rb.handleFn, rb.vars);
123
+
124
+ router.post('/queries/' + route.name, csr =>
125
+ route.handle(caching.router, csr),
126
+ );
127
+ } else {
128
+ throw new Error(
129
+ `query js/ts ${name} file needs to either have ` +
130
+ 'a .graphql file or a handle function',
131
+ );
132
+ }
104
133
  }
105
134
  }
106
135
 
@@ -137,3 +166,10 @@ function parseTransform(transform: any): TransformFn {
137
166
 
138
167
  return transform;
139
168
  }
169
+
170
+ function parseHandle(handle: any): HandleFn {
171
+ if (typeof handle !== 'function')
172
+ throw new Error('handle should be a function');
173
+
174
+ return handle;
175
+ }
@@ -1,9 +1,6 @@
1
+ import { QueryVar } from '../../queries/vars.js';
1
2
  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
3
  import ServerRouter from '../ServerRouter.js';
6
- import { calcKey } from '../../ssr/index.js';
7
4
 
8
5
  export type CacheIfFn = (response: any, vars: Record<string, any>) => boolean;
9
6
 
@@ -16,233 +13,32 @@ export type TransformFn = (
16
13
  vars: Record<string, any>,
17
14
  ) => void | any | Promise<void | any>;
18
15
 
19
- export type QueryRouteArgs = {
20
- vars: Record<string, QueryVar> | null;
21
- cacheIfFn: CacheIfFn | null;
22
- preventCaching: boolean;
23
- transformFn: TransformFn | null;
24
- };
25
-
26
- // only internal
27
- export class QueryRoute {
28
- name: string;
29
- query: string;
30
- vars: Record<string, QueryVar> | null;
31
- cacheIfFn: CacheIfFn | null;
32
- transformFn: TransformFn | null;
33
-
34
- constructor(name: string, query: string, args: QueryRouteArgs) {
35
- if (args.cacheIfFn && !vars)
36
- throw new Error(
37
- 'queryRoute: ' +
38
- name +
39
- ' cannot have caching function if there are no ' +
40
- 'variables defined',
41
- );
42
-
43
- this.name = name;
44
- this.query = query;
45
- this.vars = args.vars;
46
- this.cacheIfFn = args.cacheIfFn;
47
- this.transformFn = args.transformFn;
48
-
49
- if (args.preventCaching) {
50
- if (this.cacheIfFn) throw new Error('unreachable');
51
- // prevent filling defaults
52
- return;
53
- }
54
-
55
- // add default vars and cacheIfFn if we know the route
56
- if (this.name === 'entry') this.fillEntryDefaults();
57
- else if (this.name === 'global') this.fillGlobalDefaults();
58
- else this.fillBasicDefaults();
59
- }
60
-
61
- private fillEntryDefaults() {
62
- if (this.vars) return;
63
-
64
- // the _setName step happens in parseVars which happens before setting
65
- // the defaults
66
- this.vars = {
67
- siteId: vars.siteId().z_setName('siteId'),
68
- uri: vars.string().z_setName('uri'),
69
- };
70
- this.cacheIfFn = res => !!extractEntry(res);
71
- }
72
-
73
- private fillGlobalDefaults() {
74
- if (this.vars) return;
75
-
76
- this.vars = { siteId: vars.siteId().z_setName('siteId') };
77
- this.cacheIfFn = () => true;
78
- }
79
-
80
- /**
81
- * This adds caching to queries containing `query {` or
82
- * `query ($siteId: [QueryArgument) {` without any additional vars
83
- */
84
- private fillBasicDefaults() {
85
- if (this.vars) return;
86
-
87
- const NO_VAR_TEST = /(^|\s)query\s*{/;
88
- const SITE_ID_VAR_TEST =
89
- /(^|\s)query\s*\(\s*\$siteId\s*:\s*\[\s*QueryArgument\s*\]\s*\)\s*{/;
90
-
91
- if (NO_VAR_TEST.test(this.query)) {
92
- this.vars = {};
93
- this.cacheIfFn = () => true;
94
- } else if (SITE_ID_VAR_TEST.test(this.query)) {
95
- this.vars = { siteId: vars.siteId().z_setName('siteId') };
96
- this.cacheIfFn = () => true;
97
- }
98
- }
99
-
100
- /**
101
- * Returns the validated variables if some vars where defined
102
- * else just returns all vars
103
- */
104
- validateVars(vars: any, cs: ServerRouter): Record<string, any> {
105
- if (!vars || typeof vars !== 'object')
106
- throw new Error('expected an object as vars');
107
-
108
- if (!this.vars) return vars;
109
-
110
- const nVars: Record<string, any> = {};
111
-
112
- for (const [k, v] of Object.entries(this.vars)) {
113
- nVars[k] = v.validValue(vars[k], cs);
114
- }
115
-
116
- return nVars;
117
- }
118
-
119
- private async transform(
120
- jsonResp: Record<string, any>,
121
- vars: Record<string, any>,
122
- ): Promise<void> {
123
- if (!this.transformFn || !jsonResp.data) return;
124
-
125
- const transformed = await this.transformFn(jsonResp.data, vars);
126
- if (typeof transformed !== 'undefined') jsonResp.data = transformed;
16
+ export type HandleFn = (
17
+ csr: CrelteServerRequest,
18
+ vars: Record<string, any>,
19
+ ) => Promise<any> | any;
20
+
21
+ /**
22
+ * Returns the validated variables if some vars where defined
23
+ * else just returns all vars
24
+ */
25
+ export function validateVars(
26
+ qvars: Record<string, QueryVar> | null,
27
+ vars: any,
28
+ cs: ServerRouter,
29
+ ): Record<string, any> {
30
+ if (!vars || typeof vars !== 'object')
31
+ throw new Error('expected an object as vars');
32
+
33
+ if (!qvars) return vars;
34
+
35
+ const nVars: Record<string, any> = {};
36
+
37
+ for (const [k, v] of Object.entries(qvars)) {
38
+ nVars[k] = v.validValue(vars[k], cs);
127
39
  }
128
40
 
129
- async handle(
130
- caching: QueriesCaching,
131
- csr: CrelteServerRequest,
132
- ): Promise<Response> {
133
- let vars;
134
- try {
135
- const reqVars = await csr.req.json();
136
- vars = this.validateVars(reqVars, caching.router);
137
- if ('qName' in vars || 'xCraftSite' in vars)
138
- throw new Error(
139
- 'qName and xCraftSite are reserved variable names',
140
- );
141
- } catch (e) {
142
- return newError(e, 400);
143
- }
144
-
145
- let logInfo: string | null = null;
146
- if (caching.debug) {
147
- logInfo = `[queries: ${this.name}] vars: ${JSON.stringify(vars)}`;
148
- }
149
-
150
- let previewToken: string | null = null;
151
- let siteToken: string | null = null;
152
-
153
- const reqSearch = new URL(csr.req.url).searchParams;
154
-
155
- if (reqSearch.has('token')) {
156
- previewToken = reqSearch.get('token');
157
- } else if (reqSearch.has('siteToken')) {
158
- siteToken = reqSearch.get('siteToken');
159
- }
160
-
161
- // check for x-craft-site header and pass it on
162
- const xCraftSite = csr.req.headers.get('X-Craft-Site');
163
-
164
- let cacheKey: string | null = null;
165
- const useCache = !previewToken && caching.isEnabled();
166
- if (useCache) {
167
- cacheKey = await calcKey({ ...vars, qName: this.name, xCraftSite });
168
- const cached = await caching.getCache(cacheKey);
169
-
170
- if (logInfo) console.log(`${logInfo} ${cached ? 'hit' : 'miss'}`);
171
-
172
- // we found something in the cache
173
- if (cached) return Response.json(cached);
174
- }
175
-
176
- const headers: Record<string, string> = {
177
- 'Content-Type': 'application/json',
178
- };
179
-
180
- const auth = csr.getEnv('ENDPOINT_TOKEN');
181
- if (auth) headers['Authorization'] = 'Bearer ' + auth;
182
-
183
- const url = new URL(csr.getEnv('ENDPOINT_URL'));
184
- if (previewToken) url.searchParams.set('token', previewToken);
185
- if (siteToken) url.searchParams.set('siteToken', siteToken);
186
-
187
- const xDebug = csr.req.headers.get('X-Debug');
188
- if (xDebug) headers['X-Debug'] = xDebug;
189
-
190
- if (xCraftSite) headers['X-Craft-Site'] = xCraftSite;
191
-
192
- // now execute the gql request
193
- let resp: Response;
194
- try {
195
- resp = await fetch(url, {
196
- method: 'POST',
197
- headers,
198
- body: JSON.stringify({
199
- query: this.query,
200
- variables: vars,
201
- }),
202
- });
203
-
204
- // if the response is not ok we don't cache anything
205
- // and just return the response
206
- if (!resp.ok) return resp;
207
- } catch (e) {
208
- return newError(e, 500);
209
- }
210
-
211
- const respHeaders: Record<string, string> = {};
212
- const xDebugLink = resp.headers.get('X-Debug-Link');
213
- if (xDebugLink) respHeaders['X-Debug'] = xDebugLink;
214
-
215
- let jsonResp: Record<string, any>;
216
- try {
217
- jsonResp = await resp.json();
218
- if (!jsonResp || typeof jsonResp !== 'object')
219
- throw new Error('invalid json response');
220
- await this.transform(jsonResp, vars);
221
- } catch (e) {
222
- return newError(e, 500);
223
- }
224
-
225
- // also no caching for errors
226
- if (jsonResp.errors) {
227
- return Response.json(jsonResp, { headers: respHeaders });
228
- }
229
-
230
- // now we have a valid json resp.
231
- // should we cache it?
232
- if (cacheKey && this.cacheIfFn?.(jsonResp.data, vars)) {
233
- try {
234
- await caching.setCache(cacheKey, jsonResp);
235
- if (logInfo) console.log(logInfo + ' set cache');
236
- } catch (e) {
237
- console.error('could not cache gql response', e);
238
- }
239
- // if caching is generally disabled we don't warn
240
- } else if (cacheKey && logInfo) {
241
- console.warn('!! ' + logInfo + ' caching not allowed');
242
- }
243
-
244
- return Response.json(jsonResp, { headers: respHeaders });
245
- }
41
+ return nVars;
246
42
  }
247
43
 
248
44
  export function newError(e: any, status: number): Response {