crelte 0.5.12 → 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/blocks/Blocks.d.ts +1 -1
- package/dist/blocks/Blocks.d.ts.map +1 -1
- package/dist/bodyClass/BodyClass.d.ts +18 -1
- package/dist/bodyClass/BodyClass.d.ts.map +1 -1
- package/dist/bodyClass/BodyClass.js +21 -2
- package/dist/bodyClass/ClientBodyClass.d.ts +7 -2
- package/dist/bodyClass/ClientBodyClass.d.ts.map +1 -1
- package/dist/bodyClass/ClientBodyClass.js +29 -15
- package/dist/bodyClass/ServerBodyClass.d.ts +7 -1
- package/dist/bodyClass/ServerBodyClass.d.ts.map +1 -1
- package/dist/bodyClass/ServerBodyClass.js +19 -5
- package/dist/bodyClass/utils.d.ts +39 -0
- package/dist/bodyClass/utils.d.ts.map +1 -0
- package/dist/bodyClass/utils.js +84 -0
- package/dist/crelte.d.ts.map +1 -1
- package/dist/crelte.js +2 -0
- package/dist/init/client.js +1 -1
- package/dist/init/server.d.ts.map +1 -1
- package/dist/init/server.js +1 -0
- package/dist/init/shared.d.ts.map +1 -1
- package/dist/init/shared.js +3 -0
- package/dist/loadData/entry.d.ts +11 -2
- package/dist/loadData/entry.d.ts.map +1 -1
- package/dist/loadData/entry.js +11 -2
- 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 +22 -1
- 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 +7 -5
- 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 +4 -3
- 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/blocks/Blocks.ts +1 -1
- package/src/bodyClass/BodyClass.ts +25 -3
- package/src/bodyClass/ClientBodyClass.ts +37 -20
- package/src/bodyClass/ServerBodyClass.ts +28 -7
- package/src/bodyClass/utils.ts +118 -0
- package/src/crelte.ts +3 -0
- package/src/init/client.ts +1 -1
- package/src/init/server.ts +1 -0
- package/src/init/shared.ts +3 -0
- package/src/loadData/entry.ts +12 -2
- package/src/queries/Queries.ts +3 -0
- package/src/queries/index.ts +30 -1
- package/src/queries/vars.ts +58 -3
- package/src/routing/route/BaseRoute.ts +8 -22
- package/src/server/queries/QueryGqlRoute.ts +20 -4
- package/src/server/queries/QueryHandleRoute.ts +5 -2
- package/src/server/queries/queries.ts +29 -3
- package/src/server/queries/routes.ts +18 -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
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/blocks/Blocks.ts
CHANGED
|
@@ -125,7 +125,7 @@ export class BlockModules {
|
|
|
125
125
|
* ```
|
|
126
126
|
*/
|
|
127
127
|
export function blockModules(
|
|
128
|
-
modules: Record<string,
|
|
128
|
+
modules: Record<string, (() => Promise<any>) | Module>,
|
|
129
129
|
opts: BlockModulesOptions = {},
|
|
130
130
|
): BlockModules {
|
|
131
131
|
return new BlockModules(modules, opts);
|
|
@@ -41,15 +41,33 @@ export default class BodyClass {
|
|
|
41
41
|
* it.
|
|
42
42
|
*/
|
|
43
43
|
toggle(cls: string, force?: boolean): void {
|
|
44
|
-
this.inner.toggle(cls, force);
|
|
45
|
-
this.store.set();
|
|
44
|
+
if (this.inner.toggle(cls, force)) this.store.set();
|
|
46
45
|
}
|
|
47
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Removes the given classes from the body
|
|
49
|
+
*/
|
|
48
50
|
remove(...classes: string[]): void {
|
|
49
51
|
this.inner.remove(...classes);
|
|
50
52
|
this.store.set();
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Sets the class for the given variant removing the old class for that
|
|
57
|
+
* variant, if cls is null it will remove the variant class
|
|
58
|
+
*
|
|
59
|
+
* If you just have like a dark or light mode and only for the dark mode a
|
|
60
|
+
* class, **prefer** `toggle('dark', isDarkMode)` over
|
|
61
|
+
* `setVariant('mode', isDarkMode ? 'dark' : null)`
|
|
62
|
+
*
|
|
63
|
+
* ## Note
|
|
64
|
+
* The variant name is only used for the internal state management
|
|
65
|
+
* and has no inpact on the actual class name
|
|
66
|
+
*/
|
|
67
|
+
setVariant(variant: string, cls: string | null): void {
|
|
68
|
+
if (this.inner.setVariant(variant, cls)) this.store.set();
|
|
69
|
+
}
|
|
70
|
+
|
|
53
71
|
/** @hidden */
|
|
54
72
|
z_toRequest(): BodyClass {
|
|
55
73
|
return new BodyClass(this.inner.toRequest(), this.store.stage());
|
|
@@ -65,8 +83,12 @@ export default class BodyClass {
|
|
|
65
83
|
export interface PlatformBodyClass {
|
|
66
84
|
contains(cls: string): boolean;
|
|
67
85
|
add(...classes: string[]): void;
|
|
68
|
-
|
|
86
|
+
// returns true if the value was changed (this is different to the
|
|
87
|
+
// DOMTokenList.toggle which returns true if the class is now present)
|
|
88
|
+
toggle(cls: string, force?: boolean): boolean;
|
|
69
89
|
remove(...classes: string[]): void;
|
|
90
|
+
// returns true if the value was changed
|
|
91
|
+
setVariant(variant: string, cls: string | null): boolean;
|
|
70
92
|
toRequest(): PlatformBodyClass;
|
|
71
93
|
render?: () => void;
|
|
72
94
|
}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
+
import SsrCache from '../ssr/SsrCache.js';
|
|
1
2
|
import { PlatformBodyClass } from './BodyClass.js';
|
|
3
|
+
import { ClassSet, ssrCacheToVariants, Variants } from './utils.js';
|
|
2
4
|
|
|
3
5
|
export default class ClientBodyClass implements PlatformBodyClass {
|
|
4
|
-
private
|
|
6
|
+
private variants: Variants;
|
|
7
|
+
// during the request store the classes here
|
|
8
|
+
private inner: ClassSet | null;
|
|
5
9
|
|
|
6
|
-
constructor(inner?:
|
|
10
|
+
constructor(variants: Variants, inner?: ClassSet) {
|
|
11
|
+
this.variants = variants;
|
|
7
12
|
this.inner = inner ?? null;
|
|
8
13
|
}
|
|
9
14
|
|
|
15
|
+
static fromSsrCache(ssrCache: SsrCache): ClientBodyClass {
|
|
16
|
+
const variants = ssrCacheToVariants(ssrCache);
|
|
17
|
+
return new ClientBodyClass(variants);
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
contains(cls: string): boolean {
|
|
11
21
|
if (this.inner) return this.inner.has(cls);
|
|
12
22
|
return cl().contains(cls);
|
|
@@ -17,16 +27,20 @@ export default class ClientBodyClass implements PlatformBodyClass {
|
|
|
17
27
|
else cl().add(...classes);
|
|
18
28
|
}
|
|
19
29
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
// returns true if the value was changed
|
|
31
|
+
toggle(cls: string, force?: boolean): boolean {
|
|
32
|
+
const exists = this.contains(cls);
|
|
33
|
+
const shouldExist = typeof force === 'boolean' ? force : !exists;
|
|
34
|
+
const changed = shouldExist !== exists;
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
if (this.inner) {
|
|
37
|
+
if (shouldExist) this.inner.add(cls);
|
|
26
38
|
else this.inner.delete(cls);
|
|
27
39
|
} else {
|
|
28
40
|
cl().toggle(cls, force);
|
|
29
41
|
}
|
|
42
|
+
|
|
43
|
+
return changed;
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
remove(...classes: string[]): void {
|
|
@@ -34,26 +48,29 @@ export default class ClientBodyClass implements PlatformBodyClass {
|
|
|
34
48
|
else cl().remove(...classes);
|
|
35
49
|
}
|
|
36
50
|
|
|
51
|
+
setVariant(variant: string, cls: string | null): boolean {
|
|
52
|
+
if (this.inner) return this.inner.setVariant(variant, cls);
|
|
53
|
+
|
|
54
|
+
const { remove, add } = this.variants.set(variant, cls);
|
|
55
|
+
|
|
56
|
+
if (remove) cl().remove(remove);
|
|
57
|
+
if (add) cl().add(add);
|
|
58
|
+
|
|
59
|
+
return !!remove || !!add;
|
|
60
|
+
}
|
|
61
|
+
|
|
37
62
|
toRequest(): ClientBodyClass {
|
|
38
|
-
const inner = new
|
|
39
|
-
return new ClientBodyClass(inner);
|
|
63
|
+
const inner = new ClassSet(cl(), this.variants.entries(), true);
|
|
64
|
+
return new ClientBodyClass(this.variants, inner);
|
|
40
65
|
}
|
|
41
66
|
|
|
42
67
|
render(): void {
|
|
43
68
|
if (!this.inner) throw new Error('call toRequest first');
|
|
44
|
-
const current = new Set(cl());
|
|
45
|
-
|
|
46
|
-
for (const cls of this.inner) {
|
|
47
|
-
const existed = current.delete(cls);
|
|
48
|
-
if (!existed) cl().add(cls);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// now lets remove all classes that still exist in current
|
|
52
|
-
for (const cls of current) {
|
|
53
|
-
cl().remove(cls);
|
|
54
|
-
}
|
|
55
69
|
|
|
70
|
+
const inner = this.inner;
|
|
56
71
|
this.inner = null;
|
|
72
|
+
|
|
73
|
+
inner.applyHistory(this);
|
|
57
74
|
}
|
|
58
75
|
}
|
|
59
76
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import SsrCache from '../ssr/SsrCache.js';
|
|
1
2
|
import { PlatformBodyClass } from './BodyClass.js';
|
|
3
|
+
import { ClassSet, variantsToSsrCache } from './utils.js';
|
|
2
4
|
|
|
3
5
|
export default class ServerBodyClass implements PlatformBodyClass {
|
|
4
|
-
private inner:
|
|
6
|
+
private inner: ClassSet;
|
|
5
7
|
|
|
6
8
|
constructor() {
|
|
7
|
-
this.inner = new
|
|
9
|
+
this.inner = new ClassSet();
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
contains(cls: string): boolean {
|
|
@@ -16,7 +18,7 @@ export default class ServerBodyClass implements PlatformBodyClass {
|
|
|
16
18
|
classes.forEach(cls => this.inner.add(cls));
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
toggle(cls: string, force?: boolean):
|
|
21
|
+
toggle(cls: string, force?: boolean): boolean {
|
|
20
22
|
validate([cls]);
|
|
21
23
|
|
|
22
24
|
if (import.meta.env.DEV && typeof force !== 'boolean') {
|
|
@@ -27,10 +29,14 @@ export default class ServerBodyClass implements PlatformBodyClass {
|
|
|
27
29
|
);
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
const
|
|
32
|
+
const exists = this.inner.has(cls);
|
|
33
|
+
const shouldExist = typeof force === 'boolean' ? force : !exists;
|
|
34
|
+
const changed = shouldExist !== exists;
|
|
31
35
|
|
|
32
|
-
if (
|
|
36
|
+
if (shouldExist) this.inner.add(cls);
|
|
33
37
|
else this.inner.delete(cls);
|
|
38
|
+
|
|
39
|
+
return changed;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
remove(...classes: string[]): void {
|
|
@@ -38,19 +44,34 @@ export default class ServerBodyClass implements PlatformBodyClass {
|
|
|
38
44
|
classes.forEach(cls => this.inner.delete(cls));
|
|
39
45
|
}
|
|
40
46
|
|
|
47
|
+
/**
|
|
48
|
+
* @returns Returns true if the value was changed
|
|
49
|
+
*/
|
|
50
|
+
setVariant(variant: string, cls: string | null): boolean {
|
|
51
|
+
return this.inner.setVariant(variant, cls);
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
toRequest(): ServerBodyClass {
|
|
55
|
+
// no second request should ever start on the server
|
|
42
56
|
return this;
|
|
43
57
|
}
|
|
44
58
|
|
|
59
|
+
z_populateSsrCache(ssrCache: SsrCache): void {
|
|
60
|
+
variantsToSsrCache(this.inner.z_variants, ssrCache);
|
|
61
|
+
}
|
|
62
|
+
|
|
45
63
|
z_processHtmlTemplate(html: string): string {
|
|
46
64
|
const SEARCH_STR = '<!--body-class-->';
|
|
47
|
-
if (this.inner.
|
|
65
|
+
if (this.inner.length && !html.includes(SEARCH_STR)) {
|
|
48
66
|
throw new Error(
|
|
49
67
|
'index.html needs to contain `class="<!--body-class-->"`',
|
|
50
68
|
);
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
return html.replace(
|
|
71
|
+
return html.replace(
|
|
72
|
+
SEARCH_STR,
|
|
73
|
+
Array.from(this.inner.classes()).join(' '),
|
|
74
|
+
);
|
|
54
75
|
}
|
|
55
76
|
}
|
|
56
77
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import SsrCache from '../ssr/SsrCache.js';
|
|
2
|
+
|
|
3
|
+
export type HistoryClassSet = {
|
|
4
|
+
add(cls: string): void;
|
|
5
|
+
remove(cls: string): void;
|
|
6
|
+
setVariant(variant: string, cls: string | null): void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export class ClassSet {
|
|
10
|
+
private _classes: Set<string>;
|
|
11
|
+
/** @hidden */
|
|
12
|
+
z_variants: Variants;
|
|
13
|
+
private history: ((cl: HistoryClassSet) => void)[] | null;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
classes?: Iterable<string>,
|
|
17
|
+
variants?: Iterable<[string, string]>,
|
|
18
|
+
history: boolean = false,
|
|
19
|
+
) {
|
|
20
|
+
this._classes = new Set(classes);
|
|
21
|
+
this.z_variants = new Variants();
|
|
22
|
+
this.history = history ? [] : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
classes(): Iterable<string> {
|
|
26
|
+
return this._classes;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get length(): number {
|
|
30
|
+
return this._classes.size;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
has(cls: string): boolean {
|
|
34
|
+
return this._classes.has(cls);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
add(cls: string): void {
|
|
38
|
+
this._classes.add(cls);
|
|
39
|
+
if (this.history) this.history.push(c => c.add(cls));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
delete(cls: string): void {
|
|
43
|
+
this._classes.delete(cls);
|
|
44
|
+
if (this.history) this.history.push(c => c.remove(cls));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @returns Returns true if the value was changed
|
|
49
|
+
*/
|
|
50
|
+
setVariant(variant: string, cls: string | null): boolean {
|
|
51
|
+
const { remove, add } = this.z_variants.set(variant, cls);
|
|
52
|
+
|
|
53
|
+
if (remove) this.delete(remove);
|
|
54
|
+
if (add) this.add(add);
|
|
55
|
+
|
|
56
|
+
const changed = !!remove || !!add;
|
|
57
|
+
|
|
58
|
+
if (this.history) this.history.push(c => c.setVariant(variant, cls));
|
|
59
|
+
|
|
60
|
+
return changed;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Fails if no history is active.
|
|
65
|
+
*/
|
|
66
|
+
applyHistory(cl: HistoryClassSet): void {
|
|
67
|
+
this.history!.forEach(fn => fn(cl));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type VariantSetReturn = {
|
|
72
|
+
remove?: string;
|
|
73
|
+
add?: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export class Variants {
|
|
77
|
+
// list of active variant classes
|
|
78
|
+
private inner: Map<string, string>;
|
|
79
|
+
|
|
80
|
+
constructor(entries?: Iterable<[string, string]>) {
|
|
81
|
+
this.inner = new Map(entries);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
set(variant: string, cls: string | null): VariantSetReturn {
|
|
85
|
+
const current = this.inner.get(variant) ?? null;
|
|
86
|
+
|
|
87
|
+
if (current === cls) return {};
|
|
88
|
+
|
|
89
|
+
const obj: VariantSetReturn = {};
|
|
90
|
+
|
|
91
|
+
if (current) obj.remove = current;
|
|
92
|
+
if (cls) {
|
|
93
|
+
obj.add = cls;
|
|
94
|
+
this.inner.set(variant, cls);
|
|
95
|
+
} else {
|
|
96
|
+
this.inner.delete(variant);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return obj;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
entries(): Iterable<[string, string]> {
|
|
103
|
+
return this.inner.entries();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// separate function for tree shaking
|
|
108
|
+
export function ssrCacheToVariants(ssrCache: SsrCache): Variants {
|
|
109
|
+
return new Variants(ssrCache.get('BODY_CLASS_VAR')!);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// separate function for tree shaking
|
|
113
|
+
export function variantsToSsrCache(
|
|
114
|
+
variants: Variants,
|
|
115
|
+
ssrCache: SsrCache,
|
|
116
|
+
): void {
|
|
117
|
+
ssrCache.set('BODY_CLASS_VAR', Array.from(variants.entries()));
|
|
118
|
+
}
|
package/src/crelte.ts
CHANGED
|
@@ -292,6 +292,9 @@ export function newCrelte({
|
|
|
292
292
|
cookies,
|
|
293
293
|
bodyClass,
|
|
294
294
|
|
|
295
|
+
// when adding a new helper function make sure to add it to
|
|
296
|
+
// onNewCrelteRequest if needed
|
|
297
|
+
|
|
295
298
|
getPlugin: name => plugins.get(name),
|
|
296
299
|
getEnv: key => ssrCache.get(key as any) as any,
|
|
297
300
|
frontendUrl: path => urlWithPath(ssrCache.get('FRONTEND_URL')!, path),
|
package/src/init/client.ts
CHANGED
|
@@ -88,7 +88,7 @@ export async function main(data: MainData) {
|
|
|
88
88
|
router: new Router(router),
|
|
89
89
|
queries,
|
|
90
90
|
cookies: new Cookies(new ClientCookies()),
|
|
91
|
-
bodyClass: new BodyClass(
|
|
91
|
+
bodyClass: new BodyClass(ClientBodyClass.fromSsrCache(ssrCache)),
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
const app = new InternalApp(data.app);
|
package/src/init/server.ts
CHANGED
package/src/init/shared.ts
CHANGED
|
@@ -74,6 +74,9 @@ export function onNewCrelteRequest(
|
|
|
74
74
|
cookies: crelte.cookies.z_toRequest(),
|
|
75
75
|
bodyClass: crelte.bodyClass.z_toRequest(),
|
|
76
76
|
};
|
|
77
|
+
// make sure helper funcitons link to the correct instances
|
|
78
|
+
nCrelte.getPlugin = name => nCrelte.plugins.get(name);
|
|
79
|
+
nCrelte.getGlobalStore = name => nCrelte.globals.getStore(name);
|
|
77
80
|
return crelteToRequest(nCrelte, req);
|
|
78
81
|
}
|
|
79
82
|
|
package/src/loadData/entry.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CrelteRequest } from '../index.js';
|
|
2
|
-
import { Query } from '../queries/Queries.js';
|
|
2
|
+
import { Query, QueryOptions } from '../queries/Queries.js';
|
|
3
3
|
|
|
4
4
|
export type Entry = {
|
|
5
5
|
sectionHandle: string;
|
|
@@ -31,12 +31,22 @@ export function entryQueryVars(cr: CrelteRequest): EntryQueryVars {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* ## Example
|
|
36
|
+
* `App.svelte`
|
|
37
|
+
* ```ts
|
|
38
|
+
* import entryQuery from '@/queries/entry.graphql';
|
|
39
|
+
*
|
|
40
|
+
* export const loadEntry => cr => queryEntry(cr, entryQuery, entryQueryVars(cr));
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
34
43
|
export async function queryEntry(
|
|
35
44
|
cr: CrelteRequest,
|
|
36
45
|
entryQuery: Query,
|
|
37
46
|
vars: EntryQueryVars,
|
|
47
|
+
opts?: QueryOptions,
|
|
38
48
|
): Promise<Entry> {
|
|
39
|
-
const page = await cr.query(entryQuery, vars);
|
|
49
|
+
const page = await cr.query(entryQuery, vars, opts);
|
|
40
50
|
return extractEntry(page) ?? ENTRY_ERROR_404;
|
|
41
51
|
}
|
|
42
52
|
|
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
|
*
|
|
@@ -81,7 +106,11 @@ export type TransformFn<
|
|
|
81
106
|
export type Transform<
|
|
82
107
|
T extends Record<string, QueryVar<any>> = Record<string, QueryVar<any>>,
|
|
83
108
|
F extends TransformFn<T> = TransformFn<T>,
|
|
84
|
-
> = (
|
|
109
|
+
> = (
|
|
110
|
+
response: any,
|
|
111
|
+
vars: InferVariableTypes<T>,
|
|
112
|
+
csr: CrelteServerRequest,
|
|
113
|
+
) => Awaited<ReturnType<F>>;
|
|
85
114
|
|
|
86
115
|
/** use {@link Handle} */
|
|
87
116
|
export type HandleFn<
|
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
|
|