crelte 0.5.10 → 0.5.12
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/dist/bodyClass/BodyClass.d.ts +39 -0
- package/dist/bodyClass/BodyClass.d.ts.map +1 -0
- package/dist/bodyClass/BodyClass.js +51 -0
- package/dist/bodyClass/ClientBodyClass.d.ts +12 -0
- package/dist/bodyClass/ClientBodyClass.d.ts.map +1 -0
- package/dist/bodyClass/ClientBodyClass.js +57 -0
- package/dist/bodyClass/ServerBodyClass.d.ts +12 -0
- package/dist/bodyClass/ServerBodyClass.d.ts.map +1 -0
- package/dist/bodyClass/ServerBodyClass.js +47 -0
- package/dist/bodyClass/index.d.ts +2 -0
- package/dist/bodyClass/index.d.ts.map +1 -0
- package/dist/bodyClass/index.js +1 -0
- package/dist/cookies/ClientCookies.d.ts +8 -3
- package/dist/cookies/ClientCookies.d.ts.map +1 -1
- package/dist/cookies/ClientCookies.js +31 -7
- package/dist/cookies/Cookies.d.ts +42 -0
- package/dist/cookies/Cookies.d.ts.map +1 -0
- package/dist/cookies/Cookies.js +44 -0
- package/dist/cookies/ServerCookies.d.ts +3 -2
- package/dist/cookies/ServerCookies.d.ts.map +1 -1
- package/dist/cookies/ServerCookies.js +6 -4
- package/dist/cookies/index.d.ts +1 -25
- package/dist/cookies/index.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -1
- package/dist/crelte.d.ts +7 -1
- package/dist/crelte.d.ts.map +1 -1
- package/dist/crelte.js +2 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/init/client.d.ts +1 -8
- package/dist/init/client.d.ts.map +1 -1
- package/dist/init/client.js +26 -24
- package/dist/init/server.d.ts.map +1 -1
- package/dist/init/server.js +12 -4
- package/dist/init/shared.d.ts +1 -0
- package/dist/init/shared.d.ts.map +1 -1
- package/dist/init/shared.js +16 -5
- package/dist/loadData/Globals.d.ts.map +1 -1
- package/dist/node/index.js +1 -1
- package/dist/plugins/Events.d.ts +12 -7
- package/dist/plugins/Events.d.ts.map +1 -1
- package/dist/plugins/Plugins.d.ts +36 -1
- package/dist/plugins/Plugins.d.ts.map +1 -1
- package/dist/plugins/Plugins.js +32 -0
- package/dist/queries/Queries.d.ts +30 -5
- package/dist/queries/Queries.d.ts.map +1 -1
- package/dist/queries/Queries.js +19 -2
- package/dist/queries/gql.d.ts +2 -2
- package/dist/queries/gql.d.ts.map +1 -1
- package/dist/queries/index.d.ts +47 -2
- package/dist/queries/index.d.ts.map +1 -1
- package/dist/queries/index.js +2 -2
- package/dist/queries/vars.d.ts +2 -0
- package/dist/queries/vars.d.ts.map +1 -1
- package/dist/queries/vars.js +10 -0
- package/dist/routing/route/Request.d.ts +1 -1
- package/dist/routing/route/Request.d.ts.map +1 -1
- package/dist/routing/route/Request.js +7 -3
- package/dist/routing/router/BaseRouter.d.ts +2 -1
- package/dist/routing/router/BaseRouter.d.ts.map +1 -1
- package/dist/routing/router/BaseRouter.js +4 -2
- package/dist/routing/router/ClientRouter.d.ts.map +1 -1
- package/dist/routing/router/ClientRouter.js +21 -15
- package/dist/routing/router/Router.d.ts +2 -1
- package/dist/routing/router/Router.d.ts.map +1 -1
- package/dist/routing/router/Router.js +10 -13
- package/dist/routing/utils.d.ts +1 -0
- package/dist/routing/utils.d.ts.map +1 -1
- package/dist/routing/utils.js +1 -1
- package/dist/server/CrelteServer.d.ts +1 -0
- package/dist/server/CrelteServer.d.ts.map +1 -1
- package/dist/server/CrelteServer.js +5 -2
- package/dist/server/ServerRouter.d.ts.map +1 -1
- package/dist/server/ServerRouter.js +17 -7
- package/dist/server/queries/QueryGqlRoute.d.ts +28 -0
- package/dist/server/queries/QueryGqlRoute.d.ts.map +1 -0
- package/dist/server/queries/QueryGqlRoute.js +194 -0
- package/dist/server/queries/QueryHandleRoute.d.ts +12 -0
- package/dist/server/queries/QueryHandleRoute.d.ts.map +1 -0
- package/dist/server/queries/QueryHandleRoute.js +24 -0
- package/dist/server/queries/queries.d.ts.map +1 -1
- package/dist/server/queries/queries.js +42 -19
- package/dist/server/queries/routes.d.ts +7 -30
- package/dist/server/queries/routes.d.ts.map +1 -1
- package/dist/server/queries/routes.js +13 -199
- package/dist/std/stores/StagedWritable.d.ts +48 -0
- package/dist/std/stores/StagedWritable.d.ts.map +1 -0
- package/dist/std/stores/StagedWritable.js +84 -0
- package/dist/std/stores/index.d.ts +2 -1
- package/dist/std/stores/index.d.ts.map +1 -1
- package/dist/std/stores/index.js +2 -1
- package/dist/std/sync/Barrier.js +1 -1
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +11 -0
- package/package.json +5 -1
- package/src/bodyClass/BodyClass.ts +72 -0
- package/src/bodyClass/ClientBodyClass.ts +62 -0
- package/src/bodyClass/ServerBodyClass.ts +65 -0
- package/src/bodyClass/index.ts +1 -0
- package/src/cookies/ClientCookies.ts +41 -10
- package/src/cookies/Cookies.ts +70 -0
- package/src/cookies/ServerCookies.ts +9 -6
- package/src/cookies/index.ts +5 -29
- package/src/crelte.ts +9 -0
- package/src/index.ts +15 -1
- package/src/init/client.ts +29 -24
- package/src/init/server.ts +12 -4
- package/src/init/shared.ts +18 -6
- package/src/loadData/Globals.ts +1 -1
- package/src/node/index.ts +1 -1
- package/src/plugins/Events.ts +22 -7
- package/src/plugins/Plugins.ts +66 -1
- package/src/queries/Queries.ts +47 -14
- package/src/queries/gql.ts +2 -2
- package/src/queries/index.ts +71 -0
- package/src/queries/vars.ts +13 -0
- package/src/routing/route/Request.ts +11 -4
- package/src/routing/router/BaseRouter.ts +4 -2
- package/src/routing/router/ClientRouter.ts +26 -18
- package/src/routing/router/Router.ts +10 -11
- package/src/routing/utils.ts +1 -1
- package/src/server/CrelteServer.ts +4 -2
- package/src/server/ServerRouter.ts +18 -7
- package/src/server/queries/QueryGqlRoute.ts +224 -0
- package/src/server/queries/QueryHandleRoute.ts +37 -0
- package/src/server/queries/queries.ts +57 -21
- package/src/server/queries/routes.ts +25 -229
- package/src/std/stores/StagedWritable.ts +96 -0
- package/src/std/stores/index.ts +2 -1
- package/src/std/sync/Barrier.ts +1 -1
- package/src/utils.ts +15 -0
package/src/queries/vars.ts
CHANGED
|
@@ -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
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Site from '../Site.js';
|
|
2
|
-
import { objClone } from '../../utils.js';
|
|
2
|
+
import { objClone, promiseThen } from '../../utils.js';
|
|
3
3
|
import BaseRoute, { RouteOrigin } from './BaseRoute.js';
|
|
4
4
|
import Route, { TemplateModule } from './Route.js';
|
|
5
5
|
import { Entry } from '../../loadData/index.js';
|
|
@@ -254,8 +254,15 @@ class RenderBarrier {
|
|
|
254
254
|
const action = this.inner.add();
|
|
255
255
|
|
|
256
256
|
return {
|
|
257
|
-
ready:
|
|
258
|
-
if (!this.inner.isOpen())
|
|
257
|
+
ready: () => {
|
|
258
|
+
if (!this.inner.isOpen())
|
|
259
|
+
return promiseThen(
|
|
260
|
+
// wait for action.ready
|
|
261
|
+
action.ready(null),
|
|
262
|
+
// then return if it was cancelled
|
|
263
|
+
() => this.cancelled,
|
|
264
|
+
);
|
|
265
|
+
|
|
259
266
|
return this.cancelled;
|
|
260
267
|
},
|
|
261
268
|
remove: () => {
|
|
@@ -292,7 +299,7 @@ export type DelayRender = {
|
|
|
292
299
|
*
|
|
293
300
|
* @returns if the render was cancelled
|
|
294
301
|
*/
|
|
295
|
-
ready: () => Promise<boolean
|
|
302
|
+
ready: () => Promise<boolean> | boolean;
|
|
296
303
|
|
|
297
304
|
/**
|
|
298
305
|
* If youre not interested when the render happens anymore
|
|
@@ -123,8 +123,10 @@ export default class BaseRouter {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
|
-
*
|
|
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
|
|
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?
|
|
@@ -107,7 +107,7 @@ export default class ClientRouter extends BaseRouter {
|
|
|
107
107
|
async pushRequest(req: Request, _opts: RequestOptions = {}) {
|
|
108
108
|
const url = req.url;
|
|
109
109
|
|
|
110
|
-
return
|
|
110
|
+
return this.handleRequest(req, route => {
|
|
111
111
|
window.history.pushState(
|
|
112
112
|
route.z_toState(),
|
|
113
113
|
'',
|
|
@@ -119,18 +119,13 @@ export default class ClientRouter extends BaseRouter {
|
|
|
119
119
|
async replaceRequest(req: Request, _opts: RequestOptions = {}) {
|
|
120
120
|
const url = req.url;
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
});
|
|
130
|
-
} catch (e) {
|
|
131
|
-
console.warn('replacing route failed', e);
|
|
132
|
-
throw e;
|
|
133
|
-
}
|
|
122
|
+
return this.handleRequest(req, () => {
|
|
123
|
+
window.history.replaceState(
|
|
124
|
+
req.z_toState(),
|
|
125
|
+
'',
|
|
126
|
+
url.pathname + url.search + url.hash,
|
|
127
|
+
);
|
|
128
|
+
});
|
|
134
129
|
}
|
|
135
130
|
|
|
136
131
|
back(): void {
|
|
@@ -164,6 +159,7 @@ export default class ClientRouter extends BaseRouter {
|
|
|
164
159
|
const req = this.targetToRequest(link.href, {
|
|
165
160
|
origin: 'click',
|
|
166
161
|
context: { ...link.dataset },
|
|
162
|
+
disableScroll: attributeToBool(link, 'data-disable-scroll'),
|
|
167
163
|
});
|
|
168
164
|
const currRoute = this.route.get();
|
|
169
165
|
const routeEq =
|
|
@@ -189,7 +185,9 @@ export default class ClientRouter extends BaseRouter {
|
|
|
189
185
|
|
|
190
186
|
if (
|
|
191
187
|
link &&
|
|
192
|
-
|
|
188
|
+
// todo remove data-no-preload
|
|
189
|
+
!attributeToBool(link, 'data-no-preload') &&
|
|
190
|
+
!attributeToBool(link, 'data-disable-preload') &&
|
|
193
191
|
link.href
|
|
194
192
|
) {
|
|
195
193
|
this.preload(link.href);
|
|
@@ -202,11 +200,10 @@ export default class ClientRouter extends BaseRouter {
|
|
|
202
200
|
window.addEventListener('scroll', () => this.onScroll());
|
|
203
201
|
|
|
204
202
|
window.addEventListener('popstate', async e => {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
203
|
+
const req = this.targetToRequest(window.location.href, {
|
|
204
|
+
origin: 'pop',
|
|
205
|
+
});
|
|
208
206
|
req.z_fillFromState(e.state);
|
|
209
|
-
req.origin = 'pop';
|
|
210
207
|
|
|
211
208
|
// todo handle errors
|
|
212
209
|
this.handleRequest(req, () => {});
|
|
@@ -312,3 +309,14 @@ export default class ClientRouter extends BaseRouter {
|
|
|
312
309
|
}
|
|
313
310
|
}
|
|
314
311
|
}
|
|
312
|
+
|
|
313
|
+
function attributeToBool(el: HTMLElement, attr: string): boolean {
|
|
314
|
+
switch (el.getAttribute(attr)) {
|
|
315
|
+
case '':
|
|
316
|
+
case 'true':
|
|
317
|
+
return true;
|
|
318
|
+
case 'false':
|
|
319
|
+
default:
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -226,19 +226,18 @@ export default class Router {
|
|
|
226
226
|
});
|
|
227
227
|
if (!req) return;
|
|
228
228
|
|
|
229
|
-
|
|
230
|
-
return await this.inner.pushRequest(req, opts);
|
|
231
|
-
} catch (e) {
|
|
229
|
+
return this.inner.pushRequest(req, opts).catch(e => {
|
|
232
230
|
console.warn('pushing route failed', e);
|
|
233
231
|
throw e;
|
|
234
|
-
}
|
|
232
|
+
});
|
|
235
233
|
}
|
|
236
234
|
|
|
237
235
|
/**
|
|
238
236
|
* @deprecated use push instead
|
|
239
237
|
*/
|
|
240
238
|
pushState(route: Route | Request) {
|
|
241
|
-
|
|
239
|
+
if (import.meta.env.DEV)
|
|
240
|
+
console.warn('pushState is deprecated, use push instead');
|
|
242
241
|
this.push(route);
|
|
243
242
|
}
|
|
244
243
|
|
|
@@ -287,19 +286,18 @@ export default class Router {
|
|
|
287
286
|
});
|
|
288
287
|
if (!req) return;
|
|
289
288
|
|
|
290
|
-
|
|
291
|
-
return await this.inner.replaceRequest(req, opts);
|
|
292
|
-
} catch (e) {
|
|
289
|
+
return this.inner.replaceRequest(req, opts).catch(e => {
|
|
293
290
|
console.warn('replacing route failed', e);
|
|
294
291
|
throw e;
|
|
295
|
-
}
|
|
292
|
+
});
|
|
296
293
|
}
|
|
297
294
|
|
|
298
295
|
/**
|
|
299
296
|
* @deprecated use replace instead
|
|
300
297
|
*/
|
|
301
298
|
replaceState(route: Route | Request) {
|
|
302
|
-
|
|
299
|
+
if (import.meta.env.DEV)
|
|
300
|
+
console.warn('replaceState is deprecated, use replace instead');
|
|
303
301
|
this.replace(route);
|
|
304
302
|
}
|
|
305
303
|
|
|
@@ -409,7 +407,8 @@ export default class Router {
|
|
|
409
407
|
return nRouter;
|
|
410
408
|
}
|
|
411
409
|
|
|
412
|
-
|
|
410
|
+
/** @hidden */
|
|
411
|
+
z_requestCompleted() {
|
|
413
412
|
this._request = null;
|
|
414
413
|
}
|
|
415
414
|
}
|
package/src/routing/utils.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -24,6 +24,7 @@ export default class CrelteServerRequest {
|
|
|
24
24
|
private _sites: Site[];
|
|
25
25
|
private _langs: string[];
|
|
26
26
|
private _queries: Queries;
|
|
27
|
+
private _scookies: ServerCookies;
|
|
27
28
|
private _cookies: Cookies;
|
|
28
29
|
|
|
29
30
|
constructor(req: ServerRequest, opts: CrelteServerRequestOptions) {
|
|
@@ -37,7 +38,8 @@ export default class CrelteServerRequest {
|
|
|
37
38
|
this._queries = opts.queries.z_toRequest(
|
|
38
39
|
new Request(new URL(req.url), req.site),
|
|
39
40
|
);
|
|
40
|
-
this.
|
|
41
|
+
this._scookies = new ServerCookies(req.headers);
|
|
42
|
+
this._cookies = new Cookies(this._scookies);
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -145,6 +147,6 @@ export default class CrelteServerRequest {
|
|
|
145
147
|
|
|
146
148
|
/** @hidden */
|
|
147
149
|
z_finishResponse(resp: Response) {
|
|
148
|
-
|
|
150
|
+
this._scookies._populateHeaders(resp.headers);
|
|
149
151
|
}
|
|
150
152
|
}
|
|
@@ -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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
+
}
|