crelte 0.5.1 → 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, };
@@ -19,6 +19,8 @@ export declare const vars: {
19
19
  * the returned array will **never be empty**, but might be null if
20
20
  * allowed. Id's are always non negative integers
21
21
  *
22
+ * The numbers are always unique and sorted in ascending order
23
+ *
22
24
  * ## Warning
23
25
  * Ids are not automatically safe to be cached, it is also not
24
26
  * enough to just check if the filter returned some results.
@@ -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;IAE5B;;;;;;;;OAQG;cACK,QAAQ,CAAC,MAAM,CAAC;IAExB;;;;;;;;;;;;;;;;OAgBG;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"}
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"}
@@ -18,6 +18,8 @@ export const vars = {
18
18
  * the returned array will **never be empty**, but might be null if
19
19
  * allowed. Id's are always non negative integers
20
20
  *
21
+ * The numbers are always unique and sorted in ascending order
22
+ *
21
23
  * ## Warning
22
24
  * Ids are not automatically safe to be cached, it is also not
23
25
  * enough to just check if the filter returned some results.
@@ -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,7 +114,7 @@ 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`);
118
120
  // make unique and sort by number
@@ -142,3 +144,46 @@ export function isQueryVar(v) {
142
144
  function isValidId(id) {
143
145
  return typeof id === 'number' && Number.isInteger(id) && id >= 0;
144
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.1",
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;
@@ -22,6 +22,8 @@ export const vars = {
22
22
  * the returned array will **never be empty**, but might be null if
23
23
  * allowed. Id's are always non negative integers
24
24
  *
25
+ * The numbers are always unique and sorted in ascending order
26
+ *
25
27
  * ## Warning
26
28
  * Ids are not automatically safe to be cached, it is also not
27
29
  * enough to just check if the filter returned some results.
@@ -122,7 +124,7 @@ export class QueryVar<T = any> {
122
124
  break;
123
125
 
124
126
  case 'id':
125
- if (typeof v === 'string') v = parseInt(v);
127
+ if (typeof v === 'string') v = Number(v);
126
128
 
127
129
  if (!isValidId(v))
128
130
  throw new Error(`variable ${this.name} is not a valid id`);
@@ -144,7 +146,7 @@ export class QueryVar<T = any> {
144
146
  }
145
147
 
146
148
  // convert strings to numbers
147
- v = v.map(v => (typeof v === 'string' ? parseInt(v) : v));
149
+ v = v.map(Number);
148
150
 
149
151
  if (!v.every(isValidId))
150
152
  throw new Error(
@@ -185,3 +187,50 @@ export function isQueryVar(v: any): v is QueryVar {
185
187
  function isValidId(id: any): id is number {
186
188
  return typeof id === 'number' && Number.isInteger(id) && id >= 0;
187
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