crelte 0.5.13 → 0.5.14
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/queries/Queries.d.ts +3 -0
- package/dist/queries/Queries.d.ts.map +1 -1
- package/dist/queries/Queries.js +3 -0
- package/dist/queries/index.d.ts +21 -0
- package/dist/queries/index.d.ts.map +1 -1
- package/dist/queries/vars.d.ts +25 -2
- package/dist/queries/vars.d.ts.map +1 -1
- package/dist/queries/vars.js +42 -1
- package/dist/routing/route/BaseRoute.d.ts.map +1 -1
- package/dist/routing/route/BaseRoute.js +4 -17
- package/dist/server/queries/QueryGqlRoute.d.ts +3 -1
- package/dist/server/queries/QueryGqlRoute.d.ts.map +1 -1
- package/dist/server/queries/QueryGqlRoute.js +4 -2
- package/dist/server/queries/QueryHandleRoute.d.ts +3 -2
- package/dist/server/queries/QueryHandleRoute.d.ts.map +1 -1
- package/dist/server/queries/QueryHandleRoute.js +4 -2
- package/dist/server/queries/queries.d.ts.map +1 -1
- package/dist/server/queries/queries.js +12 -2
- package/dist/server/queries/routes.d.ts +3 -2
- package/dist/server/queries/routes.d.ts.map +1 -1
- package/dist/server/queries/routes.js +9 -3
- package/dist/std/index.d.ts +1 -0
- package/dist/std/index.d.ts.map +1 -1
- package/dist/std/index.js +1 -0
- package/dist/std/url/index.d.ts +40 -0
- package/dist/std/url/index.d.ts.map +1 -0
- package/dist/std/url/index.js +65 -0
- package/dist/std/url/utils.d.ts +19 -0
- package/dist/std/url/utils.d.ts.map +1 -0
- package/dist/std/url/utils.js +40 -0
- package/dist/vite/index.js +3 -2
- package/package.json +5 -1
- package/src/queries/Queries.ts +3 -0
- package/src/queries/index.ts +25 -0
- package/src/queries/vars.ts +58 -3
- package/src/routing/route/BaseRoute.ts +8 -22
- package/src/server/queries/QueryGqlRoute.ts +17 -2
- package/src/server/queries/QueryHandleRoute.ts +5 -2
- package/src/server/queries/queries.ts +29 -3
- package/src/server/queries/routes.ts +17 -3
- package/src/std/index.ts +1 -0
- package/src/std/url/index.ts +78 -0
- package/src/std/url/utils.ts +48 -0
- package/src/vite/index.ts +4 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/std/url/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,kCAM1E;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,SAAS,GAAG,GAAG,GAAG,MAAM,GAAG,GAAG,CAMxD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,GAAG,OAAO,CAWxE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAGxD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { BaseRoute } from '../../routing/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Checks if a search param should be removed.
|
|
4
|
+
* This is the case if the value is `null`, `undefined`, or an empty string.
|
|
5
|
+
*/
|
|
6
|
+
export function deleteSearchParam(value) {
|
|
7
|
+
return (typeof value === 'undefined' ||
|
|
8
|
+
value === null ||
|
|
9
|
+
(typeof value === 'string' && value === ''));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Converts a `BaseRoute`, `URL`, or string to a `URL` object.
|
|
13
|
+
*/
|
|
14
|
+
export function toUrl(url) {
|
|
15
|
+
if (typeof url === 'string')
|
|
16
|
+
return new URL(url);
|
|
17
|
+
if (url instanceof BaseRoute)
|
|
18
|
+
return new URL(url.url);
|
|
19
|
+
return url;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Compares two `URLSearchParams` objects for equality.
|
|
23
|
+
*/
|
|
24
|
+
export function searchEq(a, b) {
|
|
25
|
+
if (a.size !== b.size)
|
|
26
|
+
return false;
|
|
27
|
+
// Clone to avoid mutating the original objects
|
|
28
|
+
const cloneA = new URLSearchParams(a);
|
|
29
|
+
const cloneB = new URLSearchParams(b);
|
|
30
|
+
cloneA.sort();
|
|
31
|
+
cloneB.sort();
|
|
32
|
+
return cloneA.toString() === cloneB.toString();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Compares two pathnames for equality, ignoring trailing slashes.
|
|
36
|
+
*/
|
|
37
|
+
export function pathnameEq(a, b) {
|
|
38
|
+
// check for trailing slashes
|
|
39
|
+
return a === b || a === b + '/' || a + '/' === b;
|
|
40
|
+
}
|
package/dist/vite/index.js
CHANGED
|
@@ -126,7 +126,7 @@ export default function crelte(opts) {
|
|
|
126
126
|
publicDir: isSsrBuild ? false : 'public',
|
|
127
127
|
base: '/',
|
|
128
128
|
server: {
|
|
129
|
-
port: 8080,
|
|
129
|
+
port: config.server?.port ?? 8080,
|
|
130
130
|
},
|
|
131
131
|
optimizeDeps: {
|
|
132
132
|
exclude: ['crelte'],
|
|
@@ -256,7 +256,8 @@ export default function crelte(opts) {
|
|
|
256
256
|
async function serveVite(env, vite) {
|
|
257
257
|
let sites = null;
|
|
258
258
|
const handle = async (nReq, res) => {
|
|
259
|
-
const protocol =
|
|
259
|
+
const protocol = nReq.headers['x-forwarded-proto'] ??
|
|
260
|
+
(vite.config.server.https ? 'https' : 'http');
|
|
260
261
|
const baseUrl = protocol + '://' + nReq.headers['host'];
|
|
261
262
|
const req = requestToWebRequest(baseUrl, nReq);
|
|
262
263
|
const serverMod = await vite.ssrLoadModule('./src/server.js');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crelte",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.14",
|
|
4
4
|
"author": "Crelte <support@crelte.com>",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -107,6 +107,10 @@
|
|
|
107
107
|
"./std/rand": {
|
|
108
108
|
"types": "./dist/std/rand/index.d.ts",
|
|
109
109
|
"default": "./dist/std/rand/index.js"
|
|
110
|
+
},
|
|
111
|
+
"./std/url": {
|
|
112
|
+
"types": "./dist/std/url/index.d.ts",
|
|
113
|
+
"default": "./dist/std/url/index.js"
|
|
110
114
|
}
|
|
111
115
|
},
|
|
112
116
|
"dependencies": {
|
package/src/queries/Queries.ts
CHANGED
|
@@ -42,6 +42,9 @@ export type NamedQuery = { queryName: string };
|
|
|
42
42
|
* Create a NamedQuery for the given server query name.
|
|
43
43
|
*
|
|
44
44
|
* Prefer importing a graphql file instead.
|
|
45
|
+
*
|
|
46
|
+
* #### Note
|
|
47
|
+
* This needs to match the name of the js/ts file.
|
|
45
48
|
*/
|
|
46
49
|
export function namedQuery(name: string): NamedQuery {
|
|
47
50
|
return { queryName: name };
|
package/src/queries/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import Queries, {
|
|
|
8
8
|
QueryOptions,
|
|
9
9
|
} from '../queries/Queries.js';
|
|
10
10
|
import type CrelteServerRequest from '../server/CrelteServer.js';
|
|
11
|
+
import ServerRouter from '../server/ServerRouter.js';
|
|
11
12
|
import { gql } from './gql.js';
|
|
12
13
|
import QueryError, { QueryErrorResponse } from './QueryError.js';
|
|
13
14
|
import { QueryVar, ValidIf, vars, varsIdsEqual } from './vars.js';
|
|
@@ -38,6 +39,30 @@ export type InferVariableTypes<T> = {
|
|
|
38
39
|
[K in keyof T]: InferQueryVarType<T[K]>;
|
|
39
40
|
};
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Defines wether the query variables are valid.
|
|
44
|
+
*
|
|
45
|
+
* Either throw or return a boolean.
|
|
46
|
+
*
|
|
47
|
+
* #### Example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { vars, ValidVars } from 'crelte/queries';
|
|
50
|
+
*
|
|
51
|
+
* export const variables = {
|
|
52
|
+
* siteId: vars.siteId(),
|
|
53
|
+
* category: vars.id()
|
|
54
|
+
* };
|
|
55
|
+
*
|
|
56
|
+
* export const validVars: ValidVars<typeof variables> = (vars, sr) => {
|
|
57
|
+
* if (!vars.category === 5) throw new Error('category needs to be 5');
|
|
58
|
+
* };
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export type ValidVars<T extends Record<string, QueryVar<any>>> = (
|
|
62
|
+
vars: InferVariableTypes<T>,
|
|
63
|
+
sr: ServerRouter,
|
|
64
|
+
) => void | boolean;
|
|
65
|
+
|
|
41
66
|
/**
|
|
42
67
|
* Defines when a query can safely be cached.
|
|
43
68
|
*
|
package/src/queries/vars.ts
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
import ServerRouter from '../server/ServerRouter.js';
|
|
2
2
|
|
|
3
3
|
export const vars = {
|
|
4
|
+
/**
|
|
5
|
+
* Any is not validated except for nullability
|
|
6
|
+
*/
|
|
4
7
|
any: (): QueryVar<any> => new QueryVar(),
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Number is a number, but will also parse strings to numbers
|
|
11
|
+
*/
|
|
5
12
|
number: (): QueryVar<number> => new QueryVar().number(),
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* String is a string, but will not parse numbers to strings
|
|
16
|
+
*/
|
|
6
17
|
string: (): QueryVar<string> => new QueryVar().string(),
|
|
7
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Strings is an array of strings, but will also convert a single string to
|
|
21
|
+
* an array with one element
|
|
22
|
+
*
|
|
23
|
+
* #### Note
|
|
24
|
+
* The returned array will **never be empty**, but might be null if allowed.
|
|
25
|
+
* It is not deduped or sorted.
|
|
26
|
+
* If you have ids you should use `vars.ids()` instead.
|
|
27
|
+
*/
|
|
28
|
+
strings: (): QueryVar<string[]> => new QueryVar().strings(),
|
|
29
|
+
|
|
8
30
|
/**
|
|
9
31
|
* Id is almost the same as number but will also parse
|
|
10
32
|
* strings, but only allow non negative integers
|
|
@@ -37,6 +59,9 @@ export const vars = {
|
|
|
37
59
|
*/
|
|
38
60
|
ids: (): QueryVar<number[]> => new QueryVar().ids(),
|
|
39
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Checks for a valid site id which exists
|
|
64
|
+
*/
|
|
40
65
|
siteId: (): QueryVar<number> =>
|
|
41
66
|
new QueryVar()
|
|
42
67
|
.number()
|
|
@@ -47,11 +72,11 @@ export const vars = {
|
|
|
47
72
|
|
|
48
73
|
/// either throw with an error which will get returned in a 400 response or
|
|
49
74
|
// return false if the value is not valid
|
|
50
|
-
export type ValidIf<T> = (v: T,
|
|
75
|
+
export type ValidIf<T> = (v: T, sr: ServerRouter) => boolean;
|
|
51
76
|
|
|
52
77
|
export class QueryVar<T = any> {
|
|
53
78
|
private name: string | null;
|
|
54
|
-
private type: 'any' | 'string' | 'number' | 'id' | 'ids';
|
|
79
|
+
private type: 'any' | 'string' | 'strings' | 'number' | 'id' | 'ids';
|
|
55
80
|
private flagNullable: boolean;
|
|
56
81
|
private defaultValue: T | undefined;
|
|
57
82
|
private validIfFn: ValidIf<T>;
|
|
@@ -69,6 +94,11 @@ export class QueryVar<T = any> {
|
|
|
69
94
|
return this as unknown as QueryVar<string>;
|
|
70
95
|
}
|
|
71
96
|
|
|
97
|
+
strings(): QueryVar<string[]> {
|
|
98
|
+
this.type = 'strings';
|
|
99
|
+
return this as unknown as QueryVar<string[]>;
|
|
100
|
+
}
|
|
101
|
+
|
|
72
102
|
number(): QueryVar<number> {
|
|
73
103
|
this.type = 'number';
|
|
74
104
|
return this as unknown as QueryVar<number>;
|
|
@@ -98,7 +128,7 @@ export class QueryVar<T = any> {
|
|
|
98
128
|
* Set a validation function for this variable
|
|
99
129
|
*
|
|
100
130
|
* If the value is allowed to be null and it is null
|
|
101
|
-
*
|
|
131
|
+
* the passed function will not be called.
|
|
102
132
|
*/
|
|
103
133
|
validIf(fn: ValidIf<T>): QueryVar<T> {
|
|
104
134
|
this.validIfFn = fn;
|
|
@@ -127,6 +157,31 @@ export class QueryVar<T = any> {
|
|
|
127
157
|
throw new Error(`variable ${this.name} is not a string`);
|
|
128
158
|
break;
|
|
129
159
|
|
|
160
|
+
case 'strings':
|
|
161
|
+
if (typeof v === 'string') v = [v];
|
|
162
|
+
|
|
163
|
+
if (!Array.isArray(v))
|
|
164
|
+
throw new Error(
|
|
165
|
+
`variable ${this.name} is not a string or a list of strings`,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (v.length <= 0) {
|
|
169
|
+
if (this.defaultValue !== undefined)
|
|
170
|
+
return this.defaultValue;
|
|
171
|
+
|
|
172
|
+
if (this.flagNullable) return null;
|
|
173
|
+
|
|
174
|
+
throw new Error(
|
|
175
|
+
`variable ${this.name} is not allowed to be empty`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!v.every(s => typeof s === 'string'))
|
|
180
|
+
throw new Error(
|
|
181
|
+
`variable ${this.name} is not a list of strings`,
|
|
182
|
+
);
|
|
183
|
+
break;
|
|
184
|
+
|
|
130
185
|
case 'number':
|
|
131
186
|
if (typeof v !== 'number')
|
|
132
187
|
throw new Error(`variable ${this.name} is not a number`);
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deleteSearchParam,
|
|
3
|
+
pathnameEq,
|
|
4
|
+
searchEq,
|
|
5
|
+
} from '../../std/url/utils.js';
|
|
1
6
|
import { objClone } from '../../utils.js';
|
|
2
7
|
import Site from '../Site.js';
|
|
3
8
|
import { trimSlashEnd } from '../utils.js';
|
|
@@ -238,12 +243,7 @@ export default class BaseRoute {
|
|
|
238
243
|
* ```
|
|
239
244
|
*/
|
|
240
245
|
setSearchParam(key: string, value?: string | number | null) {
|
|
241
|
-
|
|
242
|
-
typeof value === 'undefined' ||
|
|
243
|
-
value === null ||
|
|
244
|
-
(typeof value === 'string' && value === '');
|
|
245
|
-
|
|
246
|
-
if (!deleteValue) {
|
|
246
|
+
if (!deleteSearchParam(value)) {
|
|
247
247
|
this.search.set(key, value as string);
|
|
248
248
|
} else {
|
|
249
249
|
this.search.delete(key);
|
|
@@ -344,8 +344,8 @@ export default class BaseRoute {
|
|
|
344
344
|
eqUrl(route: BaseRoute | null) {
|
|
345
345
|
return (
|
|
346
346
|
route &&
|
|
347
|
-
this.url.
|
|
348
|
-
this.url.
|
|
347
|
+
this.url.origin === route.url.origin &&
|
|
348
|
+
pathnameEq(this.url.pathname, route.url.pathname)
|
|
349
349
|
);
|
|
350
350
|
}
|
|
351
351
|
|
|
@@ -353,20 +353,6 @@ export default class BaseRoute {
|
|
|
353
353
|
* Checks if the search params are equal to another route
|
|
354
354
|
*/
|
|
355
355
|
eqSearch(route: BaseRoute | null) {
|
|
356
|
-
const searchEq = (a: URLSearchParams, b: URLSearchParams) => {
|
|
357
|
-
if (a.size !== b.size) return false;
|
|
358
|
-
|
|
359
|
-
a.sort();
|
|
360
|
-
b.sort();
|
|
361
|
-
|
|
362
|
-
const aEntries = Array.from(a.entries());
|
|
363
|
-
const bEntries = Array.from(b.entries());
|
|
364
|
-
|
|
365
|
-
return aEntries
|
|
366
|
-
.map((a, i) => [a, bEntries[i]])
|
|
367
|
-
.every(([[ak, av], [bk, bv]]) => ak === bk && av === bv);
|
|
368
|
-
};
|
|
369
|
-
|
|
370
356
|
return route && searchEq(this.search, route.search);
|
|
371
357
|
}
|
|
372
358
|
|
|
@@ -3,10 +3,17 @@ import QueriesCaching from './QueriesCaching.js';
|
|
|
3
3
|
import { QueryVar, vars } from '../../queries/vars.js';
|
|
4
4
|
import { extractEntry } from '../../loadData/index.js';
|
|
5
5
|
import { calcKey } from '../../ssr/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
CacheIfFn,
|
|
8
|
+
newError,
|
|
9
|
+
TransformFn,
|
|
10
|
+
validateVars,
|
|
11
|
+
ValidIfFn,
|
|
12
|
+
} from './routes.js';
|
|
7
13
|
|
|
8
14
|
export type QueryGqlArgs = {
|
|
9
15
|
vars: Record<string, QueryVar> | null;
|
|
16
|
+
validIfFn: ValidIfFn | null;
|
|
10
17
|
cacheIfFn: CacheIfFn | null;
|
|
11
18
|
preventCaching: boolean;
|
|
12
19
|
transformFn: TransformFn | null;
|
|
@@ -17,6 +24,7 @@ export default class QueryGqlRoute {
|
|
|
17
24
|
name: string;
|
|
18
25
|
query: string;
|
|
19
26
|
vars: Record<string, QueryVar> | null;
|
|
27
|
+
validIfFn: ValidIfFn | null;
|
|
20
28
|
cacheIfFn: CacheIfFn | null;
|
|
21
29
|
transformFn: TransformFn | null;
|
|
22
30
|
|
|
@@ -32,6 +40,7 @@ export default class QueryGqlRoute {
|
|
|
32
40
|
this.name = name;
|
|
33
41
|
this.query = query;
|
|
34
42
|
this.vars = args.vars;
|
|
43
|
+
this.validIfFn = args.validIfFn;
|
|
35
44
|
this.cacheIfFn = args.cacheIfFn;
|
|
36
45
|
this.transformFn = args.transformFn;
|
|
37
46
|
|
|
@@ -112,7 +121,13 @@ export default class QueryGqlRoute {
|
|
|
112
121
|
let vars: Record<string, any>;
|
|
113
122
|
try {
|
|
114
123
|
const reqVars = await csr.req.json();
|
|
115
|
-
vars = validateVars(
|
|
124
|
+
vars = validateVars(
|
|
125
|
+
this.vars,
|
|
126
|
+
reqVars,
|
|
127
|
+
this.validIfFn,
|
|
128
|
+
caching.router,
|
|
129
|
+
);
|
|
130
|
+
|
|
116
131
|
if ('qName' in vars || 'xCraftSite' in vars)
|
|
117
132
|
throw new Error(
|
|
118
133
|
'qName and xCraftSite are reserved variable names',
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { QueryVar } from '../../queries/vars.js';
|
|
2
2
|
import CrelteServerRequest from '../CrelteServer.js';
|
|
3
3
|
import ServerRouter from '../ServerRouter.js';
|
|
4
|
-
import { HandleFn, newError, validateVars } from './routes.js';
|
|
4
|
+
import { HandleFn, newError, validateVars, ValidIfFn } from './routes.js';
|
|
5
5
|
|
|
6
6
|
// only internal
|
|
7
7
|
export default class QueryHandleRoute {
|
|
8
8
|
name: string;
|
|
9
|
+
validIfFn: ValidIfFn | null;
|
|
9
10
|
handleFn: HandleFn;
|
|
10
11
|
vars: Record<string, QueryVar> | null;
|
|
11
12
|
|
|
12
13
|
constructor(
|
|
13
14
|
name: string,
|
|
15
|
+
validIfFn: ValidIfFn | null,
|
|
14
16
|
handleFn: HandleFn,
|
|
15
17
|
vars: Record<string, QueryVar> | null,
|
|
16
18
|
) {
|
|
17
19
|
this.name = name;
|
|
20
|
+
this.validIfFn = validIfFn;
|
|
18
21
|
this.handleFn = handleFn;
|
|
19
22
|
this.vars = vars;
|
|
20
23
|
}
|
|
@@ -26,7 +29,7 @@ export default class QueryHandleRoute {
|
|
|
26
29
|
let vars: Record<string, any>;
|
|
27
30
|
try {
|
|
28
31
|
const reqVars = await csr.req.json();
|
|
29
|
-
vars = validateVars(this.vars, reqVars, cs);
|
|
32
|
+
vars = validateVars(this.vars, reqVars, this.validIfFn, cs);
|
|
30
33
|
} catch (e) {
|
|
31
34
|
return newError(e, 400);
|
|
32
35
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ServerRouter from '../ServerRouter.js';
|
|
2
2
|
import QueriesCaching from './QueriesCaching.js';
|
|
3
|
-
import { CacheIfFn, HandleFn, TransformFn } from './routes.js';
|
|
3
|
+
import { CacheIfFn, HandleFn, TransformFn, ValidIfFn } from './routes.js';
|
|
4
4
|
import { isQueryVar, QueryVar } from '../../queries/vars.js';
|
|
5
5
|
import { Platform } from '../platform.js';
|
|
6
6
|
import QueryHandleRoute from './QueryHandleRoute.js';
|
|
@@ -13,6 +13,11 @@ type ModQuery = {
|
|
|
13
13
|
|
|
14
14
|
type ModTs = {
|
|
15
15
|
variables?: any;
|
|
16
|
+
// only to hint that its validVars
|
|
17
|
+
validVariables?: any;
|
|
18
|
+
// only to hint that its validVars
|
|
19
|
+
validIf?: any;
|
|
20
|
+
validVars?: any;
|
|
16
21
|
caching?: any;
|
|
17
22
|
transform?: any;
|
|
18
23
|
handle?: any;
|
|
@@ -24,6 +29,7 @@ type RouteBuilder = {
|
|
|
24
29
|
query: string | null;
|
|
25
30
|
jsFile: string | null;
|
|
26
31
|
vars: Record<string, QueryVar> | null;
|
|
32
|
+
validIfFn: ValidIfFn | null;
|
|
27
33
|
/** corresponds to the caching export */
|
|
28
34
|
cacheIfFn: CacheIfFn | null;
|
|
29
35
|
preventCaching: boolean;
|
|
@@ -59,6 +65,7 @@ export async function initQueryRoutes(
|
|
|
59
65
|
query: null,
|
|
60
66
|
jsFile: null,
|
|
61
67
|
vars: null,
|
|
68
|
+
validIfFn: null,
|
|
62
69
|
cacheIfFn: null,
|
|
63
70
|
preventCaching: false,
|
|
64
71
|
transformFn: null,
|
|
@@ -87,6 +94,17 @@ export async function initQueryRoutes(
|
|
|
87
94
|
routeBuilder.vars = parseVars(mts.variables);
|
|
88
95
|
}
|
|
89
96
|
|
|
97
|
+
if (mts.validVariables || mts.validIf) {
|
|
98
|
+
throw new Error(`use the function validVars in ${filename}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (mts.validVars) {
|
|
102
|
+
if (typeof mts.validVars !== 'function') {
|
|
103
|
+
throw new Error('validVars should be a function');
|
|
104
|
+
}
|
|
105
|
+
routeBuilder.validIfFn = mts.validVars;
|
|
106
|
+
}
|
|
107
|
+
|
|
90
108
|
if (mts.caching) {
|
|
91
109
|
routeBuilder.cacheIfFn = parseCaching(mts.caching);
|
|
92
110
|
} else if (typeof mts.caching === 'boolean') {
|
|
@@ -108,7 +126,10 @@ export async function initQueryRoutes(
|
|
|
108
126
|
|
|
109
127
|
for (const [name, rb] of routeBuilders.entries()) {
|
|
110
128
|
if (rb.query) {
|
|
111
|
-
if (rb.handleFn)
|
|
129
|
+
if (rb.handleFn)
|
|
130
|
+
throw new Error(
|
|
131
|
+
'cannot have a handle function if a query is present',
|
|
132
|
+
);
|
|
112
133
|
|
|
113
134
|
const route = new QueryGqlRoute(name, rb.query, rb);
|
|
114
135
|
|
|
@@ -119,7 +140,12 @@ export async function initQueryRoutes(
|
|
|
119
140
|
if (rb.cacheIfFn || rb.transformFn || rb.preventCaching)
|
|
120
141
|
throw new Error('caching or transform not supported');
|
|
121
142
|
|
|
122
|
-
const route = new QueryHandleRoute(
|
|
143
|
+
const route = new QueryHandleRoute(
|
|
144
|
+
name,
|
|
145
|
+
rb.validIfFn,
|
|
146
|
+
rb.handleFn,
|
|
147
|
+
rb.vars,
|
|
148
|
+
);
|
|
123
149
|
|
|
124
150
|
router.post('/queries/' + route.name, csr =>
|
|
125
151
|
route.handle(caching.router, csr),
|
|
@@ -4,6 +4,12 @@ import ServerRouter from '../ServerRouter.js';
|
|
|
4
4
|
|
|
5
5
|
export type CacheIfFn = (response: any, vars: Record<string, any>) => boolean;
|
|
6
6
|
|
|
7
|
+
/// Either throw or return a boolean
|
|
8
|
+
export type ValidIfFn = (
|
|
9
|
+
vars: Record<string, any>,
|
|
10
|
+
sr: ServerRouter,
|
|
11
|
+
) => void | boolean;
|
|
12
|
+
|
|
7
13
|
/// Anything other than returning undefined will replace the response
|
|
8
14
|
//
|
|
9
15
|
// Note that even if you return undefined since the response is by reference
|
|
@@ -20,13 +26,14 @@ export type HandleFn = (
|
|
|
20
26
|
) => Promise<any> | any;
|
|
21
27
|
|
|
22
28
|
/**
|
|
23
|
-
* Returns the validated variables if some vars
|
|
29
|
+
* Returns the validated variables if some vars were defined
|
|
24
30
|
* else just returns all vars
|
|
25
31
|
*/
|
|
26
32
|
export function validateVars(
|
|
27
33
|
qvars: Record<string, QueryVar> | null,
|
|
28
34
|
vars: any,
|
|
29
|
-
|
|
35
|
+
validIfFn: ValidIfFn | null,
|
|
36
|
+
sr: ServerRouter,
|
|
30
37
|
): Record<string, any> {
|
|
31
38
|
if (!vars || typeof vars !== 'object')
|
|
32
39
|
throw new Error('expected an object as vars');
|
|
@@ -36,7 +43,14 @@ export function validateVars(
|
|
|
36
43
|
const nVars: Record<string, any> = {};
|
|
37
44
|
|
|
38
45
|
for (const [k, v] of Object.entries(qvars)) {
|
|
39
|
-
nVars[k] = v.validValue(vars[k],
|
|
46
|
+
nVars[k] = v.validValue(vars[k], sr);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (validIfFn) {
|
|
50
|
+
// or throw
|
|
51
|
+
const valid = validIfFn(nVars, sr);
|
|
52
|
+
if (typeof valid === 'boolean' && !valid)
|
|
53
|
+
throw new Error('invalid variables for query');
|
|
40
54
|
}
|
|
41
55
|
|
|
42
56
|
return nVars;
|
package/src/std/index.ts
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { BaseRoute } from '../../routing/index.js';
|
|
2
|
+
import { deleteSearchParam, pathnameEq, searchEq, toUrl } from './utils.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sets the search params of a URL based on the provided options.
|
|
6
|
+
*
|
|
7
|
+
* If a value is `null`, `undefined`, or an empty string, the corresponding
|
|
8
|
+
* search param will be deleted.
|
|
9
|
+
*
|
|
10
|
+
* #### Example
|
|
11
|
+
* ```js
|
|
12
|
+
* urlWithSearch(entry.url, { p: 1 });
|
|
13
|
+
* // or remove a value
|
|
14
|
+
* urlWithSearch(entry.url, { p: null });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function urlWithSearch(
|
|
18
|
+
url: BaseRoute | URL | string | null | undefined,
|
|
19
|
+
opts: Record<string, string | number | null | undefined>,
|
|
20
|
+
): string | null {
|
|
21
|
+
if (!url) return null;
|
|
22
|
+
|
|
23
|
+
url = toUrl(url);
|
|
24
|
+
|
|
25
|
+
for (const [k, v] of Object.entries(opts)) {
|
|
26
|
+
if (!deleteSearchParam(v)) {
|
|
27
|
+
url.searchParams.set(k, v as string);
|
|
28
|
+
} else {
|
|
29
|
+
url.searchParams.delete(k);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return url.href;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compares two URLs for equality.
|
|
38
|
+
* Normally search and hash are ignored.
|
|
39
|
+
*
|
|
40
|
+
* If either or both url is `null` or `undefined`, the function will return
|
|
41
|
+
* `false`.
|
|
42
|
+
*
|
|
43
|
+
* #### Example
|
|
44
|
+
* ```svelte
|
|
45
|
+
* <script>
|
|
46
|
+
* import { getRoute } from 'crelte';
|
|
47
|
+
*
|
|
48
|
+
* const route = getRoute();
|
|
49
|
+
* </script>
|
|
50
|
+
*
|
|
51
|
+
* <a href={item.url} class:active={urlEq($route, item.url)}>
|
|
52
|
+
* {item.title}
|
|
53
|
+
* </a>
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function urlEq(
|
|
57
|
+
a: BaseRoute | URL | string | null | undefined,
|
|
58
|
+
b: BaseRoute | URL | string | null | undefined,
|
|
59
|
+
opts?: { search?: boolean; hash?: boolean },
|
|
60
|
+
): boolean {
|
|
61
|
+
if (!a || !b) return false;
|
|
62
|
+
|
|
63
|
+
a = toUrl(a);
|
|
64
|
+
b = toUrl(b);
|
|
65
|
+
|
|
66
|
+
// check origin and pathname
|
|
67
|
+
const baseMatches =
|
|
68
|
+
a.origin === b.origin && pathnameEq(a.pathname, b.pathname);
|
|
69
|
+
if (!baseMatches) return false;
|
|
70
|
+
|
|
71
|
+
// check search
|
|
72
|
+
if (opts?.search && !searchEq(a.searchParams, b.searchParams)) return false;
|
|
73
|
+
|
|
74
|
+
// check hash
|
|
75
|
+
if (opts?.hash && a.hash !== b.hash) return false;
|
|
76
|
+
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { BaseRoute } from '../../routing/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a search param should be removed.
|
|
5
|
+
* This is the case if the value is `null`, `undefined`, or an empty string.
|
|
6
|
+
*/
|
|
7
|
+
export function deleteSearchParam(value: string | number | null | undefined) {
|
|
8
|
+
return (
|
|
9
|
+
typeof value === 'undefined' ||
|
|
10
|
+
value === null ||
|
|
11
|
+
(typeof value === 'string' && value === '')
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Converts a `BaseRoute`, `URL`, or string to a `URL` object.
|
|
17
|
+
*/
|
|
18
|
+
export function toUrl(url: BaseRoute | URL | string): URL {
|
|
19
|
+
if (typeof url === 'string') return new URL(url);
|
|
20
|
+
|
|
21
|
+
if (url instanceof BaseRoute) return new URL(url.url);
|
|
22
|
+
|
|
23
|
+
return url;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compares two `URLSearchParams` objects for equality.
|
|
28
|
+
*/
|
|
29
|
+
export function searchEq(a: URLSearchParams, b: URLSearchParams): boolean {
|
|
30
|
+
if (a.size !== b.size) return false;
|
|
31
|
+
|
|
32
|
+
// Clone to avoid mutating the original objects
|
|
33
|
+
const cloneA = new URLSearchParams(a);
|
|
34
|
+
const cloneB = new URLSearchParams(b);
|
|
35
|
+
|
|
36
|
+
cloneA.sort();
|
|
37
|
+
cloneB.sort();
|
|
38
|
+
|
|
39
|
+
return cloneA.toString() === cloneB.toString();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Compares two pathnames for equality, ignoring trailing slashes.
|
|
44
|
+
*/
|
|
45
|
+
export function pathnameEq(a: string, b: string): boolean {
|
|
46
|
+
// check for trailing slashes
|
|
47
|
+
return a === b || a === b + '/' || a + '/' === b;
|
|
48
|
+
}
|
package/src/vite/index.ts
CHANGED
|
@@ -180,7 +180,7 @@ export default function crelte(opts?: CrelteOptions): Plugin {
|
|
|
180
180
|
publicDir: isSsrBuild ? false : 'public',
|
|
181
181
|
base: '/',
|
|
182
182
|
server: {
|
|
183
|
-
port: 8080,
|
|
183
|
+
port: config.server?.port ?? 8080,
|
|
184
184
|
},
|
|
185
185
|
optimizeDeps: {
|
|
186
186
|
exclude: ['crelte'],
|
|
@@ -338,7 +338,9 @@ async function serveVite(env: EnvData, vite: ViteDevServer) {
|
|
|
338
338
|
nReq: Connect.IncomingMessage,
|
|
339
339
|
res: ServerResponse,
|
|
340
340
|
) => {
|
|
341
|
-
const protocol =
|
|
341
|
+
const protocol =
|
|
342
|
+
(nReq.headers['x-forwarded-proto'] as string | undefined) ??
|
|
343
|
+
(vite.config.server.https ? 'https' : 'http');
|
|
342
344
|
const baseUrl = protocol + '://' + nReq.headers['host'];
|
|
343
345
|
|
|
344
346
|
const req = requestToWebRequest(baseUrl, nReq);
|