eddev 2.3.12 → 2.3.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/app/entry/MetaTags.d.ts +1 -1
- package/dist/app/entry/MetaTags.d.ts.map +1 -1
- package/dist/app/entry/MetaTags.js +2 -0
- package/dist/app/entry/spa-root.d.ts +1 -1
- package/dist/app/entry/spa-root.d.ts.map +1 -1
- package/dist/app/entry/ssr-root-client.d.ts +1 -1
- package/dist/app/entry/ssr-root-client.d.ts.map +1 -1
- package/dist/app/entry/ssr-root.d.ts +1 -1
- package/dist/app/entry/ssr-root.d.ts.map +1 -1
- package/dist/app/lib/blocks/BlockPropMutator.d.ts +1 -1
- package/dist/app/lib/blocks/BlockPropMutator.d.ts.map +1 -1
- package/dist/app/lib/blocks/ContentBlocks.d.ts +2 -2
- package/dist/app/lib/blocks/ContentBlocks.d.ts.map +1 -1
- package/dist/app/lib/blocks/EditableText.d.ts +1 -1
- package/dist/app/lib/blocks/EditableText.d.ts.map +1 -1
- package/dist/app/lib/blocks/InnerBlocks.d.ts +1 -1
- package/dist/app/lib/blocks/InnerBlocks.d.ts.map +1 -1
- package/dist/app/lib/blocks/SlotBlocks.d.ts +1 -1
- package/dist/app/lib/blocks/SlotBlocks.d.ts.map +1 -1
- package/dist/app/lib/blocks/block-debugger.d.ts +3 -3
- package/dist/app/lib/blocks/block-debugger.d.ts.map +1 -1
- package/dist/app/lib/blocks/block-utils.d.ts +2 -2
- package/dist/app/lib/blocks/block-utils.d.ts.map +1 -1
- package/dist/app/lib/blocks/block-utils.js +10 -3
- package/dist/app/lib/blocks/editor/EditableBlock.d.ts +1 -1
- package/dist/app/lib/blocks/editor/EditableBlock.d.ts.map +1 -1
- package/dist/app/lib/blocks/editor/EditorHighlights.d.ts +1 -1
- package/dist/app/lib/blocks/editor/EditorHighlights.d.ts.map +1 -1
- package/dist/app/lib/blocks/editor/ErrorBoundaryEditor.d.ts +1 -1
- package/dist/app/lib/blocks/editor/controls.d.ts +2 -2
- package/dist/app/lib/blocks/editor/controls.d.ts.map +1 -1
- package/dist/app/lib/blocks/editor/installGutenbergHooks.d.ts.map +1 -1
- package/dist/app/lib/blocks/editor/installGutenbergHooks.js +8 -4
- package/dist/app/lib/blocks/editor/root-blocks.d.ts.map +1 -1
- package/dist/app/lib/blocks/editor/root-blocks.js +2 -2
- package/dist/app/lib/blocks/inline-editing.d.ts +2 -2
- package/dist/app/lib/blocks/inline-editing.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/BreakpointIndicator.d.ts +1 -1
- package/dist/app/lib/devtools/components/BreakpointIndicator.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/DevMenu.d.ts +1 -1
- package/dist/app/lib/devtools/components/DevMenu.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/DevUI.d.ts +2 -2
- package/dist/app/lib/devtools/components/DevUI.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/panels/AppDataPanel.d.ts +2 -2
- package/dist/app/lib/devtools/components/panels/AppDataPanel.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/panels/QueriesPanel.d.ts +1 -1
- package/dist/app/lib/devtools/components/panels/QueriesPanel.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/panels/RoutePanel.d.ts +2 -2
- package/dist/app/lib/devtools/components/panels/RoutePanel.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/panels/StructurePanel.d.ts +1 -1
- package/dist/app/lib/devtools/components/panels/StructurePanel.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/ui/Button.d.ts +1 -1
- package/dist/app/lib/devtools/components/ui/Button.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/ui/Chip.d.ts +2 -2
- package/dist/app/lib/devtools/components/ui/Chip.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/ui/Expander.d.ts +1 -1
- package/dist/app/lib/devtools/components/ui/Expander.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/ui/JSONInspector.d.ts +1 -1
- package/dist/app/lib/devtools/components/ui/JSONInspector.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/ui/Panel.d.ts +1 -1
- package/dist/app/lib/devtools/components/ui/Panel.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/ui/QueryMonitorDisplay.d.ts +1 -1
- package/dist/app/lib/devtools/components/ui/QueryMonitorDisplay.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/ui/TabBar.d.ts +2 -2
- package/dist/app/lib/devtools/components/ui/TabBar.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/ui/Tabs.d.ts +1 -1
- package/dist/app/lib/devtools/components/ui/Tabs.d.ts.map +1 -1
- package/dist/app/lib/devtools/hooks/useTailwind.d.ts +45 -45
- package/dist/app/lib/devtools/icons.d.ts +24 -24
- package/dist/app/lib/devtools/icons.d.ts.map +1 -1
- package/dist/app/lib/hooks/query-hooks.d.ts +8 -8
- package/dist/app/lib/hooks/query-hooks.d.ts.map +1 -1
- package/dist/app/lib/hooks/query-hooks.js +2 -1
- package/dist/app/lib/integrations/gravityforms/createGravityFormComponent.d.ts +15 -15
- package/dist/app/lib/routing/components/BrowserRouter.d.ts +1 -1
- package/dist/app/lib/routing/components/BrowserRouter.d.ts.map +1 -1
- package/dist/app/lib/routing/components/ClientOnly.d.ts +1 -1
- package/dist/app/lib/routing/components/ClientOnly.d.ts.map +1 -1
- package/dist/app/lib/routing/components/NativeLinkHandler.d.ts +1 -1
- package/dist/app/lib/routing/components/NativeLinkHandler.d.ts.map +1 -1
- package/dist/app/lib/routing/components/RouteRenderer.d.ts +3 -3
- package/dist/app/lib/routing/components/RouteRenderer.d.ts.map +1 -1
- package/dist/app/lib/routing/components/SSRRouter.d.ts +1 -1
- package/dist/app/lib/routing/components/SSRRouter.d.ts.map +1 -1
- package/dist/app/lib/runtime/apiConfig.d.ts +15 -0
- package/dist/app/lib/runtime/apiConfig.d.ts.map +1 -1
- package/dist/app/server/proxy-wp-admin.d.ts.map +1 -1
- package/dist/app/server/proxy-wp-admin.js +1 -0
- package/dist/app/server/server-context.d.ts +2 -2
- package/dist/app/server/server-context.d.ts.map +1 -1
- package/dist/app/utils/APIProvider.d.ts +1 -1
- package/dist/app/utils/APIProvider.d.ts.map +1 -1
- package/dist/app/utils/ErrorMessage.d.ts +1 -1
- package/dist/app/utils/ErrorMessage.d.ts.map +1 -1
- package/dist/app/utils/query-client.d.ts +1 -1
- package/dist/app/utils/query-client.d.ts.map +1 -1
- package/dist/app/utils/trpc-client.d.ts.map +1 -1
- package/dist/app/utils/trpc-client.js +19 -3
- package/dist/node/cli/display/CLIApp.d.ts +1 -1
- package/dist/node/cli/display/CLIApp.d.ts.map +1 -1
- package/dist/node/cli/display/components/Fullscreen.d.ts +1 -1
- package/dist/node/cli/display/components/Fullscreen.d.ts.map +1 -1
- package/dist/node/cli/display/components/LogEntries.d.ts +4 -4
- package/dist/node/cli/display/components/LogEntries.d.ts.map +1 -1
- package/dist/node/cli/display/components/MenuItem.d.ts +1 -1
- package/dist/node/cli/display/components/MenuItem.d.ts.map +1 -1
- package/dist/node/cli/display/components/TextInput.d.ts +3 -2
- package/dist/node/cli/display/components/TextInput.d.ts.map +1 -1
- package/dist/node/cli/display/tools/BlockList.d.ts +1 -1
- package/dist/node/cli/display/tools/BlockList.d.ts.map +1 -1
- package/dist/node/cli/display/tools/CreateBlock.d.ts +1 -1
- package/dist/node/cli/display/tools/CreateBlock.d.ts.map +1 -1
- package/dist/node/cli/version.d.ts +1 -1
- package/dist/node/cli/version.js +1 -1
- package/dist/node/compiler/dev-server.js +1 -1
- package/dist/node/compiler/get-vite-config.d.ts.map +1 -1
- package/dist/node/compiler/get-vite-config.js +1 -0
- package/dist/node/compiler/vinxi-app.d.ts +1 -1
- package/dist/node/compiler/vinxi-app.d.ts.map +1 -1
- package/dist/node/utils/fetch-wp.d.ts.map +1 -1
- package/dist/node/utils/fetch-wp.js +2 -1
- package/dist/node/utils/fs.d.ts +22 -19
- package/dist/node/utils/fs.d.ts.map +1 -1
- package/package.json +2 -2
- package/skills/eddev/SKILL.md +156 -0
- package/skills/eddev/docs/acf/admin-panel-widgets.mdx +99 -0
- package/skills/eddev/docs/acf/custom-enums.mdx +75 -0
- package/skills/eddev/docs/acf/custom-fields.mdx +131 -0
- package/skills/eddev/docs/acf.mdx +31 -0
- package/skills/eddev/docs/blocks/block-definition.mdx +189 -0
- package/skills/eddev/docs/blocks/core-blocks.mdx +86 -0
- package/skills/eddev/docs/blocks/data-and-editing.mdx +219 -0
- package/skills/eddev/docs/blocks/editor-config.mdx +157 -0
- package/skills/eddev/docs/blocks/nested-blocks.mdx +129 -0
- package/skills/eddev/docs/blocks/overview.mdx +58 -0
- package/skills/eddev/docs/blocks/template-parts.mdx +131 -0
- package/skills/eddev/docs/config.mdx +200 -0
- package/skills/eddev/docs/design/color.mdx +185 -0
- package/skills/eddev/docs/design/favicons.mdx +103 -0
- package/skills/eddev/docs/design/grid.mdx +120 -0
- package/skills/eddev/docs/design/icons.mdx +197 -0
- package/skills/eddev/docs/design/responsive-scaling.mdx +312 -0
- package/skills/eddev/docs/design/type.mdx +125 -0
- package/skills/eddev/docs/devtool/cli.mdx +201 -0
- package/skills/eddev/docs/devtool/overlay.mdx +5 -0
- package/skills/eddev/docs/getting-started.mdx +53 -0
- package/skills/eddev/docs/graphql/extending.mdx +186 -0
- package/skills/eddev/docs/graphql/fragments.mdx +107 -0
- package/skills/eddev/docs/graphql/global-data.mdx +47 -0
- package/skills/eddev/docs/graphql/infinite-queries.mdx +95 -0
- package/skills/eddev/docs/graphql/mutation-hooks.mdx +111 -0
- package/skills/eddev/docs/graphql/query-hooks.mdx +122 -0
- package/skills/eddev/docs/graphql/tooling.mdx +50 -0
- package/skills/eddev/docs/graphql.mdx +97 -0
- package/skills/eddev/docs/guides/color-schemes.mdx +204 -0
- package/skills/eddev/docs/guides/integrations.mdx +3 -0
- package/skills/eddev/docs/guides/page-transitions.mdx +5 -0
- package/skills/eddev/docs/guides/seo.mdx +5 -0
- package/skills/eddev/docs/guides/state-management.mdx +5 -0
- package/skills/eddev/docs/infra/caching.mdx +9 -0
- package/skills/eddev/docs/infra/deployment.mdx +13 -0
- package/skills/eddev/docs/infra/local.mdx +5 -0
- package/skills/eddev/docs/infra/security.mdx +11 -0
- package/skills/eddev/docs/routing/api.mdx +731 -0
- package/skills/eddev/docs/routing/custom.mdx +123 -0
- package/skills/eddev/docs/routing/full-details.mdx +37 -0
- package/skills/eddev/docs/routing/wordpress.mdx +70 -0
- package/skills/eddev/docs/routing.mdx +18 -0
- package/skills/eddev/docs/serverless/functions.mdx +436 -0
- package/skills/eddev/docs/serverless.mdx +202 -0
- package/skills/eddev/docs/snippets/automated-block-layouts.mdx +97 -0
- package/skills/eddev/docs/snippets/custom-routes-and-urls.mdx +91 -0
- package/skills/eddev/docs/snippets/multiple-editable-zones.mdx +87 -0
- package/skills/eddev/docs/snippets/querying-specific-blocks.mdx +164 -0
- package/skills/eddev/docs/snippets/submitting-forms-to-rpc.mdx +91 -0
- package/skills/eddev/docs/snippets/svgs.mdx +38 -0
- package/skills/eddev/docs/snippets/type-safe-acf-dropdowns.mdx +72 -0
- package/skills/eddev/docs/snippets.mdx +19 -0
- package/skills/eddev/docs/software.mdx +19 -0
- package/skills/eddev/docs/stack/how-it-works.mdx +50 -0
- package/skills/eddev/docs/stack/overview.mdx +56 -0
- package/skills/eddev/docs/stack/spa-vs-ssr.mdx +52 -0
- package/skills/eddev/docs/views/app-view.mdx +97 -0
- package/skills/eddev/docs/views/page-templates.mdx +82 -0
- package/skills/eddev/docs/views/queries.mdx +116 -0
- package/skills/eddev/docs/views.mdx +63 -0
- package/skills/eddev/index.mdx +79 -0
- package/tsconfig.app.json +2 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Custom Routes (/docs/routing/custom)
|
|
2
|
+
|
|
3
|
+
**Register fixed WordPress-backed routes for app-like screens.**
|
|
4
|
+
|
|
5
|
+
In cases where we need a custom route, which does not necessarily map to a content type, you can use the `ED()->addCustomRoute()` function.
|
|
6
|
+
|
|
7
|
+
This is mostly useful for sites which include app-like functionality — like login or checkout screens — where the URL should be fixed, and it shouldn't be removable by a client. It can also be useful for providing URLs to content which isn't a WordPress post or page, or for advanced customisation of routing.
|
|
8
|
+
|
|
9
|
+
The `ED()->addCustomRoute()` function takes two arguments:
|
|
10
|
+
|
|
11
|
+
1. The route pattern (a string), which is a simple PHP regular expression. Parenthesised groups can be extracted using dollar sign formatting.
|
|
12
|
+
2. Options for the custom route:
|
|
13
|
+
* `template` (string, required) — pointing to the `.tsx` template file to be used, starting with `views/`
|
|
14
|
+
* `queryVars` (array, optional) — allows you to map a URL segment to a GraphQL parameter.
|
|
15
|
+
* `extraVars` (function) — accepts a PHP function which takes the `queryVars` from the previous step, and can return additional GraphQL parameters. This can be useful if you need to search the database for an ID.
|
|
16
|
+
* `title` (string or function, required) — the page title to use when viewing this route. If you pass a function, you can use the first parameter to read the query var values — allowing you to look up a title if necessary.
|
|
17
|
+
* `is404` (function, optional) - An optional callback function, which will be passed the query parameters, and should return a truthy value if the page should be a 404.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
|
|
21
|
+
```php filename="/backend/routes.php"
|
|
22
|
+
<?php
|
|
23
|
+
|
|
24
|
+
// Adding a simple '/search' route, with an optional trailing slash.
|
|
25
|
+
ED()->addCustomRoute("search/?$", [
|
|
26
|
+
'template' => 'views/search.tsx',
|
|
27
|
+
'title' => function() {
|
|
28
|
+
return 'Search';
|
|
29
|
+
}
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
// Adding a custom route which uses a Regex pattern
|
|
33
|
+
// The pattern is made available to a paired GraphQL file via a 'listId' variable.
|
|
34
|
+
ED()->addCustomRoute("wishlist/([A-Za-z0-9\-\_]+)/?$", [
|
|
35
|
+
'template' => 'views/user-wishlist.tsx',
|
|
36
|
+
'queryVars' => [
|
|
37
|
+
'listId' => '$1'
|
|
38
|
+
],
|
|
39
|
+
'title' => function($params) {
|
|
40
|
+
$wishlist = Wishlists::getWishlist($params['listId']);
|
|
41
|
+
return $wishlist->title;
|
|
42
|
+
},
|
|
43
|
+
'is404' => function($params) {
|
|
44
|
+
return Wishlists::wishlistExists($params['listId']) == false;
|
|
45
|
+
}
|
|
46
|
+
]);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Inside `views/user-wishlist.graphql`, the `$listId` variable will be populated with the regular expression match from the custom route.
|
|
50
|
+
|
|
51
|
+
```graphql filename="/views/user-wishlist.graphql"
|
|
52
|
+
query UserWishlist($listId: String!) {
|
|
53
|
+
wishlist(id: $listId) {
|
|
54
|
+
title
|
|
55
|
+
items {
|
|
56
|
+
title
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Generating WordPress URLs For Custom Routes [#generating-wordpress-urls-for-custom-routes]
|
|
63
|
+
|
|
64
|
+
A custom route only tells `eddev` how to handle an incoming URL. If WordPress also needs to generate that URL from `get_permalink()`, GraphQL `uri` fields, menus, or block data, remap the WordPress permalink separately.
|
|
65
|
+
|
|
66
|
+
For posts and custom post types, use the [`post_type_link`](https://developer.wordpress.org/reference/hooks/post_type_link/) filter. SFF uses this for Film posts: each Film still lives in WordPress as a `film` post, but generated links are rewritten based on the festival/program that owns the Film. The matching custom routes then resolve those paths back to `views/single-film.tsx`.
|
|
67
|
+
|
|
68
|
+
```php filename="/backend/routes.php"
|
|
69
|
+
add_filter('post_type_link', function ($link, $post) {
|
|
70
|
+
if ($post->post_type === 'film') {
|
|
71
|
+
return Programs::getPrefixForProgram($post->post_parent) . $post->post_name . "/";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return $link;
|
|
75
|
+
}, 10, 2);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
For taxonomy or category URLs, use the matching WordPress term link filter, such as `term_link` or `category_link`, with the same idea: generate the public URL you want, then add a custom route that knows how to resolve that URL into view data.
|
|
79
|
+
|
|
80
|
+
## Extra Vars [#extra-vars]
|
|
81
|
+
|
|
82
|
+
You can use the `extraVars` option if you need to calculate some additional GraphQL parameters based on the URL.
|
|
83
|
+
|
|
84
|
+
Say you want to use `/films/{festival}/{film}/` as the pathname for Films, where film slugs are only unique to that festival. The best way to accomplish this would be to:
|
|
85
|
+
|
|
86
|
+
* Disable the default item pages, using [`"rewrite" => false`](./wordpress#post-type-routing) on the Films post type
|
|
87
|
+
* Use the `post_type_link` filter to ensure that Film URLs are generated correctly.
|
|
88
|
+
* Add a custom route for the pattern, which handles 404s correctly.
|
|
89
|
+
|
|
90
|
+
Your custom route might look something like this:
|
|
91
|
+
|
|
92
|
+
```php
|
|
93
|
+
ED()->addCustomRoute('films/([a-z0-9-]+)/([a-z0-9-]+)/?$', [
|
|
94
|
+
// Use the 'single-film' template by convention, even though it could be anything
|
|
95
|
+
'template' => 'views/single-film.tsx',
|
|
96
|
+
// Map the URL params -> query vars
|
|
97
|
+
'queryVars' => [
|
|
98
|
+
'festivalSlug' => "$1",
|
|
99
|
+
'filmSlug' => "$2",
|
|
100
|
+
],
|
|
101
|
+
'extraVars' => function ($vars) {
|
|
102
|
+
global $wp_query;
|
|
103
|
+
$post = get_post([/*...*/]);
|
|
104
|
+
if (!$post) return [];
|
|
105
|
+
|
|
106
|
+
// Optionally set the global queried_object_id, so that WordPress will handle the page title/SEO metadata as normal
|
|
107
|
+
$wp_query->queried_object_id = $post->ID;
|
|
108
|
+
$wp_query->queried_object = $post;
|
|
109
|
+
|
|
110
|
+
// Return the `postId` param
|
|
111
|
+
return [
|
|
112
|
+
'postId' => $post->ID
|
|
113
|
+
];
|
|
114
|
+
},
|
|
115
|
+
'is404' => function ($vars) {
|
|
116
|
+
return empty($vars['postId']);
|
|
117
|
+
}
|
|
118
|
+
]);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
<Callout>
|
|
122
|
+
TODO: OpenGraph Metadata
|
|
123
|
+
</Callout>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# How it Works (/docs/routing/full-details)
|
|
2
|
+
|
|
3
|
+
**Trace how eddev loads route data for SPA and serverless navigation.**
|
|
4
|
+
|
|
5
|
+
## Route Data Handling [#route-data-handling]
|
|
6
|
+
|
|
7
|
+
When navigating to a new page, the frontend router makes a request to WordPress to fetch the JSON data needed by the current route. The request is handled by the `RouteLoader` (accessible via `useRouter().loader`).
|
|
8
|
+
|
|
9
|
+
The JSON data is available via `/{path}?_props=1` in SPA mode, and `/_data/route/{path}` in serverless mode.
|
|
10
|
+
|
|
11
|
+
| Mode | URL | JSON Page |
|
|
12
|
+
| ---------- | ------------------ | ------------------------------- |
|
|
13
|
+
| Serverless | `/work/my-project` | `/_data/route/work/my-project/` |
|
|
14
|
+
| SPA | `/work/my-project` | `/work/my-project/?_props=1` |
|
|
15
|
+
|
|
16
|
+
The *serverless* mode `/_data/route/{path}` version proxies the `/{path}/?_props=1` version from the WordPress origin, but allows for additional processing, caching and (potential future) static site generation.
|
|
17
|
+
|
|
18
|
+
On the WordPress side, `eddev-php` takes over, recognising that `?_props=1` is present in the URL. WordPress resolves the route that should be displayed using the [WordPress Template Hierarchy](https://developer.wordpress.org/themes/basics/template-hierarchy/) and any [Custom Routes](./custom), and identifies the `view`, or page template, that should be rendered.
|
|
19
|
+
|
|
20
|
+
Once the page template has been found, `eddev-php` then looks for a paired `.graphql` file with the same name. So if `views/single-project.tsx` is resolved as the template, then it'll search for a `views/single-project.graphql` file.
|
|
21
|
+
|
|
22
|
+
That GraphQL file is then executed, and the result is captured.
|
|
23
|
+
|
|
24
|
+
During this process, one of the following will happen:
|
|
25
|
+
|
|
26
|
+
* The route resolves to a 404 page, so the 404 view or `_error` view receives error details.
|
|
27
|
+
* The GraphQL query throws an error, so the view is set to `_error` and `props` is set to the error details.
|
|
28
|
+
* The GraphQL succeeds, and the process continues.
|
|
29
|
+
* No GraphQL file was present, so props is set to an empty object.
|
|
30
|
+
|
|
31
|
+
After this, `wp_head()`, `wp_footer()` are called, allowing WordPress (and plugins) to spit out meta tags and any other tags that should be present on the page. This HTML output is parsed, and included in the final payload.
|
|
32
|
+
|
|
33
|
+
The request is then resolved with the final JSON payload.
|
|
34
|
+
|
|
35
|
+
<Callout>
|
|
36
|
+
Most of this process also occurs when the page is loaded directly, but the JSON payload is instead printed in a `<script>window._PAGE_DATA = {...}</script>`, with app data also being included.
|
|
37
|
+
</Callout>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# WordPress Routing (/docs/routing/wordpress)
|
|
2
|
+
|
|
3
|
+
**Let WordPress resolve URLs while React renders the matched view.**
|
|
4
|
+
|
|
5
|
+
Rather than using file-based routing, we allow WordPress to serve as the 'authority' on what a given URL should display.
|
|
6
|
+
|
|
7
|
+
This results in a tighter coupling between the CMS and the frontend — with a single source of truth for getting/resolving URLs, with very minimal configuration — and allows us to serve the site as both an SPA on the WordPress server (no Node.js needed), as well as a standalone SSR frontend (deployed to Vercel).
|
|
8
|
+
|
|
9
|
+
The stack essentially allows the [Wordpress Template Hierarchy](https://developer.wordpress.org/themes/basics/template-hierarchy/) to unfold as normal, but it allows `.tsx` files to be used, instead of `.php` files. So, when WordPress attempts to look for `single-event.php`, it'll instead look for `views/single-event.tsx`.
|
|
10
|
+
|
|
11
|
+
## Custom Routes [#custom-routes]
|
|
12
|
+
|
|
13
|
+
Custom routes can be registered using the `ED()->addCustomRoute()` method in PHP. [Read More](./custom)
|
|
14
|
+
|
|
15
|
+
## Post Type Routing [#post-type-routing]
|
|
16
|
+
|
|
17
|
+
The code below would provide:
|
|
18
|
+
|
|
19
|
+
* a listing page at `/work`, which would use `views/archive-project.tsx` as the template.
|
|
20
|
+
* a page for each project at `/work/{slug}`, which would use `views/single-project.tsx` as the template.
|
|
21
|
+
|
|
22
|
+
```php
|
|
23
|
+
ED()->registerPostType("projects", [
|
|
24
|
+
/* ... */
|
|
25
|
+
"has_archive" => true,
|
|
26
|
+
"rewrite" => [
|
|
27
|
+
"slug" => "work",
|
|
28
|
+
"with_front" => false
|
|
29
|
+
]
|
|
30
|
+
])
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
<Callout type="info">
|
|
34
|
+
To disable the default listing page, just pass `"has_archive" => false`<br />
|
|
35
|
+
To disable the default post page, just pass `"rewrite" => false`
|
|
36
|
+
</Callout>
|
|
37
|
+
|
|
38
|
+
## Posts URLs without pages [#posts-urls-without-pages]
|
|
39
|
+
|
|
40
|
+
Sometimes you might want to redirect a post type to another page. You can use the [post\_type\_link](https://developer.wordpress.org/reference/hooks/post_type_link/) filter to do this.
|
|
41
|
+
|
|
42
|
+
```php
|
|
43
|
+
ED()->registerPostType("projects", [
|
|
44
|
+
/* ... */
|
|
45
|
+
// Disable the default route, to remove it from the router
|
|
46
|
+
"rewrite" => false
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
// Return a custom URL for this post
|
|
50
|
+
add_filter('post_type_link', function ($url, $post) {
|
|
51
|
+
if ($post->post_type === 'job') {
|
|
52
|
+
return "/jobs?job=".$post->post_name;
|
|
53
|
+
}
|
|
54
|
+
return $url;
|
|
55
|
+
}, 10, 2);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
<Callout type="warning">
|
|
59
|
+
Note that this doesn't affect how routes are resolved — just how links are generated.
|
|
60
|
+
</Callout>
|
|
61
|
+
|
|
62
|
+
<Callout type="info">
|
|
63
|
+
The same functionality can be applied to Taxonomy terms too, via [term\_link](https://developer.wordpress.org/reference/hooks/term_link/).
|
|
64
|
+
</Callout>
|
|
65
|
+
|
|
66
|
+
## Rule Flushing Hack [#rule-flushing-hack]
|
|
67
|
+
|
|
68
|
+
WordPress caches the routing rules for performance reasons, which can sometimes result in routing updates not applying.
|
|
69
|
+
|
|
70
|
+
The stack tracks custom route and post-type declarations, and automatically calls [flush\_rewrite\_rules](https://developer.wordpress.org/reference/functions/flush_rewrite_rules/) when changes are detected. If you're doing something more advanced with WordPress routing, you may need to flush the router manually. Just navigate to *Settings -> Permalinks* and simply hit *Save Changes*.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Routing Overview (/docs/routing)
|
|
2
|
+
|
|
3
|
+
**Understand the WordPress and frontend sides of eddev routing.**
|
|
4
|
+
|
|
5
|
+
There are two sides to routing in our stack:
|
|
6
|
+
|
|
7
|
+
1. **WordPress/PHP** — We allow WordPress to act as the authority on routing, and the stack provides extra functions for extending this.
|
|
8
|
+
2. **Frontend API** — The `eddev` library provides JavaScript APIs for handling client-side navigation, and for preloading data and components.
|
|
9
|
+
|
|
10
|
+
<Cards>
|
|
11
|
+
<Card title="WordPress Routing" href="/routing/wordpress">
|
|
12
|
+
Let WordPress resolve URLs while React renders the matched template.
|
|
13
|
+
</Card>
|
|
14
|
+
|
|
15
|
+
<Card title="Frontend APIs" href="/routing/api">
|
|
16
|
+
Use eddev link and router APIs for client-side navigation.
|
|
17
|
+
</Card>
|
|
18
|
+
</Cards>
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# RPC Functions (/docs/serverless/functions)
|
|
2
|
+
|
|
3
|
+
**Define TypeScript RPC routes for serverless eddev projects.**
|
|
4
|
+
|
|
5
|
+
Serverless functions are TypeScript HTTP routes that allow you to build REST endpoints.
|
|
6
|
+
|
|
7
|
+
To get started:
|
|
8
|
+
|
|
9
|
+
* You'll need to create a `server/routes/` folder
|
|
10
|
+
* You'll also need to install [`zod`](https://zod.dev/) via `yarn add zod`. Zod is used for validation of RPC inputs.
|
|
11
|
+
|
|
12
|
+
The `server/routes` folder contains a set of `.ts` files, and uses file-based routing. You can also create an optional `server/_context.ts` file, which allows you to parse HTTP headers for things like auth, and pass additional information to your API handlers.
|
|
13
|
+
|
|
14
|
+
Each file must have a `default` export, and export either a [query](#queries), [mutation](#mutations), or a [router](#routers).
|
|
15
|
+
|
|
16
|
+
You can also add additional helper code anywhere in the `server` folder, and it'll be ignored — as long as it's not in the `server/routes` folder. Of course, you can also import code from elsewhere in your project.
|
|
17
|
+
|
|
18
|
+
An example folder structure:
|
|
19
|
+
|
|
20
|
+
<Files>
|
|
21
|
+
<Folder name="server/">
|
|
22
|
+
<File name="_context.ts" />
|
|
23
|
+
|
|
24
|
+
<Folder name="routes/">
|
|
25
|
+
<File name="api.pokemon.ts" />
|
|
26
|
+
|
|
27
|
+
<File name="api.echo.ts" />
|
|
28
|
+
|
|
29
|
+
<Folder name="api/">
|
|
30
|
+
<Folder name="users/">
|
|
31
|
+
<File name="auth.ts" />
|
|
32
|
+
|
|
33
|
+
<File name="prefs.ts" />
|
|
34
|
+
</Folder>
|
|
35
|
+
</Folder>
|
|
36
|
+
</Folder>
|
|
37
|
+
|
|
38
|
+
<Folder name="lib/">
|
|
39
|
+
<File name="whatever.ts" />
|
|
40
|
+
</Folder>
|
|
41
|
+
</Folder>
|
|
42
|
+
</Files>
|
|
43
|
+
|
|
44
|
+
<Callout type="info">
|
|
45
|
+
Under the hood, the stack uses [tRPC](https://trpc.io) to define server-side procedures and routers, with custom eddev integration. You should familiarize yourself with the core concepts of procedures, routing and context in tRPC, but examples are shown on this page. You don't need to install tRPC, as it's included in the stack.
|
|
46
|
+
</Callout>
|
|
47
|
+
|
|
48
|
+
<Callout type="warning">
|
|
49
|
+
RPC functions require a Vercel deployment. The [`useRPCQuery()`](#userpcquery) and [`useRPCMutation()`](#userpcmutation) hooks will work correctly in WordPress admin and the SPA frontend, as long as the `serverless.endpoints` object in `ed.config.json` is configured correctly — it must contain a mapping of the WordPress origin to the serverless endpoint, for example `"cms.ed.studio": "ed.studio"`.
|
|
50
|
+
</Callout>
|
|
51
|
+
|
|
52
|
+
## Defining Routes [#defining-routes]
|
|
53
|
+
|
|
54
|
+
Routes are defined using file-based routing. You can use a combination of folders and filenames with `.` so `api/hello.ts` and `api.hello.ts` do the same thing! It's recommended that you use `api.hello.ts` so that your filenames are easier to search for and recognise in your code editor.
|
|
55
|
+
|
|
56
|
+
Below are some examples of how the file-based routing is resolved:
|
|
57
|
+
|
|
58
|
+
<table>
|
|
59
|
+
<thead>
|
|
60
|
+
<tr>
|
|
61
|
+
<th>
|
|
62
|
+
File
|
|
63
|
+
</th>
|
|
64
|
+
|
|
65
|
+
<th>
|
|
66
|
+
URL Path
|
|
67
|
+
</th>
|
|
68
|
+
|
|
69
|
+
<th>
|
|
70
|
+
Notes
|
|
71
|
+
</th>
|
|
72
|
+
</tr>
|
|
73
|
+
</thead>
|
|
74
|
+
|
|
75
|
+
<tbody>
|
|
76
|
+
<tr>
|
|
77
|
+
<td>
|
|
78
|
+
`/routes/demo1.ts`
|
|
79
|
+
</td>
|
|
80
|
+
|
|
81
|
+
<td>
|
|
82
|
+
`/demo1`
|
|
83
|
+
</td>
|
|
84
|
+
|
|
85
|
+
<td>
|
|
86
|
+
Straight-up file -> URL
|
|
87
|
+
</td>
|
|
88
|
+
</tr>
|
|
89
|
+
|
|
90
|
+
<tr>
|
|
91
|
+
<td>
|
|
92
|
+
`/routes/api/tickets.ts`
|
|
93
|
+
</td>
|
|
94
|
+
|
|
95
|
+
<td>
|
|
96
|
+
`/api/tickets/`
|
|
97
|
+
</td>
|
|
98
|
+
|
|
99
|
+
<td>
|
|
100
|
+
Using folders
|
|
101
|
+
</td>
|
|
102
|
+
</tr>
|
|
103
|
+
|
|
104
|
+
<tr>
|
|
105
|
+
<td>
|
|
106
|
+
`/routes/api.tickets.ts`
|
|
107
|
+
</td>
|
|
108
|
+
|
|
109
|
+
<td>
|
|
110
|
+
`/api/tickets/`
|
|
111
|
+
</td>
|
|
112
|
+
|
|
113
|
+
<td>
|
|
114
|
+
Using filenames only with `.` instead of folders
|
|
115
|
+
</td>
|
|
116
|
+
</tr>
|
|
117
|
+
|
|
118
|
+
<tr>
|
|
119
|
+
<td>
|
|
120
|
+
`/routes/api/hubspot.forms.ts`
|
|
121
|
+
</td>
|
|
122
|
+
|
|
123
|
+
<td>
|
|
124
|
+
`/api/hubspot/forms/`
|
|
125
|
+
</td>
|
|
126
|
+
|
|
127
|
+
<td>
|
|
128
|
+
Using a combination of folders + `.` syntax
|
|
129
|
+
</td>
|
|
130
|
+
</tr>
|
|
131
|
+
</tbody>
|
|
132
|
+
</table>
|
|
133
|
+
|
|
134
|
+
## Procedures [#procedures]
|
|
135
|
+
|
|
136
|
+
"Procedures" are either "queries" or "mutations". Queries are GET requests, and mutations are POST requests. Both have a nearly identical syntax, but are called in different ways.
|
|
137
|
+
|
|
138
|
+
Multiple procedures can be contained within a single file by exporting a ["router"](#routers).
|
|
139
|
+
|
|
140
|
+
### Queries [#queries]
|
|
141
|
+
|
|
142
|
+
Query procedures use the `GET` HTTP method.
|
|
143
|
+
|
|
144
|
+
```typescript filename="/server/routes/api.getPokemon.ts"
|
|
145
|
+
import { rpc } from "eddev/server"
|
|
146
|
+
import { z } from "zod"
|
|
147
|
+
|
|
148
|
+
export default rpc.procedure
|
|
149
|
+
.input(
|
|
150
|
+
z.object({
|
|
151
|
+
name: z.string().min(2),
|
|
152
|
+
}),
|
|
153
|
+
)
|
|
154
|
+
.query(async ({ ctx, input }) => {
|
|
155
|
+
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${input.name}`)
|
|
156
|
+
const data = await response.json()
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
pokemonInfo: data,
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### `useRPCQuery()` [#userpcquery]
|
|
165
|
+
|
|
166
|
+
In React, you can then call `useRPCQuery` with the name `"api.getPokemon"` and type-safe arguments, and observe the result.
|
|
167
|
+
|
|
168
|
+
```tsx filename="PokemonInfo.tsx"
|
|
169
|
+
import { useRPCQuery } from "eddev/hooks"
|
|
170
|
+
|
|
171
|
+
type Props = {
|
|
172
|
+
name: string
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function Pokemon(props: Props) {
|
|
176
|
+
const pokemon = useRPCQuery("api.getPokemon", { name: props.name })
|
|
177
|
+
|
|
178
|
+
if (pokemon.isSuccess) {
|
|
179
|
+
return (
|
|
180
|
+
<div>
|
|
181
|
+
Pokemon data: <pre>{JSON.stringify(pokemon.data.pokemonInfo, null, 2)}</pre>
|
|
182
|
+
</div>
|
|
183
|
+
)
|
|
184
|
+
} else if (pokemon.isLoading) {
|
|
185
|
+
return <div>Loading...</div>
|
|
186
|
+
} else if (pokemon.isError) {
|
|
187
|
+
return <div>Error: {pokemon.error.message}</div>
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
<Callout>
|
|
193
|
+
The return result of `useRPCQuery` is the same as our generated GraphQL hooks. It also accepts a third argument, the query options, where you can pass options like `refetchInterval` to have the hook auto-refresh. See [TanStack docs for useQuery](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery)
|
|
194
|
+
</Callout>
|
|
195
|
+
|
|
196
|
+
#### `rpcClient.query()` [#rpcclientquery]
|
|
197
|
+
|
|
198
|
+
You can also call queries procedurally using `rpcClient.query()`, like so:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const bulbasaur = await rpcClient.query("api.getPokemon", { name: "bulbasaur" })
|
|
202
|
+
console.log("Bulbasaur is", bulbasaur)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Mutations [#mutations]
|
|
206
|
+
|
|
207
|
+
Use `mutation` procedures for actions that should be triggered when a user completes an action, like submitting a form.
|
|
208
|
+
|
|
209
|
+
Mutations will be called from React with `const mutation = useRPCMutation("api.submit")` and `mutation.mutate(args)`
|
|
210
|
+
|
|
211
|
+
```typescript filename="/server/routes/api.subscribe.ts"
|
|
212
|
+
import { rpc } from "eddev/server"
|
|
213
|
+
import { z } from "zod"
|
|
214
|
+
|
|
215
|
+
export default rpc.procedure
|
|
216
|
+
.input(
|
|
217
|
+
z.object({
|
|
218
|
+
email: z.string(),
|
|
219
|
+
listId: z.string()
|
|
220
|
+
}),
|
|
221
|
+
)
|
|
222
|
+
.mutation(async ({ input }) => {
|
|
223
|
+
const params = new URLSearchParams()
|
|
224
|
+
params.append("EMAIL", input.email)
|
|
225
|
+
params.append("id", input.listId)
|
|
226
|
+
const response = await fetch(`${env.CAMPAIGN_MONITOR_URL}&${params}`, {
|
|
227
|
+
method: "GET",
|
|
228
|
+
})
|
|
229
|
+
if (response.ok) {
|
|
230
|
+
return { success: true }
|
|
231
|
+
} else {
|
|
232
|
+
return { success: false }
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### `useRPCMutation()` [#userpcmutation]
|
|
238
|
+
|
|
239
|
+
In React, you can then call `useRPCMutation` with the name `"api.subscribe"` and type-safe arguments, and observe the result.
|
|
240
|
+
|
|
241
|
+
```tsx filename="SignupForm.tsx"
|
|
242
|
+
import { useRPCMutation } from "eddev/hooks"
|
|
243
|
+
import { useState } from "react"
|
|
244
|
+
|
|
245
|
+
type Props = {
|
|
246
|
+
listId: string
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function SignupForm(props: Props) {
|
|
250
|
+
const [email, setEmail] = useState("")
|
|
251
|
+
const subscribe = useRPCMutation("api.subscribe")
|
|
252
|
+
|
|
253
|
+
if (subscribe.isSuccess) {
|
|
254
|
+
console.log("Result is", subscribe.data)
|
|
255
|
+
return <div>Thank you for signing up!</div>
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return <form onSubmit={e => {
|
|
259
|
+
e.preventDefault()
|
|
260
|
+
subscribe.mutate({ email: email, listId: props.listId })
|
|
261
|
+
}}>
|
|
262
|
+
{/* Error message */}
|
|
263
|
+
{subscribe.isError && <p>{subscribe.error.message}</p>}
|
|
264
|
+
{/* Disable input when submitting */}
|
|
265
|
+
<input type="email" disabled={subscribe.isPending} value={email} onChange={e => setEmail(e.currentTarget.value)} />
|
|
266
|
+
<button type="submit">Submit</button>
|
|
267
|
+
</form>
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
<Callout>
|
|
272
|
+
The return result of `useRPCMutation` is the same as our generated GraphQL hooks. It also accepts a second argument, the mutation options, where you can pass options like `onSuccess` to trigger functions to occur after successful submission. See [TanStack docs for useMutation](https://tanstack.com/query/latest/docs/framework/react/reference/useMutation)
|
|
273
|
+
</Callout>
|
|
274
|
+
|
|
275
|
+
#### `rpcClient.mutation()` [#rpcclientmutation]
|
|
276
|
+
|
|
277
|
+
You can also call mutations procedurally using `rpcClient.mutation()`, like so:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const result = await rpcClient.mutation("api.subscribe", { email: "hello@internet.com", listId: "12345" })
|
|
281
|
+
console.log("Sign up result", result)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Routers [#routers]
|
|
285
|
+
|
|
286
|
+
Multiple procedures can be exported from a single file by exporting a "router". A router mounts multiple endpoints at once. Use `rpc.router()` when you have a group of highly-related API functions.
|
|
287
|
+
|
|
288
|
+
```typescript filename="/server/routes/api.math.ts"
|
|
289
|
+
import { rpc } from "eddev/server"
|
|
290
|
+
import z from "zod"
|
|
291
|
+
|
|
292
|
+
export default rpc.router({
|
|
293
|
+
sum: rpc.procedure
|
|
294
|
+
.input(
|
|
295
|
+
z.object({
|
|
296
|
+
a: z.number(),
|
|
297
|
+
b: z.number(),
|
|
298
|
+
}),
|
|
299
|
+
)
|
|
300
|
+
.query(({ input }) => {
|
|
301
|
+
return input.a + input.b
|
|
302
|
+
}),
|
|
303
|
+
random: rpc.procedure.query(() => {
|
|
304
|
+
return Math.random()
|
|
305
|
+
}),
|
|
306
|
+
pi: rpc.procedure.query(() => {
|
|
307
|
+
return Math.PI
|
|
308
|
+
}),
|
|
309
|
+
})
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Procedures defined in a router can be called using [`useRPCQuery()`](#userpcquery) and [`useRPCMutation()`](#userpcmutation), like any other procedure.
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
import { useRPCQuery } from "eddev/hooks"
|
|
316
|
+
|
|
317
|
+
export function Demo() {
|
|
318
|
+
const pi = useRPCQuery("api.math.pi")
|
|
319
|
+
const random = useRPCQuery("api.math.random")
|
|
320
|
+
|
|
321
|
+
const sum = useRPCQuery(
|
|
322
|
+
"api.math.sum",
|
|
323
|
+
{ a: pi.data ?? 0, b: random.data ?? 0 },
|
|
324
|
+
{
|
|
325
|
+
enabled: pi.isSuccess && random.isSuccess,
|
|
326
|
+
},
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<div>
|
|
331
|
+
<div>Pi: {pi.data}</div>
|
|
332
|
+
<div>Random: {random.data}</div>
|
|
333
|
+
<div>Sum: {sum.data}</div>
|
|
334
|
+
</div>
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Context Middleware [#context-middleware]
|
|
341
|
+
|
|
342
|
+
The `/server/_context.ts` file is optional, but can be defined to extract common metadata from a request. You can use it to parse Auth headers or cookies, and the returned values will be available in the `ctx` argument of your procedures.
|
|
343
|
+
|
|
344
|
+
```typescript filename="/server/_context.ts"
|
|
345
|
+
import { defineServerContext } from "eddev/server"
|
|
346
|
+
|
|
347
|
+
export default defineServerContext((ctx) => {
|
|
348
|
+
const headers = ctx.req.headers
|
|
349
|
+
return {
|
|
350
|
+
...ctx,
|
|
351
|
+
user: {
|
|
352
|
+
ip: headers.get("x-real-ip") ?? headers.get("x-real-ip") ?? "",
|
|
353
|
+
},
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
You'll need to run `eddev dev` to ensure that the TypeScript types are generated at least once. In any procedure, you can now access `ctx.user.ip`.
|
|
359
|
+
|
|
360
|
+
```typescript filename="/server/api.ip.ts"
|
|
361
|
+
import { rpc } from "eddev/server"
|
|
362
|
+
|
|
363
|
+
export default rpc.procedure.query(async ({ ctx }) => {
|
|
364
|
+
return {
|
|
365
|
+
ip: "Your IP is " + ctx.user.ip,
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Connecting to WordPress [#connecting-to-wordpress]
|
|
371
|
+
|
|
372
|
+
[Generated GraphQL hooks](/docs/graphql/query-hooks) can be called using `.fetch()` and `.mutate()`, with arguments.
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { rpc } from "eddev/server"
|
|
376
|
+
|
|
377
|
+
export default rpc.procedure
|
|
378
|
+
.query(async ({ ctx, input }) => {
|
|
379
|
+
const data = useLatestNews.fetch({ category: "design" })
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
latest: data.news.nodes[0]
|
|
383
|
+
}
|
|
384
|
+
})
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Non-RPC Responses [#non-rpc-responses]
|
|
388
|
+
|
|
389
|
+
RPC queries typically return JSON payloads with a `Content-Type: application/json` header, however procedures can also be used as generic HTTP endpoints, by simply returning a native JavaScript `Response` object. Note that this will only work on Vercel-deployed domains.
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { rpc } from "eddev/server"
|
|
393
|
+
|
|
394
|
+
export default rpc.procedure.query(async () => {
|
|
395
|
+
return new Response("Hello world", {
|
|
396
|
+
status: 200,
|
|
397
|
+
headers: {
|
|
398
|
+
"Content-Type": "text/plain",
|
|
399
|
+
"Cache-Control": "max-age=3600, s-maxage=3600, public",
|
|
400
|
+
},
|
|
401
|
+
})
|
|
402
|
+
})
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
<Callout>
|
|
406
|
+
Note that when returning a `Response` object, the procedure may no longer be callable with `useRPCQuery()` and `useRPCMutation()`, since it may no longer conform to tRPC's return protocol.
|
|
407
|
+
</Callout>
|
|
408
|
+
|
|
409
|
+
There are also some helper methods for force-rendering pages from other routes.
|
|
410
|
+
|
|
411
|
+
The `renderPage()` function can be used to display a page with a specific route. This should be avoided in the majority of cases.
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
import { rpc, renderPage } from "eddev/server"
|
|
415
|
+
|
|
416
|
+
export default rpc.procedure.query(async () => {
|
|
417
|
+
return renderPage({
|
|
418
|
+
pathname: "/",
|
|
419
|
+
})
|
|
420
|
+
})
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Similarly, the `renderErrorPage()` function can be used to display the site's standard error page.
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import { renderErrorPage, rpc } from "eddev/server"
|
|
427
|
+
|
|
428
|
+
export default rpc.procedure.query(async () => {
|
|
429
|
+
const result = await renderErrorPage({
|
|
430
|
+
title: "Bad Request",
|
|
431
|
+
statusCode: 400,
|
|
432
|
+
userMessage: "Something went wrong",
|
|
433
|
+
})
|
|
434
|
+
return result
|
|
435
|
+
})
|
|
436
|
+
```
|