crelte 0.5.0 → 0.5.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.
@@ -7,7 +7,7 @@ export default class ServerCookies {
7
7
  requestCookies;
8
8
  setCookies;
9
9
  constructor(headers) {
10
- this.requestCookies = parseCookies(headers.get('cookie') ?? '');
10
+ this.requestCookies = parseCookies(headers.get('Cookie') ?? '');
11
11
  this.setCookies = new Map();
12
12
  }
13
13
  /// Rethrns the value of the cookie with the given name, or null if it doesn't exist.
@@ -27,7 +27,7 @@ export default class ServerCookies {
27
27
  }
28
28
  _populateHeaders(headers) {
29
29
  for (const setCookie of this.setCookies.values()) {
30
- headers.append('set-cookie', setCookieToString(setCookie));
30
+ headers.append('Set-Cookie', setCookieToString(setCookie));
31
31
  }
32
32
  }
33
33
  }
@@ -89,11 +89,11 @@ export async function main(data) {
89
89
  intro: config.playIntro,
90
90
  });
91
91
  };
92
- router.onError = e => {
92
+ router.onError = (e, req) => {
93
93
  console.error('routing failed:', e, 'reloading trying to fix it');
94
94
  // since onError is called only on subsequent requests we should never
95
95
  // have an infinite loop here
96
- window.location.reload();
96
+ window.location.href = req.url.href;
97
97
  };
98
98
  router.onRender = async (cr, readyForRoute, domUpdated) => {
99
99
  if (appInstance && cr.req.disableLoadData) {
@@ -44,7 +44,7 @@ export async function main(data) {
44
44
  ssrCache.set('FRONTEND_URL', data.serverData.frontend);
45
45
  const cookies = new ServerCookies(data.serverData.headers);
46
46
  ssrCache.set('crelteSites', data.serverData.sites);
47
- const router = new ServerRouter(data.serverData.sites, data.serverData.headers.get('accept-language') ?? '', { debugTiming: config.debugTiming ?? false });
47
+ const router = new ServerRouter(data.serverData.sites, data.serverData.headers.get('Accept-Language') ?? '', { debugTiming: config.debugTiming ?? false });
48
48
  const queries = newQueries(ssrCache, router.route.readonly(), config);
49
49
  const crelte = newCrelte({
50
50
  config,
@@ -171,8 +171,8 @@ class Inner {
171
171
  if (!resp.ok) {
172
172
  throw new QueryError({ status: resp.status, body: await resp.text() }, 'resp not ok');
173
173
  }
174
- if (resp.headers.get('x-debug-link'))
175
- console.log('Debug link', resp.headers.get('x-debug-link'));
174
+ if (resp.headers.get('X-Debug-Link'))
175
+ console.log('Debug link', resp.headers.get('X-Debug-Link'));
176
176
  if (timing) {
177
177
  console.log(logName + ' completed took: ' + (Date.now() - timing) + 'ms', vars);
178
178
  }
@@ -1,8 +1,8 @@
1
1
  import Queries, { isQuery, QueriesOptions, Query, QueryOptions } from '../queries/Queries.js';
2
2
  import { gql } from './gql.js';
3
3
  import QueryError from './QueryError.js';
4
- import { QueryVar, ValidIf, vars } from './vars.js';
5
- export { Queries, type QueriesOptions, type QueryOptions, type Query, isQuery, QueryError, gql, vars, type ValidIf, QueryVar, };
4
+ import { QueryVar, ValidIf, vars, varsIdsEqual } from './vars.js';
5
+ export { Queries, type QueriesOptions, type QueryOptions, type Query, isQuery, QueryError, gql, vars, type ValidIf, QueryVar, varsIdsEqual, };
6
6
  type InferQueryVarType<T> = T extends QueryVar<infer U> ? U : never;
7
7
  type InferVariableTypes<T> = {
8
8
  [K in keyof T]: InferQueryVarType<T[K]>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/queries/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EACf,OAAO,EACP,cAAc,EACd,KAAK,EACL,YAAY,EACZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,UAAU,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEpD,OAAO,EACN,OAAO,EACP,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,OAAO,EACP,UAAU,EACV,GAAG,EACH,IAAI,EACJ,KAAK,OAAO,EACZ,QAAQ,GACR,CAAC;AAEF,KAAK,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEpE,KAAK,kBAAkB,CAAC,CAAC,IAAI;KAC3B,CAAC,IAAI,MAAM,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACvC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,OAAO,CAClB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,IACpE,OAAO,GAAG,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/queries/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EACf,OAAO,EACP,cAAc,EACd,KAAK,EACL,YAAY,EACZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,UAAU,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAElE,OAAO,EACN,OAAO,EACP,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,OAAO,EACP,UAAU,EACV,GAAG,EACH,IAAI,EACJ,KAAK,OAAO,EACZ,QAAQ,EACR,YAAY,GACZ,CAAC;AAEF,KAAK,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEpE,KAAK,kBAAkB,CAAC,CAAC,IAAI;KAC3B,CAAC,IAAI,MAAM,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACvC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,OAAO,CAClB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,IACpE,OAAO,GAAG,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import Queries, { isQuery, } from '../queries/Queries.js';
2
2
  import { gql } from './gql.js';
3
3
  import QueryError from './QueryError.js';
4
- import { QueryVar, vars } from './vars.js';
5
- export { Queries, isQuery, QueryError, gql, vars, QueryVar, };
4
+ import { QueryVar, vars, varsIdsEqual } from './vars.js';
5
+ export { Queries, isQuery, QueryError, gql, vars, QueryVar, varsIdsEqual, };
@@ -3,19 +3,6 @@ export declare const vars: {
3
3
  any: () => QueryVar<any>;
4
4
  number: () => QueryVar<number>;
5
5
  string: () => QueryVar<string>;
6
- id: () => QueryVar<number>;
7
- ids: () => QueryVar<number[]>;
8
- siteId: () => QueryVar<number>;
9
- };
10
- export type ValidIf<T> = (v: T, cs: ServerRouter) => boolean;
11
- export declare class QueryVar<T = any> {
12
- private name;
13
- private type;
14
- private flagNullable;
15
- private validIfFn;
16
- constructor();
17
- string(): QueryVar<string>;
18
- number(): QueryVar<number>;
19
6
  /**
20
7
  * Id is almost the same as number but will also parse
21
8
  * strings, but only allow non negative integers
@@ -25,13 +12,15 @@ export declare class QueryVar<T = any> {
25
12
  * you need to validate the response to make sure filters
26
13
  * with this id returned something
27
14
  */
28
- id(): QueryVar<number>;
15
+ id: () => QueryVar<number>;
29
16
  /**
30
17
  * Ids is an array of ids
31
18
  * it will also convert a single id to an array with one element
32
19
  * the returned array will **never be empty**, but might be null if
33
20
  * allowed. Id's are always non negative integers
34
21
  *
22
+ * The numbers are always unique and sorted in ascending order
23
+ *
35
24
  * ## Warning
36
25
  * Ids are not automatically safe to be cached, it is also not
37
26
  * enough to just check if the filter returned some results.
@@ -43,6 +32,19 @@ export declare class QueryVar<T = any> {
43
32
  * To mitigate this you could do a second query with the filtered
44
33
  * ids in the field, and check if the return matches the length.
45
34
  */
35
+ ids: () => QueryVar<number[]>;
36
+ siteId: () => QueryVar<number>;
37
+ };
38
+ export type ValidIf<T> = (v: T, cs: ServerRouter) => boolean;
39
+ export declare class QueryVar<T = any> {
40
+ private name;
41
+ private type;
42
+ private flagNullable;
43
+ private validIfFn;
44
+ constructor();
45
+ string(): QueryVar<string>;
46
+ number(): QueryVar<number>;
47
+ id(): QueryVar<number>;
46
48
  ids(): QueryVar<number[]>;
47
49
  nullable(): QueryVar<T | null>;
48
50
  /**
@@ -61,4 +63,36 @@ export declare class QueryVar<T = any> {
61
63
  __QueryVar__(): void;
62
64
  }
63
65
  export declare function isQueryVar(v: any): v is QueryVar;
66
+ /**
67
+ * Checks if two id arrays are equal
68
+ *
69
+ * The first argument needs to come from a `vars.ids()` variable.
70
+ * The second argument should come from a query, where the output is trusted.
71
+ *
72
+ * ## Example
73
+ * ```
74
+ * export const variables = {
75
+ * categories: vars.ids()
76
+ * };
77
+ *
78
+ * export const caching: Caching<typeof variables> = (res, vars) => {
79
+ * // res is the graphql response
80
+ * return varsIdsEqual(vars.categories, res.categories);
81
+ * };
82
+ * ```
83
+ *
84
+ * ## Note
85
+ * The following cases are considered equal:
86
+ * ```
87
+ * varsIdsEqual(null, null);
88
+ * varsIdsEqual([], null);
89
+ * varsIdsEqual([1,2], ['2',1]);
90
+ * ```
91
+ * These are not equal:
92
+ * ```
93
+ * varsIdsEqual([1], null);
94
+ * varsIdsEqual([2,1], [2,1]); // because the second arg gets ordered
95
+ * ```
96
+ */
97
+ export declare function varsIdsEqual(a: number[] | null | undefined, b: (string | number)[] | null | undefined): boolean;
64
98
  //# sourceMappingURL=vars.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vars.d.ts","sourceRoot":"","sources":["../../src/queries/vars.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,2BAA2B,CAAC;AAErD,eAAO,MAAM,IAAI;eACP,QAAQ,CAAC,GAAG,CAAC;kBACV,QAAQ,CAAC,MAAM,CAAC;kBAChB,QAAQ,CAAC,MAAM,CAAC;cACpB,QAAQ,CAAC,MAAM,CAAC;eACf,QAAQ,CAAC,MAAM,EAAE,CAAC;kBACf,QAAQ,CAAC,MAAM,CAAC;CAM5B,CAAC;AAIF,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,YAAY,KAAK,OAAO,CAAC;AAE7D,qBAAa,QAAQ,CAAC,CAAC,GAAG,GAAG;IAC5B,OAAO,CAAC,IAAI,CAAgB;IAC5B,OAAO,CAAC,IAAI,CAA6C;IACzD,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,SAAS,CAAa;;IAS9B,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC;IAK1B,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC;IAK1B;;;;;;;;OAQG;IACH,EAAE,IAAI,QAAQ,CAAC,MAAM,CAAC;IAKtB;;;;;;;;;;;;;;;;OAgBG;IACH,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;IAKzB,QAAQ,IAAI,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC;IAK9B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IAKpC,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,YAAY,GAAG,CAAC,GAAG,IAAI;IAkE9C;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IAKpC,YAAY;CACZ;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,QAAQ,CAEhD"}
1
+ {"version":3,"file":"vars.d.ts","sourceRoot":"","sources":["../../src/queries/vars.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,2BAA2B,CAAC;AAErD,eAAO,MAAM,IAAI;eACP,QAAQ,CAAC,GAAG,CAAC;kBACV,QAAQ,CAAC,MAAM,CAAC;kBAChB,QAAQ,CAAC,MAAM,CAAC;IAE5B;;;;;;;;OAQG;cACK,QAAQ,CAAC,MAAM,CAAC;IAExB;;;;;;;;;;;;;;;;;;OAkBG;eACM,QAAQ,CAAC,MAAM,EAAE,CAAC;kBAEf,QAAQ,CAAC,MAAM,CAAC;CAM5B,CAAC;AAIF,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,YAAY,KAAK,OAAO,CAAC;AAE7D,qBAAa,QAAQ,CAAC,CAAC,GAAG,GAAG;IAC5B,OAAO,CAAC,IAAI,CAAgB;IAC5B,OAAO,CAAC,IAAI,CAA6C;IACzD,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,SAAS,CAAa;;IAS9B,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC;IAK1B,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC;IAK1B,EAAE,IAAI,QAAQ,CAAC,MAAM,CAAC;IAKtB,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;IAKzB,QAAQ,IAAI,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC;IAK9B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IAKpC,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,YAAY,GAAG,CAAC,GAAG,IAAI;IAqE9C;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IAKpC,YAAY;CACZ;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,QAAQ,CAEhD;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,YAAY,CAC3B,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,EAC9B,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,GACvC,OAAO,CAWT"}
@@ -2,7 +2,35 @@ export const vars = {
2
2
  any: () => new QueryVar(),
3
3
  number: () => new QueryVar().number(),
4
4
  string: () => new QueryVar().string(),
5
+ /**
6
+ * Id is almost the same as number but will also parse
7
+ * strings, but only allow non negative integers
8
+ *
9
+ * ## Warning
10
+ * Ids are not automatically safe to be cached
11
+ * you need to validate the response to make sure filters
12
+ * with this id returned something
13
+ */
5
14
  id: () => new QueryVar().id(),
15
+ /**
16
+ * Ids is an array of ids
17
+ * it will also convert a single id to an array with one element
18
+ * the returned array will **never be empty**, but might be null if
19
+ * allowed. Id's are always non negative integers
20
+ *
21
+ * The numbers are always unique and sorted in ascending order
22
+ *
23
+ * ## Warning
24
+ * Ids are not automatically safe to be cached, it is also not
25
+ * enough to just check if the filter returned some results.
26
+ * Since for example a `relatedTo` filter works like an `or` and
27
+ * not an `and` meaning if you request ids `[1,2,3]` and
28
+ * only 1 and 3 have related entries you will get results
29
+ * even though id 2 did not return anything.
30
+ *
31
+ * To mitigate this you could do a second query with the filtered
32
+ * ids in the field, and check if the return matches the length.
33
+ */
6
34
  ids: () => new QueryVar().ids(),
7
35
  siteId: () => new QueryVar()
8
36
  .number()
@@ -27,36 +55,10 @@ export class QueryVar {
27
55
  this.type = 'number';
28
56
  return this;
29
57
  }
30
- /**
31
- * Id is almost the same as number but will also parse
32
- * strings, but only allow non negative integers
33
- *
34
- * ## Warning
35
- * Ids are not automatically safe to be cached
36
- * you need to validate the response to make sure filters
37
- * with this id returned something
38
- */
39
58
  id() {
40
59
  this.type = 'id';
41
60
  return this;
42
61
  }
43
- /**
44
- * Ids is an array of ids
45
- * it will also convert a single id to an array with one element
46
- * the returned array will **never be empty**, but might be null if
47
- * allowed. Id's are always non negative integers
48
- *
49
- * ## Warning
50
- * Ids are not automatically safe to be cached, it is also not
51
- * enough to just check if the filter returned some results.
52
- * Since for example a `relatedTo` filter works like an `or` and
53
- * not an `and` meaning if you request ids `[1,2,3]` and
54
- * only 1 and 3 have related entries you will get results
55
- * even though id 2 did not return anything.
56
- *
57
- * To mitigate this you could do a second query with the filtered
58
- * ids in the field, and check if the return matches the length.
59
- */
60
62
  ids() {
61
63
  this.type = 'ids';
62
64
  return this;
@@ -97,7 +99,7 @@ export class QueryVar {
97
99
  break;
98
100
  case 'id':
99
101
  if (typeof v === 'string')
100
- v = parseInt(v);
102
+ v = Number(v);
101
103
  if (!isValidId(v))
102
104
  throw new Error(`variable ${this.name} is not a valid id`);
103
105
  break;
@@ -112,9 +114,11 @@ export class QueryVar {
112
114
  throw new Error(`variable ${this.name} is not allowed to be empty`);
113
115
  }
114
116
  // convert strings to numbers
115
- v = v.map(v => (typeof v === 'string' ? parseInt(v) : v));
117
+ v = v.map(Number);
116
118
  if (!v.every(isValidId))
117
119
  throw new Error(`variable ${this.name} is not a list of valid ids`);
120
+ // make unique and sort by number
121
+ v = Array.from(new Set(v)).sort((a, b) => a - b);
118
122
  break;
119
123
  default:
120
124
  throw new Error('uknown type ' + this.type);
@@ -140,3 +144,46 @@ export function isQueryVar(v) {
140
144
  function isValidId(id) {
141
145
  return typeof id === 'number' && Number.isInteger(id) && id >= 0;
142
146
  }
147
+ /**
148
+ * Checks if two id arrays are equal
149
+ *
150
+ * The first argument needs to come from a `vars.ids()` variable.
151
+ * The second argument should come from a query, where the output is trusted.
152
+ *
153
+ * ## Example
154
+ * ```
155
+ * export const variables = {
156
+ * categories: vars.ids()
157
+ * };
158
+ *
159
+ * export const caching: Caching<typeof variables> = (res, vars) => {
160
+ * // res is the graphql response
161
+ * return varsIdsEqual(vars.categories, res.categories);
162
+ * };
163
+ * ```
164
+ *
165
+ * ## Note
166
+ * The following cases are considered equal:
167
+ * ```
168
+ * varsIdsEqual(null, null);
169
+ * varsIdsEqual([], null);
170
+ * varsIdsEqual([1,2], ['2',1]);
171
+ * ```
172
+ * These are not equal:
173
+ * ```
174
+ * varsIdsEqual([1], null);
175
+ * varsIdsEqual([2,1], [2,1]); // because the second arg gets ordered
176
+ * ```
177
+ */
178
+ export function varsIdsEqual(a, b) {
179
+ const aEmpty = !a?.length;
180
+ const bEmpty = !b?.length;
181
+ if (aEmpty && bEmpty)
182
+ return true;
183
+ if (aEmpty || bEmpty)
184
+ return false;
185
+ if (a.length !== b.length)
186
+ return false;
187
+ const nb = b.map(Number).sort((a, b) => a - b);
188
+ return a.every((v, i) => v === nb[i]);
189
+ }
@@ -9,7 +9,7 @@ export type ClientRouterOptions = {
9
9
  export default class ClientRouter extends BaseRouter {
10
10
  private scrollDebounceTimeout;
11
11
  private preloadOnMouseOver;
12
- onError: (e: any) => void;
12
+ onError: (e: any, req: Request) => void;
13
13
  constructor(sites: SiteFromGraphQl[], opts: ClientRouterOptions);
14
14
  /**
15
15
  * ## Throws
@@ -1 +1 @@
1
- {"version":3,"file":"ClientRouter.d.ts","sourceRoot":"","sources":["../../../src/routing/router/ClientRouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,UAAU,EAAE,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,MAAM,mBAAmB,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,MAAM,mBAAmB,GAAG;IACjC,kBAAkB,EAAE,OAAO,CAAC;CAC5B,GAAG,iBAAiB,CAAC;AAEtB,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,UAAU;IACnD,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,kBAAkB,CAAU;IAEpC,OAAO,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;gBAEd,KAAK,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,mBAAmB;IAQ/D;;OAEG;IACG,IAAI;IAuBV;;OAEG;IACG,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IA6ChD,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,GAAE,cAAmB;IAYpD,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,GAAE,cAAmB;IAiB7D;;;OAGG;IACG,qBAAqB,CAC1B,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GACnC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IASxB,MAAM;IAgEN,QAAQ;IAgCR,YAAY,CAAC,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,GAAG,IAAI;CAwEpD"}
1
+ {"version":3,"file":"ClientRouter.d.ts","sourceRoot":"","sources":["../../../src/routing/router/ClientRouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,UAAU,EAAE,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,MAAM,mBAAmB,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,MAAM,mBAAmB,GAAG;IACjC,kBAAkB,EAAE,OAAO,CAAC;CAC5B,GAAG,iBAAiB,CAAC;AAEtB,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,UAAU;IACnD,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,kBAAkB,CAAU;IAEpC,OAAO,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;gBAE5B,KAAK,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,mBAAmB;IAQ/D;;OAEG;IACG,IAAI;IAuBV;;OAEG;IACG,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IA6ChD,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,GAAE,cAAmB;IAYpD,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,GAAE,cAAmB;IAiB7D;;;OAGG;IACG,qBAAqB,CAC1B,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GACnC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IASxB,MAAM;IAgEN,QAAQ;IAgCR,YAAY,CAAC,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,GAAG,IAAI;CAwEpD"}
@@ -97,7 +97,7 @@ export default class ClientRouter extends BaseRouter {
97
97
  }
98
98
  catch (e) {
99
99
  console.error('request failed', e);
100
- this.onError(e);
100
+ this.onError(e, req);
101
101
  }
102
102
  }
103
103
  listen() {
@@ -82,7 +82,7 @@ export default class ServerRouter {
82
82
  const { params, handlers } = this.inner.find(req.method, new URL(req.url).pathname);
83
83
  if (!handlers.length)
84
84
  return null;
85
- const languages = parseAcceptLanguage(req.headers.get('accept-language') ?? '').map(([l]) => l);
85
+ const languages = parseAcceptLanguage(req.headers.get('Accept-Language') ?? '').map(([l]) => l);
86
86
  const prefSite = preferredSite(this._sites, languages);
87
87
  const site = siteFromUrl(this._sites, new URL(req.url)) ??
88
88
  prefSite ??
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/server/queries/routes.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,MAAM,oBAAoB,CAAC;AACrD,OAAO,cAAc,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAQ,MAAM,uBAAuB,CAAC;AAEvD,OAAO,YAAY,MAAM,oBAAoB,CAAC;AAG9C,MAAM,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;AAG9E,qBAAa,UAAU;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC;IACtC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;gBAG3B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,IAAI,EACrC,SAAS,EAAE,SAAS,GAAG,IAAI;IAoB5B,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,kBAAkB;IAO1B;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAgBzB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAexD,MAAM,CACX,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,mBAAmB,GACtB,OAAO,CAAC,QAAQ,CAAC;CAwGpB;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAEzD"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/server/queries/routes.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,MAAM,oBAAoB,CAAC;AACrD,OAAO,cAAc,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAQ,MAAM,uBAAuB,CAAC;AAEvD,OAAO,YAAY,MAAM,oBAAoB,CAAC;AAG9C,MAAM,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;AAG9E,qBAAa,UAAU;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC;IACtC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;gBAG3B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,IAAI,EACrC,SAAS,EAAE,SAAS,GAAG,IAAI;IAoB5B,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,kBAAkB;IAO1B;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAgBzB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAexD,MAAM,CACX,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,mBAAmB,GACtB,OAAO,CAAC,QAAQ,CAAC;CAiHpB;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAEzD"}
@@ -79,6 +79,8 @@ export class QueryRoute {
79
79
  try {
80
80
  const reqVars = await csr.req.json();
81
81
  vars = this.validateVars(reqVars, caching.router);
82
+ if ('qName' in vars || 'xCraftSite' in vars)
83
+ throw new Error('qName and xCraftSite are reserved variable names');
82
84
  }
83
85
  catch (e) {
84
86
  return newError(e, 400);
@@ -96,10 +98,12 @@ export class QueryRoute {
96
98
  else if (reqSearch.has('siteToken')) {
97
99
  siteToken = reqSearch.get('siteToken');
98
100
  }
101
+ // check for x-craft-site header and pass it on
102
+ const xCraftSite = csr.req.headers.get('X-Craft-Site');
99
103
  let cacheKey = null;
100
104
  const useCache = !previewToken && caching.isEnabled();
101
105
  if (useCache) {
102
- cacheKey = await calcKey({ name: this.name, ...vars });
106
+ cacheKey = await calcKey({ ...vars, qName: this.name, xCraftSite });
103
107
  const cached = await caching.getCache(cacheKey);
104
108
  if (logInfo)
105
109
  console.log(`${logInfo} ${cached ? 'hit' : 'miss'}`);
@@ -121,6 +125,8 @@ export class QueryRoute {
121
125
  const xDebug = csr.req.headers.get('X-Debug');
122
126
  if (xDebug)
123
127
  headers['X-Debug'] = xDebug;
128
+ if (xCraftSite)
129
+ headers['X-Craft-Site'] = xCraftSite;
124
130
  // now execute the gql request
125
131
  let resp;
126
132
  try {
@@ -141,7 +147,7 @@ export class QueryRoute {
141
147
  return newError(e, 500);
142
148
  }
143
149
  const respHeaders = {};
144
- const xDebugLink = resp.headers.get('x-debug-link');
150
+ const xDebugLink = resp.headers.get('X-Debug-Link');
145
151
  if (xDebugLink)
146
152
  respHeaders['X-Debug'] = xDebugLink;
147
153
  let jsonResp;
@@ -96,7 +96,7 @@ export async function modRender(env, mod, template, req, opts = {}) {
96
96
  return new Response(html, { status, headers: nHeaders });
97
97
  }
98
98
  export async function modRenderError(env, mod, thrownError, template, req, opts = {}) {
99
- const acceptLang = req.headers.get('accept-language') ?? null;
99
+ const acceptLang = req.headers.get('Accept-Language') ?? null;
100
100
  // in the case of an error let's try to render a nice Error Page
101
101
  const error = {
102
102
  status: 500,
@@ -250,7 +250,7 @@ export default function crelte(opts) {
250
250
  async function serveVite(env, vite) {
251
251
  vite.middlewares.use(async (nReq, res, next) => {
252
252
  const protocol = vite.config.server.https ? 'https' : 'http';
253
- const baseUrl = protocol + '://' + nReq.headers['host'];
253
+ const baseUrl = protocol + '://' + nReq.headers['Host'];
254
254
  const req = requestToWebRequest(baseUrl, nReq);
255
255
  let thrownError = null;
256
256
  let serverMod;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crelte",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "author": "Crelte <support@crelte.com>",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -10,7 +10,7 @@ export default class ServerCookies implements Cookies {
10
10
  setCookies: Map<string, SetCookie>;
11
11
 
12
12
  constructor(headers: Headers) {
13
- this.requestCookies = parseCookies(headers.get('cookie') ?? '');
13
+ this.requestCookies = parseCookies(headers.get('Cookie') ?? '');
14
14
  this.setCookies = new Map();
15
15
  }
16
16
 
@@ -35,7 +35,7 @@ export default class ServerCookies implements Cookies {
35
35
 
36
36
  _populateHeaders(headers: Headers) {
37
37
  for (const setCookie of this.setCookies.values()) {
38
- headers.append('set-cookie', setCookieToString(setCookie));
38
+ headers.append('Set-Cookie', setCookieToString(setCookie));
39
39
  }
40
40
  }
41
41
  }
@@ -124,11 +124,11 @@ export async function main(data: MainData) {
124
124
  });
125
125
  };
126
126
 
127
- router.onError = e => {
127
+ router.onError = (e, req) => {
128
128
  console.error('routing failed:', e, 'reloading trying to fix it');
129
129
  // since onError is called only on subsequent requests we should never
130
130
  // have an infinite loop here
131
- window.location.reload();
131
+ window.location.href = req.url.href;
132
132
  };
133
133
 
134
134
  router.onRender = async (cr, readyForRoute, domUpdated) => {
@@ -73,7 +73,7 @@ export async function main(data: MainData): Promise<RenderResponse> {
73
73
  ssrCache.set('crelteSites', data.serverData.sites);
74
74
  const router = new ServerRouter(
75
75
  data.serverData.sites,
76
- data.serverData.headers.get('accept-language') ?? '',
76
+ data.serverData.headers.get('Accept-Language') ?? '',
77
77
  { debugTiming: config.debugTiming ?? false },
78
78
  );
79
79
 
@@ -304,8 +304,8 @@ class Inner {
304
304
  );
305
305
  }
306
306
 
307
- if (resp.headers.get('x-debug-link'))
308
- console.log('Debug link', resp.headers.get('x-debug-link'));
307
+ if (resp.headers.get('X-Debug-Link'))
308
+ console.log('Debug link', resp.headers.get('X-Debug-Link'));
309
309
 
310
310
  if (timing) {
311
311
  console.log(
@@ -6,7 +6,7 @@ import Queries, {
6
6
  } from '../queries/Queries.js';
7
7
  import { gql } from './gql.js';
8
8
  import QueryError from './QueryError.js';
9
- import { QueryVar, ValidIf, vars } from './vars.js';
9
+ import { QueryVar, ValidIf, vars, varsIdsEqual } from './vars.js';
10
10
 
11
11
  export {
12
12
  Queries,
@@ -19,6 +19,7 @@ export {
19
19
  vars,
20
20
  type ValidIf,
21
21
  QueryVar,
22
+ varsIdsEqual,
22
23
  };
23
24
 
24
25
  type InferQueryVarType<T> = T extends QueryVar<infer U> ? U : never;
@@ -4,8 +4,39 @@ export const vars = {
4
4
  any: (): QueryVar<any> => new QueryVar(),
5
5
  number: (): QueryVar<number> => new QueryVar().number(),
6
6
  string: (): QueryVar<string> => new QueryVar().string(),
7
+
8
+ /**
9
+ * Id is almost the same as number but will also parse
10
+ * strings, but only allow non negative integers
11
+ *
12
+ * ## Warning
13
+ * Ids are not automatically safe to be cached
14
+ * you need to validate the response to make sure filters
15
+ * with this id returned something
16
+ */
7
17
  id: (): QueryVar<number> => new QueryVar().id(),
18
+
19
+ /**
20
+ * Ids is an array of ids
21
+ * it will also convert a single id to an array with one element
22
+ * the returned array will **never be empty**, but might be null if
23
+ * allowed. Id's are always non negative integers
24
+ *
25
+ * The numbers are always unique and sorted in ascending order
26
+ *
27
+ * ## Warning
28
+ * Ids are not automatically safe to be cached, it is also not
29
+ * enough to just check if the filter returned some results.
30
+ * Since for example a `relatedTo` filter works like an `or` and
31
+ * not an `and` meaning if you request ids `[1,2,3]` and
32
+ * only 1 and 3 have related entries you will get results
33
+ * even though id 2 did not return anything.
34
+ *
35
+ * To mitigate this you could do a second query with the filtered
36
+ * ids in the field, and check if the return matches the length.
37
+ */
8
38
  ids: (): QueryVar<number[]> => new QueryVar().ids(),
39
+
9
40
  siteId: (): QueryVar<number> =>
10
41
  new QueryVar()
11
42
  .number()
@@ -41,37 +72,11 @@ export class QueryVar<T = any> {
41
72
  return this as unknown as QueryVar<number>;
42
73
  }
43
74
 
44
- /**
45
- * Id is almost the same as number but will also parse
46
- * strings, but only allow non negative integers
47
- *
48
- * ## Warning
49
- * Ids are not automatically safe to be cached
50
- * you need to validate the response to make sure filters
51
- * with this id returned something
52
- */
53
75
  id(): QueryVar<number> {
54
76
  this.type = 'id';
55
77
  return this as unknown as QueryVar<number>;
56
78
  }
57
79
 
58
- /**
59
- * Ids is an array of ids
60
- * it will also convert a single id to an array with one element
61
- * the returned array will **never be empty**, but might be null if
62
- * allowed. Id's are always non negative integers
63
- *
64
- * ## Warning
65
- * Ids are not automatically safe to be cached, it is also not
66
- * enough to just check if the filter returned some results.
67
- * Since for example a `relatedTo` filter works like an `or` and
68
- * not an `and` meaning if you request ids `[1,2,3]` and
69
- * only 1 and 3 have related entries you will get results
70
- * even though id 2 did not return anything.
71
- *
72
- * To mitigate this you could do a second query with the filtered
73
- * ids in the field, and check if the return matches the length.
74
- */
75
80
  ids(): QueryVar<number[]> {
76
81
  this.type = 'ids';
77
82
  return this as unknown as QueryVar<number[]>;
@@ -119,7 +124,7 @@ export class QueryVar<T = any> {
119
124
  break;
120
125
 
121
126
  case 'id':
122
- if (typeof v === 'string') v = parseInt(v);
127
+ if (typeof v === 'string') v = Number(v);
123
128
 
124
129
  if (!isValidId(v))
125
130
  throw new Error(`variable ${this.name} is not a valid id`);
@@ -141,12 +146,15 @@ export class QueryVar<T = any> {
141
146
  }
142
147
 
143
148
  // convert strings to numbers
144
- v = v.map(v => (typeof v === 'string' ? parseInt(v) : v));
149
+ v = v.map(Number);
145
150
 
146
151
  if (!v.every(isValidId))
147
152
  throw new Error(
148
153
  `variable ${this.name} is not a list of valid ids`,
149
154
  );
155
+
156
+ // make unique and sort by number
157
+ v = Array.from(new Set(v as number[])).sort((a, b) => a - b);
150
158
  break;
151
159
 
152
160
  default:
@@ -176,6 +184,53 @@ export function isQueryVar(v: any): v is QueryVar {
176
184
  }
177
185
 
178
186
  // does not do string to number conversion
179
- function isValidId(id: any): boolean {
187
+ function isValidId(id: any): id is number {
180
188
  return typeof id === 'number' && Number.isInteger(id) && id >= 0;
181
189
  }
190
+
191
+ /**
192
+ * Checks if two id arrays are equal
193
+ *
194
+ * The first argument needs to come from a `vars.ids()` variable.
195
+ * The second argument should come from a query, where the output is trusted.
196
+ *
197
+ * ## Example
198
+ * ```
199
+ * export const variables = {
200
+ * categories: vars.ids()
201
+ * };
202
+ *
203
+ * export const caching: Caching<typeof variables> = (res, vars) => {
204
+ * // res is the graphql response
205
+ * return varsIdsEqual(vars.categories, res.categories);
206
+ * };
207
+ * ```
208
+ *
209
+ * ## Note
210
+ * The following cases are considered equal:
211
+ * ```
212
+ * varsIdsEqual(null, null);
213
+ * varsIdsEqual([], null);
214
+ * varsIdsEqual([1,2], ['2',1]);
215
+ * ```
216
+ * These are not equal:
217
+ * ```
218
+ * varsIdsEqual([1], null);
219
+ * varsIdsEqual([2,1], [2,1]); // because the second arg gets ordered
220
+ * ```
221
+ */
222
+ export function varsIdsEqual(
223
+ a: number[] | null | undefined,
224
+ b: (string | number)[] | null | undefined,
225
+ ): boolean {
226
+ const aEmpty = !a?.length;
227
+ const bEmpty = !b?.length;
228
+ if (aEmpty && bEmpty) return true;
229
+ if (aEmpty || bEmpty) return false;
230
+
231
+ if (a.length !== b.length) return false;
232
+
233
+ const nb = b.map(Number).sort((a, b) => a - b);
234
+
235
+ return a.every((v, i) => v === nb[i]);
236
+ }
@@ -13,7 +13,7 @@ export default class ClientRouter extends BaseRouter {
13
13
  private scrollDebounceTimeout: any | null;
14
14
  private preloadOnMouseOver: boolean;
15
15
 
16
- onError: (e: any) => void;
16
+ onError: (e: any, req: Request) => void;
17
17
 
18
18
  constructor(sites: SiteFromGraphQl[], opts: ClientRouterOptions) {
19
19
  super(sites, navigator.languages.slice(), opts);
@@ -138,7 +138,7 @@ export default class ClientRouter extends BaseRouter {
138
138
  return await this.handleRequest(req, updateHistory);
139
139
  } catch (e) {
140
140
  console.error('request failed', e);
141
- this.onError(e);
141
+ this.onError(e, req);
142
142
  }
143
143
  }
144
144
 
@@ -121,7 +121,7 @@ export default class ServerRouter {
121
121
  if (!handlers.length) return null;
122
122
 
123
123
  const languages = parseAcceptLanguage(
124
- req.headers.get('accept-language') ?? '',
124
+ req.headers.get('Accept-Language') ?? '',
125
125
  ).map(([l]) => l);
126
126
  const prefSite = preferredSite(this._sites, languages);
127
127
  const site =
@@ -104,6 +104,10 @@ export class QueryRoute {
104
104
  try {
105
105
  const reqVars = await csr.req.json();
106
106
  vars = this.validateVars(reqVars, caching.router);
107
+ if ('qName' in vars || 'xCraftSite' in vars)
108
+ throw new Error(
109
+ 'qName and xCraftSite are reserved variable names',
110
+ );
107
111
  } catch (e) {
108
112
  return newError(e, 400);
109
113
  }
@@ -124,10 +128,13 @@ export class QueryRoute {
124
128
  siteToken = reqSearch.get('siteToken');
125
129
  }
126
130
 
131
+ // check for x-craft-site header and pass it on
132
+ const xCraftSite = csr.req.headers.get('X-Craft-Site');
133
+
127
134
  let cacheKey: string | null = null;
128
135
  const useCache = !previewToken && caching.isEnabled();
129
136
  if (useCache) {
130
- cacheKey = await calcKey({ name: this.name, ...vars });
137
+ cacheKey = await calcKey({ ...vars, qName: this.name, xCraftSite });
131
138
  const cached = await caching.getCache(cacheKey);
132
139
 
133
140
  if (logInfo) console.log(`${logInfo} ${cached ? 'hit' : 'miss'}`);
@@ -150,6 +157,8 @@ export class QueryRoute {
150
157
  const xDebug = csr.req.headers.get('X-Debug');
151
158
  if (xDebug) headers['X-Debug'] = xDebug;
152
159
 
160
+ if (xCraftSite) headers['X-Craft-Site'] = xCraftSite;
161
+
153
162
  // now execute the gql request
154
163
  let resp: Response;
155
164
  try {
@@ -170,7 +179,7 @@ export class QueryRoute {
170
179
  }
171
180
 
172
181
  const respHeaders: Record<string, string> = {};
173
- const xDebugLink = resp.headers.get('x-debug-link');
182
+ const xDebugLink = resp.headers.get('X-Debug-Link');
174
183
  if (xDebugLink) respHeaders['X-Debug'] = xDebugLink;
175
184
 
176
185
  let jsonResp: Record<string, any>;
@@ -219,7 +219,7 @@ export async function modRenderError(
219
219
  req: Request,
220
220
  opts: ModRenderOptions = {},
221
221
  ): Promise<Response> {
222
- const acceptLang = req.headers.get('accept-language') ?? null;
222
+ const acceptLang = req.headers.get('Accept-Language') ?? null;
223
223
 
224
224
  // in the case of an error let's try to render a nice Error Page
225
225
  const error = {
package/src/vite/index.ts CHANGED
@@ -323,7 +323,7 @@ export default function crelte(opts?: CrelteOptions): Plugin {
323
323
  async function serveVite(env: EnvData, vite: ViteDevServer) {
324
324
  vite.middlewares.use(async (nReq, res, next) => {
325
325
  const protocol = vite.config.server.https ? 'https' : 'http';
326
- const baseUrl = protocol + '://' + nReq.headers['host'];
326
+ const baseUrl = protocol + '://' + nReq.headers['Host'];
327
327
 
328
328
  const req = requestToWebRequest(baseUrl, nReq);
329
329