(null, (set) => {
6 | if (!browser) return;
7 | const m = window.matchMedia(mediaQueryString);
8 | set(m.matches);
9 | const el = (e: MediaQueryListEvent) => set(e.matches);
10 | m.addEventListener('change', el);
11 | return () => {
12 | m.removeEventListener('change', el);
13 | };
14 | });
15 | return matches;
16 | };
17 |
--------------------------------------------------------------------------------
/docs/src/routes/+layout.server.ts:
--------------------------------------------------------------------------------
1 | import { version } from '../../../package/package.json';
2 | import type { LayoutServerLoad } from './$types';
3 |
4 | export const prerender = true;
5 |
6 | export const load: LayoutServerLoad = () => ({ version });
7 |
--------------------------------------------------------------------------------
/docs/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 | {title}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | {#if !dev}
47 |
48 | {/if}
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | {#if currentPageIndex > 0} {/if}
57 |
58 |
59 |
60 |
61 |
100 |
--------------------------------------------------------------------------------
/docs/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
--------------------------------------------------------------------------------
/docs/src/routes/acknowledgements/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 | Acknowledgements
6 |
7 |
8 | Huge thanks to Alex / KATT
9 | for being the first sponsor of this project 🎉, for his outstanding work at
10 | tRPC.io and for his encouragement.
11 |
12 |
13 |
14 | Thank you for your support,
15 |
16 | Ionut-Cristian Florescu
17 |
18 |
--------------------------------------------------------------------------------
/docs/src/routes/authentication/+page.server.ts:
--------------------------------------------------------------------------------
1 | import loadCodeBlocks from '$lib/loadCodeBlocks';
2 | import type { PageServerLoad } from './$types';
3 |
4 | export const prerender = true;
5 |
6 | export const load: PageServerLoad = async () => ({
7 | codeBlocks: await loadCodeBlocks({
8 | 'bookstall/src/routes/login/+page.server.ts': 'example',
9 | 'bookstall/src/lib/trpc/context.ts': 'example',
10 | 'bookstall/src/lib/trpc/middleware/auth.ts': 'example',
11 | 'bookstall/src/lib/trpc/routes/authors.ts': 'example'
12 | })
13 | });
14 |
--------------------------------------------------------------------------------
/docs/src/routes/authentication/+page.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 | Using authentication
9 |
10 |
11 | The tRPC-SvelteKit
package works with both cookie-based and JWT-based authentication.
12 |
13 |
14 |
15 | Below is an example of how you could secure your tRPC routes with cookie-based authentication.
16 |
17 |
18 |
19 | Let's assume you have a username/password-based login page that POSTs to the following action:
24 |
25 |
26 |
27 |
28 | Your tRPC context builder could look like this:
29 |
30 |
31 |
32 | You could define an authentication middleware like this:
33 |
34 |
35 |
36 | And you could use the auth middleware in your tRPC procedured as needed:
37 |
38 |
39 |
--------------------------------------------------------------------------------
/docs/src/routes/experimental-websocket-support/+page.svelte:
--------------------------------------------------------------------------------
1 | Experimental WebSocket support
2 |
3 |
4 | SvelteKit doesn't (yet) offer WebSockets support , but if you're using @sveltejs/adapter-node
, tRPC-SvelteKit
can spin up
7 | an experimental WS server to process tRPC procedure calls.
8 |
9 |
10 |
11 | Please refer to the dedicated section in the README for more information.
16 |
17 |
--------------------------------------------------------------------------------
/docs/src/routes/getting-started/+page.server.ts:
--------------------------------------------------------------------------------
1 | import loadCodeBlocks from '$lib/loadCodeBlocks';
2 | import type { PageServerLoad } from './$types';
3 |
4 | export const prerender = true;
5 |
6 | export const load: PageServerLoad = async () => {
7 | return {
8 | codeBlocks: await loadCodeBlocks({
9 | 'simple/src/lib/trpc/context.ts': 'example',
10 | 'simple/src/lib/trpc/router.ts': 'example',
11 | 'simple/src/hooks.server.ts': 'example',
12 | 'simple/src/lib/trpc/client.ts': 'example',
13 | 'simple/src/routes/+page.svelte': 'example'
14 | })
15 | };
16 | };
17 |
--------------------------------------------------------------------------------
/docs/src/routes/getting-started/+page.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 | Getting started
10 |
11 |
12 | 1
13 | Install the trpc-sveltekit
package and its dependencies with:
14 |
15 |
16 |
22 |
23 |
24 | 2
25 | Create your
26 | tRPC router :
27 |
28 |
29 |
30 |
31 |
32 | 3
33 | Create a tRPC context :
34 |
35 |
36 |
37 |
38 |
39 | 4
40 | Add this handle to your SvelteKit app
41 | hooks :
42 |
43 |
44 |
45 |
46 |
47 | If you have your own logic to place in the server hook, have a look at the sequence helper function in the SvelteKit docs.
52 |
53 |
54 |
55 | 5
56 | Define a helper function to easily use the tRPC client in your pages:
57 |
58 |
59 |
60 |
61 |
62 | 6
63 | Call the tRPC procedures in your pages:
64 |
65 |
66 |
67 |
68 |
69 |
70 |
74 |
75 |
76 | The trpc-sveltekit
package exports two functions: createTRPCHandle
and
77 | createTRPCClient
.
78 |
79 | The former is used in your SvelteKit app hooks, the latter is used in your pages.
80 |
81 |
82 |
105 |
--------------------------------------------------------------------------------
/docs/src/routes/handling-errors/+page.server.ts:
--------------------------------------------------------------------------------
1 | import loadCodeBlocks from '$lib/loadCodeBlocks';
2 | import type { PageServerLoad } from './$types';
3 |
4 | export const prerender = true;
5 |
6 | export const load: PageServerLoad = async () => ({
7 | codeBlocks: await loadCodeBlocks({
8 | 'bookstall/src/routes/authors/+page.svelte': 'example',
9 | 'bookstall/src/hooks.server.ts': 'example'
10 | })
11 | });
12 |
--------------------------------------------------------------------------------
/docs/src/routes/handling-errors/+page.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 | Handling errors
9 |
10 |
11 | Whenever an error occurs in a procedure, tRPC responds to the client with an object that includes
12 | an "error" property. This property contains all the information that you need to handle the error
13 | in the client. Here's an example of how you can handle the error in your SvelteKit page (look at
14 | the try/catch
block in the handleSave
function):
15 |
16 |
17 |
18 |
19 |
20 | Please refer to the error handling page in the tRPC documentation for more information.
23 |
24 |
25 |
26 | You can also supply an onError
handler to the createTRPCHandle
method in
27 | your hooks.server.ts
, which could be useful, for instance, if you want to log all
28 | errors to a service like Sentry:
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/docs/src/routes/hire-the-author/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 | Hire the author
6 |
7 | Hi there!
8 |
9 |
12 |
13 |
14 | I'm Ionut-Cristian Florescu, a full-stack developer from Bucharest, Romania, EU, with more than 20
15 | years of experience in building commercial web applications and open-source projects.
16 |
17 | tRPC-SvelteKit is one of my dearest open-source projects.
18 |
19 |
20 |
21 | You can learn more about what I do by visiting my profiles on GitHub
26 |
33 |
40 | or LinkedIn ,
41 | but since you are on this page, you probably have a pretty good idea of how my skills could help
42 | you.
43 |
44 |
45 |
46 | So, if you want to hire my services, don't hesitate to drop me a line at the email address listed
47 | in my GitHub profile. I'm currently getting a constant flow of approaches from recruiters, some of
48 | them relevant, others not so relevant. Mentioning "tRPC-SvelteKit" in your text would help me
49 | prioritize your message.
50 |
51 |
52 |
53 | Thank you for your interest,
54 |
55 | Ionut-Cristian Florescu
56 |
57 |
58 |
78 |
--------------------------------------------------------------------------------
/docs/src/routes/page-data/+page.server.ts:
--------------------------------------------------------------------------------
1 | import loadCodeBlocks from '$lib/loadCodeBlocks';
2 | import type { PageServerLoad } from './$types';
3 |
4 | export const prerender = true;
5 |
6 | export const load: PageServerLoad = async () => ({
7 | codeBlocks: await loadCodeBlocks({
8 | 'simple/src/routes/page-data/+page.ts': 'example',
9 | 'simple/src/routes/page-data/+page.svelte': 'example'
10 | })
11 | });
12 |
--------------------------------------------------------------------------------
/docs/src/routes/page-data/+page.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 | Page data
10 |
11 |
12 | Assuming you created a trpc()
helper function as described in the
13 | getting started
14 | section, you can call tRPC procedures in your SvelteKit
15 | page load functions :
17 |
18 |
19 |
20 |
21 | You can then use the loaded data in your pages like so:
22 |
23 |
24 |
25 |
26 | A load function defined in a +page.ts
file will run
27 | both on the server and in the browser . You should therefore consider using a
28 | +page.server.js
instead.
29 |
30 |
31 |
32 | Have a look at this SvelteKit documentation section to learn the difference between shared vs. server data loading.
37 |
38 |
--------------------------------------------------------------------------------
/docs/src/routes/page-server-data/+page.server.ts:
--------------------------------------------------------------------------------
1 | import loadCodeBlocks from '$lib/loadCodeBlocks';
2 | import type { PageServerLoad } from './$types';
3 |
4 | export const prerender = true;
5 |
6 | export const load: PageServerLoad = async () => ({
7 | codeBlocks: await loadCodeBlocks({
8 | 'simple/src/routes/page-server-data/+page.server.ts': 'example',
9 | 'simple/src/routes/page-server-data/+page.svelte': 'example'
10 | })
11 | });
12 |
--------------------------------------------------------------------------------
/docs/src/routes/page-server-data/+page.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 | Page server data
9 |
10 |
11 | If you want to call tRPC procedures in your SvelteKit page server load functions , you should bypass HTTP and invoke the methods directly on the
16 | tRPC caller :
17 |
18 |
19 |
20 |
21 | You can then use the loaded data in your pages like so:
22 |
23 |
24 |
25 |
26 | Have a look at this SvelteKit documentation section to learn the difference between shared vs. server data loading.
31 |
32 |
--------------------------------------------------------------------------------
/docs/src/routes/recipes-and-caveats/+page.server.ts:
--------------------------------------------------------------------------------
1 | import loadCodeBlocks from '$lib/loadCodeBlocks';
2 | import type { PageServerLoad } from './$types';
3 |
4 | export const prerender = true;
5 |
6 | export const load: PageServerLoad = async () => ({
7 | codeBlocks: await loadCodeBlocks({
8 | 'bookstall/src/lib/trpc/router.ts': 'example',
9 | 'bookstall/src/routes/authors/+page.svelte': 'example',
10 | 'simple/src/lib/trpc/context.ts': 'example'
11 | })
12 | });
13 |
--------------------------------------------------------------------------------
/docs/src/routes/recipes-and-caveats/+page.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 | Recipes and caveats
9 |
10 | Custom HTTP headers
11 |
12 |
13 | The createTRPCClient
method optionally accepts a headers
option, which
14 | can be useful, for example, to set an Authorization
header.
15 |
16 |
17 | Setting response headers and cookies
18 |
19 |
20 | To change the headers of the response, you want to use the event
provided in
21 | getContext
. You may also pass the event
into the context, so that it can
22 | be accessed in your procedures.
23 |
24 |
25 |
26 | You can then use event.setHeaders
and event.cookies
to edit the headers.
27 |
28 |
29 |
30 |
31 | Using custom data transformers
32 |
33 |
34 | The createTRPCClient
method optionally accepts a transformer
option.
35 | Please refer to
36 | this section in the tRPC.io documentation
39 | for more information, and keep in mind that you'll have to use the same transformer
when
40 | you're defining the router and when you're creating the client.
41 |
42 |
43 | If you're looking for a convenient transformer based on superjson
48 | with
49 | Decimal.js
50 | support, consider using the
51 | trpc-transformer
54 | package. Keep in mind that you'll have to install the superjson
and
55 | decimal.js
peer dependencies as well.
56 |
57 | Response caching
58 |
59 |
60 | Your server responses must satisfy some criteria
65 | in order for them to be cached (i.e. by Vercel's Edge Network). Please refer to
66 | this section of the tRPC.io documentation for more information.
69 |
70 |
71 | The createHTTPHandle
method conveniently allows you to specify a
72 | responseMeta
function.
73 |
74 |
75 | Inferring types
76 |
77 |
78 | It is often useful to wrap functionality of your tRPC client API within other functions. For this
79 | purpose, it's necessary to be able to infer input types and output types generated by your tRPC
80 | router. The@trpc/server
package exports two helper types to assist with inferring
81 | these types from your router, namely inferRouterInputs
and
82 | inferRouterOutputs
. Please refer to
83 | this section of the tRPC.io documentation
86 | for more information.
87 |
88 |
89 |
90 | To make your code faster to type and easier to read, you could further refine these helper types
91 | when defining your router:
92 |
93 |
94 |
95 |
96 | Then, you could use the helper types in your pages code like so:
97 |
98 |
99 |
100 | Reserved path prefix
101 |
102 |
103 | Invoking the createHTTPHandle
method reserves the /trpc
path prefix for
104 | the tRPC API. This means that you cannot use this prefix for any other purpose. If you need to use
105 | this prefix for other purposes, you can use the url
option to change the reserved prefix.
106 |
107 |
--------------------------------------------------------------------------------
/docs/src/routes/robots.txt/+server.ts:
--------------------------------------------------------------------------------
1 | import { GITHUB_PAGES_ROOT } from '$lib/constants';
2 | import type { RequestHandler } from './$types';
3 |
4 | export const prerender = true;
5 |
6 | export const GET: RequestHandler = () => {
7 | return new Response(
8 | [
9 | 'User-agent: *',
10 | 'Allow: /',
11 | `Host: ${GITHUB_PAGES_ROOT}/`,
12 | `Sitemap: ${GITHUB_PAGES_ROOT}/sitemap.xml`
13 | ].join('\n')
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/docs/src/routes/site.webmanifest/+server.ts:
--------------------------------------------------------------------------------
1 | import { GITHUB_PAGES_ROOT } from '$lib/constants';
2 | import type { RequestHandler } from './$types';
3 |
4 | export const prerender = true;
5 |
6 | const ICON_SIZES = [192, 512];
7 |
8 | export const GET: RequestHandler = () => {
9 | return new Response(
10 | JSON.stringify({
11 | name: 'tRPC-SvelteKit',
12 | short_name: 'tRPC-SvelteKit',
13 | start_url: './',
14 | scope: '.',
15 | icons: ICON_SIZES.map((size) => ({
16 | src: `${GITHUB_PAGES_ROOT}/android-chrome-${size}x${size}.png`,
17 | sizes: `${size}x${size}`,
18 | type: 'image/png'
19 | })),
20 | theme_color: '#141e26',
21 | background_color: '#141e26',
22 | display: 'standalone'
23 | })
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/docs/src/routes/sitemap.xml/+server.ts:
--------------------------------------------------------------------------------
1 | import { GITHUB_PAGES_ROOT, PAGES } from '$lib/constants';
2 | import type { RequestHandler } from './$types';
3 |
4 | export const prerender = true;
5 |
6 | export const GET: RequestHandler = () => {
7 | const date = new Date().toISOString();
8 | return new Response(
9 | [
10 | '',
11 | '',
12 | ...PAGES.map(
13 | ({ path }) =>
14 | `${GITHUB_PAGES_ROOT}${
15 | path === '/' ? '' : path
16 | } ${date} weekly 0.7 `
17 | ),
18 | ' '
19 | ].join('\n')
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/docs/src/routes/suggested-structure/+page.server.ts:
--------------------------------------------------------------------------------
1 | import loadCodeBlocks from '$lib/loadCodeBlocks';
2 | import type { PageServerLoad } from './$types';
3 |
4 | export const prerender = true;
5 |
6 | export const load: PageServerLoad = async () => ({
7 | codeBlocks: await loadCodeBlocks({
8 | 'suggested-structure.txt': 'misc',
9 | 'bookstall/src/lib/trpc/t.ts': 'example'
10 | })
11 | });
12 |
--------------------------------------------------------------------------------
/docs/src/routes/suggested-structure/+page.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 | Suggested structure
11 |
12 |
13 | Here's how you could structure the tRPC-related files in a non-trivial application in order to
14 | avoid cyclic dependencies:
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | The t.ts
file contains a simple helper that allows you to build routes and middleware:
23 |
24 |
25 |
26 |
27 |
28 | You'll have to import t.ts
in multiple other files such as router.ts
,
29 | middleware.ts
,
30 | routes/route-1.ts
, routes/route-2.ts
, etc.
31 |
32 |
33 |
38 |
--------------------------------------------------------------------------------
/docs/src/routes/using-with-svelte-query/+page.svelte:
--------------------------------------------------------------------------------
1 | Using with Svelte-Query
2 |
3 |
4 | If you want to use tRPC-SvelteKit
with
5 | Svelte Query ,
6 | check out the
7 | tRPC Svelte Query Adapter
12 | by @ vishalbalaji .
13 |
14 |
--------------------------------------------------------------------------------
/docs/src/service-worker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import { build, files, version } from '$service-worker';
3 |
4 | // Create a unique cache name for this deployment
5 | const CACHE = `cache-${version}`;
6 |
7 | const ASSETS = [
8 | ...build, // the app itself
9 | ...files // everything in `static`
10 | ];
11 |
12 | self.addEventListener('install', (event) => {
13 | // Create a new cache and add all files to it
14 | async function addFilesToCache() {
15 | const cache = await caches.open(CACHE);
16 | await cache.addAll(ASSETS);
17 | }
18 |
19 | // @ts-ignore
20 | event.waitUntil(addFilesToCache());
21 | });
22 |
23 | self.addEventListener('activate', (event) => {
24 | // Remove previous cached data from disk
25 | async function deleteOldCaches() {
26 | for (const key of await caches.keys()) {
27 | if (key !== CACHE) await caches.delete(key);
28 | }
29 | }
30 |
31 | // @ts-ignore
32 | event.waitUntil(deleteOldCaches());
33 | });
34 |
35 | self.addEventListener('fetch', (event) => {
36 | // ignore POST requests etc
37 | // @ts-ignore
38 | if (event.request.method !== 'GET' || !event.request.url.startsWith('http')) return;
39 |
40 | async function respond() {
41 | // @ts-ignore
42 | const url = new URL(event.request.url);
43 | const cache = await caches.open(CACHE);
44 |
45 | // `build`/`files` can always be served from the cache
46 | if (ASSETS.includes(url.pathname)) {
47 | // @ts-ignore
48 | return cache.match(event.request);
49 | }
50 |
51 | // for everything else, try the network first, but
52 | // fall back to the cache if we're offline
53 | try {
54 | // @ts-ignore
55 | const response = await fetch(event.request);
56 |
57 | if (response.status === 200) {
58 | // @ts-ignore
59 | cache.put(event.request, response.clone());
60 | }
61 |
62 | return response;
63 | } catch {
64 | // @ts-ignore
65 | return cache.match(event.request);
66 | }
67 | }
68 |
69 | // @ts-ignore
70 | event.respondWith(respond());
71 | });
72 |
--------------------------------------------------------------------------------
/docs/static/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/docs/static/android-chrome-192x192.png
--------------------------------------------------------------------------------
/docs/static/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/docs/static/android-chrome-512x512.png
--------------------------------------------------------------------------------
/docs/static/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/docs/static/apple-touch-icon.png
--------------------------------------------------------------------------------
/docs/static/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #2b5797
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/docs/static/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/docs/static/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/docs/static/favicon.ico
--------------------------------------------------------------------------------
/docs/static/googlea3479269b2f388cf.html:
--------------------------------------------------------------------------------
1 | google-site-verification: googlea3479269b2f388cf.html
2 |
--------------------------------------------------------------------------------
/docs/static/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/docs/static/mstile-150x150.png
--------------------------------------------------------------------------------
/docs/static/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.14, written by Peter Selinger 2001-2017
9 |
10 |
12 |
31 |
35 |
48 |
52 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/docs/static/trpc-sveltekit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/docs/static/trpc-sveltekit.png
--------------------------------------------------------------------------------
/docs/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-static';
2 | import preprocess from 'svelte-preprocess';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://github.com/sveltejs/svelte-preprocess
7 | // for more information about preprocessors
8 | preprocess: preprocess(),
9 | kit: {
10 | adapter: adapter(),
11 | paths: { base: '/trpc-sveltekit', relative: false },
12 | appDir: 'internal'
13 | }
14 | };
15 |
16 | export default config;
17 |
--------------------------------------------------------------------------------
/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | }
13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
14 | //
15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
16 | // from the referenced tsconfig.json - TypeScript does not merge them in
17 | }
18 |
--------------------------------------------------------------------------------
/docs/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import type { UserConfig } from 'vite';
3 |
4 | const config: UserConfig = {
5 | plugins: [sveltekit()]
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/examples/bookstall/.env:
--------------------------------------------------------------------------------
1 | JWT_SECRET=eF4mHkWrEb15Uv3qpjg75R9hmJG2BZs5
2 |
--------------------------------------------------------------------------------
/examples/bookstall/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/examples/bookstall/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
5 | plugins: ['svelte3', '@typescript-eslint'],
6 | ignorePatterns: ['*.cjs'],
7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
8 | settings: {
9 | 'svelte3/typescript': () => require('typescript')
10 | },
11 | parserOptions: {
12 | sourceType: 'module',
13 | ecmaVersion: 2020
14 | },
15 | env: {
16 | browser: true,
17 | es2017: true,
18 | node: true
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/examples/bookstall/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 |
7 | # You should uncomment the next lines in your appplication
8 |
9 | # .env
10 | # .env.*
11 | # !.env.example
12 |
13 | vite.config.ts.timestamp-*.*
14 |
15 | # LOCK
16 | bun.lockb
17 |
18 | # PRISMA
19 | prisma/*.db
20 |
--------------------------------------------------------------------------------
/examples/bookstall/.prettierignore:
--------------------------------------------------------------------------------
1 | /.svelte-kit
2 | /package
3 |
--------------------------------------------------------------------------------
/examples/bookstall/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2022, Ionut-Cristian Florescu
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/examples/bookstall/README.md:
--------------------------------------------------------------------------------
1 | # 😎 tRPC-SvelteKit example: Bookstall
2 |
3 | [![License][license-image]][license-url]
4 |
5 | A sample SvelteKit application built to illustrate the usage of [trpc-sveltekit](https://icflorescu.github.io/trpc-sveltekit/).
6 |
7 | ## Screenshot
8 |
9 | 
10 |
11 | ## License
12 |
13 | The [ISC License](LICENSE).
14 |
15 | [license-image]: http://img.shields.io/npm/l/trpc-sveltekit.svg?style=flat-square
16 | [license-url]: LICENSE
17 |
--------------------------------------------------------------------------------
/examples/bookstall/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-bookstall",
3 | "version": "0.0.0",
4 | "author": {
5 | "name": "Ionut-Cristian Florescu",
6 | "email": "ionut.florescu@gmail.com",
7 | "url": "https://github.com/icflorescu"
8 | },
9 | "private": true,
10 | "scripts": {
11 | "dev": "vite --port 3002 --clearScreen false",
12 | "build": "vite build",
13 | "preview": "vite preview --port 3002",
14 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
15 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
16 | "lint": "prettier --plugin-search-dir . --check . && eslint .",
17 | "format": "prettier --plugin-search-dir . --write ."
18 | },
19 | "devDependencies": {
20 | "@picocss/pico": "^1.5.12",
21 | "@prisma/client": "^5.15.0",
22 | "@sveltejs/adapter-auto": "^3.2.2",
23 | "@sveltejs/kit": "^2.18.0",
24 | "@types/debounce": "^1.2.4",
25 | "@types/jsonwebtoken": "^9.0.9",
26 | "@types/node": "^22.13.9",
27 | "sass": "^1.85.1",
28 | "svelte": "^4.2.18",
29 | "svelte-check": "^3.8.0",
30 | "svelte-preprocess": "^6.0.3",
31 | "ts-node": "^10.9.2",
32 | "tslib": "^2.8.1",
33 | "typescript": "^5.8.2",
34 | "vite": "^4.5.2"
35 | },
36 | "type": "module",
37 | "dependencies": {
38 | "@trpc/client": "^10.45.2",
39 | "@trpc/server": "^10.45.2",
40 | "dayjs": "^1.11.13",
41 | "debounce": "^2.2.0",
42 | "hash-wasm": "^4.12.0",
43 | "jsonwebtoken": "^9.0.2",
44 | "prisma": "^5.15.0",
45 | "trpc-transformer": "^3.2.2",
46 | "zod": "^3.24.2"
47 | },
48 | "prisma": {
49 | "seed": "node --loader ts-node/esm prisma/seed.ts"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/examples/bookstall/prisma/bookstall.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/examples/bookstall/prisma/bookstall.db
--------------------------------------------------------------------------------
/examples/bookstall/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | generator client {
2 | provider = "prisma-client-js"
3 | }
4 |
5 | datasource db {
6 | provider = "sqlite"
7 | url = "file:./bookstall.db"
8 | }
9 |
10 | model User {
11 | id String @id @default(cuid())
12 | name String
13 | email String @unique
14 | passwordHash String
15 | updatedAt DateTime @updatedAt
16 |
17 | updatedAuthors Author[]
18 | updatedBooks Book[]
19 | updatedStores Store[]
20 | }
21 |
22 | model Author {
23 | id String @id @default(cuid())
24 | firstName String
25 | lastName String
26 | bio String?
27 | updatedAt DateTime @updatedAt
28 | updatedByUserId String?
29 |
30 | books Book[]
31 | updatedBy User? @relation(fields: [updatedByUserId], references: [id])
32 |
33 | @@unique([firstName, lastName])
34 | }
35 |
36 | model Book {
37 | id String @id @default(cuid())
38 | title String
39 | excerpt String?
40 | authorId String
41 | price Decimal
42 | updatedAt DateTime @updatedAt
43 | updatedByUserId String?
44 | author Author @relation(fields: [authorId], references: [id], onDelete: Cascade)
45 |
46 | stores Store[]
47 | updatedBy User? @relation(fields: [updatedByUserId], references: [id])
48 | }
49 |
50 | model Store {
51 | id String @id @default(cuid())
52 | name String
53 | updatedAt DateTime @updatedAt
54 | updatedByUserId String?
55 |
56 | books Book[]
57 | updatedBy User? @relation(fields: [updatedByUserId], references: [id])
58 | }
59 |
--------------------------------------------------------------------------------
/examples/bookstall/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | // and what to do when importing types
4 | declare namespace App {
5 | // interface Locals {}
6 | // interface PageData {}
7 | // interface Error {}
8 | // interface Platform {}
9 | }
10 |
--------------------------------------------------------------------------------
/examples/bookstall/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/bookstall/src/hooks.server.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from '$lib/trpc/context';
2 | import { router } from '$lib/trpc/router';
3 | import type { Handle } from '@sveltejs/kit';
4 | import { createTRPCHandle } from 'trpc-sveltekit';
5 |
6 | export const handle: Handle = createTRPCHandle({
7 | router,
8 | createContext,
9 | onError: ({ type, path, error }) =>
10 | console.error(`Encountered error while trying to process ${type} @ ${path}:`, error)
11 | });
12 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/assets/trpc-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/examples/bookstall/src/lib/assets/trpc-logo.png
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/components/AuthorizationAlert.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Unauthorized!
20 |
21 | You need to login before trying to perform this action.
22 |
23 | Cancel
24 | Login
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/components/BusyOverlay.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 | {#if visible}
8 |
9 | {/if}
10 |
11 |
26 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/components/Footer.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
29 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/components/Header.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
32 |
33 |
74 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/components/HeaderNavLink.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 | {title}
20 |
21 |
22 |
54 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/components/ModalEditor.svelte:
--------------------------------------------------------------------------------
1 |
47 |
48 |
80 |
81 |
94 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/components/inputs/CheckboxList.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 | {label}
11 |
12 | {#each options as { value, label } (value)}
13 |
14 |
15 | {label}
16 |
17 | {/each}
18 |
19 |
20 |
30 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/components/inputs/LabelAsterisk.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 | {#if required}
6 | *
7 | {/if}
8 |
9 |
15 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/components/inputs/Select.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 | {label}
16 |
17 | Select...
18 | {#each options as { value, label } (value)}
19 | {label}
20 | {/each}
21 |
22 | {#if error}
23 | {error.message}
24 | {/if}
25 |
26 |
27 |
36 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/components/inputs/TextInput.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 | {label}
18 |
27 | {#if error}
28 | {error.message}
29 | {/if}
30 |
31 |
32 |
41 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/components/inputs/TextareaInput.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 | {label}
33 |
42 | {#if error}
43 | {error.message}
44 | {/if}
45 |
46 |
47 |
62 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/dayjs.ts:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs';
2 | import relativeTime from 'dayjs/plugin/relativeTime';
3 | dayjs.extend(relativeTime);
4 |
5 | export default dayjs;
6 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/icons/IconAdd.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/icons/IconClock.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/icons/IconEmpty.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/icons/IconPencil.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/icons/IconTrash.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/icons/IconVerticalDots.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/prisma.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client';
2 |
3 | const prisma = new PrismaClient();
4 |
5 | export default prisma;
6 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/savable.ts:
--------------------------------------------------------------------------------
1 | export function savable>(obj: T): T {
2 | return Object.fromEntries(
3 | Object.entries(obj).map(([key, value]) => {
4 | if (typeof value === 'string') {
5 | const adjustedValue = value.trim();
6 | return [key, adjustedValue === '' ? null : adjustedValue];
7 | }
8 | return [key, value];
9 | })
10 | ) as T;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/trpc/client.ts:
--------------------------------------------------------------------------------
1 | import type { Router } from '$lib/trpc/router';
2 | import { createTRPCClient, type TRPCClientInit } from 'trpc-sveltekit';
3 | import transformer from 'trpc-transformer';
4 |
5 | let browserClient: ReturnType>;
6 |
7 | export function trpc(init?: TRPCClientInit) {
8 | const isBrowser = typeof window !== 'undefined';
9 | if (isBrowser && browserClient) return browserClient;
10 | const client = createTRPCClient({ init, transformer });
11 | if (isBrowser) browserClient = client;
12 | return client;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/trpc/context.ts:
--------------------------------------------------------------------------------
1 | import { JWT_SECRET } from '$env/static/private';
2 | import type { RequestEvent } from '@sveltejs/kit';
3 | import jwt from 'jsonwebtoken';
4 |
5 | export async function createContext(event: RequestEvent) {
6 | try {
7 | const token = event.cookies.get('jwt');
8 | // 👆 or, if we're using HTTP headers based authentication, we could do something like this:
9 | // const token = event.request.headers.get('authorization')?.replace('Bearer ', '');
10 |
11 | const { id: userId } = jwt.verify(token || '', JWT_SECRET) as { id: string };
12 |
13 | return { userId };
14 | } catch {
15 | return { userId: '' };
16 | }
17 | }
18 |
19 | export type Context = Awaited>;
20 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/trpc/middleware/auth.ts:
--------------------------------------------------------------------------------
1 | import { t } from '$lib/trpc/t';
2 | import { TRPCError } from '@trpc/server';
3 |
4 | export const auth = t.middleware(async ({ next, ctx }) => {
5 | if (!ctx.userId) throw new TRPCError({ code: 'UNAUTHORIZED' });
6 | return next();
7 | });
8 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/trpc/middleware/logger.ts:
--------------------------------------------------------------------------------
1 | import { t } from '$lib/trpc/t';
2 |
3 | export const logger = t.middleware(async ({ path, type, next }) => {
4 | const start = Date.now();
5 | const result = await next();
6 | const ms = Date.now() - start;
7 | console.log(`${result.ok ? 'OK' : 'ERR'} ${type} ${path} - ${ms}ms`);
8 | return result;
9 | });
10 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/trpc/router.ts:
--------------------------------------------------------------------------------
1 | import { authors } from '$lib/trpc/routes/authors';
2 | import { books } from '$lib/trpc/routes/books';
3 | import { stores } from '$lib/trpc/routes/stores';
4 | import { t } from '$lib/trpc/t';
5 | import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
6 |
7 | export const router = t.router({
8 | authors,
9 | books,
10 | stores
11 | });
12 |
13 | export const createCaller = t.createCallerFactory(router);
14 |
15 | export type Router = typeof router;
16 |
17 | // 👇 type helpers 💡
18 | export type RouterInputs = inferRouterInputs;
19 | export type RouterOutputs = inferRouterOutputs;
20 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/trpc/routes/authors.ts:
--------------------------------------------------------------------------------
1 | import prisma from '$lib/prisma';
2 | import { auth } from '$lib/trpc/middleware/auth';
3 | import { logger } from '$lib/trpc/middleware/logger';
4 | import { t } from '$lib/trpc/t';
5 | import { z } from 'zod';
6 |
7 | export const authors = t.router({
8 | list: t.procedure
9 | .use(logger)
10 | .input(z.string().optional())
11 | .query(({ input }) =>
12 | prisma.author.findMany({
13 | select: {
14 | id: true,
15 | firstName: true,
16 | lastName: true,
17 | updatedAt: true,
18 | _count: { select: { books: true } }
19 | },
20 | orderBy: { updatedAt: 'desc' },
21 | where: input
22 | ? { OR: [{ firstName: { contains: input } }, { lastName: { contains: input } }] }
23 | : undefined
24 | })
25 | ),
26 |
27 | loadOptions: t.procedure.use(logger).query(() =>
28 | prisma.author
29 | .findMany({
30 | select: { id: true, firstName: true, lastName: true },
31 | orderBy: [{ firstName: 'asc' }, { lastName: 'asc' }]
32 | })
33 | .then((authors) =>
34 | authors.map(({ id, firstName, lastName }) => ({
35 | label: `${firstName} ${lastName}`,
36 | value: id
37 | }))
38 | )
39 | ),
40 |
41 | load: t.procedure
42 | .use(logger)
43 | .use(auth) // 👈 use auth middleware
44 | .input(z.string())
45 | .query(({ input }) =>
46 | prisma.author.findUniqueOrThrow({
47 | select: {
48 | id: true,
49 | firstName: true,
50 | lastName: true,
51 | bio: true,
52 | updatedAt: true,
53 | updatedBy: { select: { name: true } }
54 | },
55 | where: { id: input }
56 | })
57 | ),
58 |
59 | save: t.procedure
60 | .use(logger)
61 | .use(auth) // 👈 use auth middleware
62 | .input(
63 | z.object({
64 | id: z.string().nullable(),
65 | firstName: z.string().min(3).max(50),
66 | lastName: z.string().min(3).max(50),
67 | bio: z.string().nullable()
68 | })
69 | )
70 | .mutation(async ({ input: { id, ...rest }, ctx: { userId } }) => {
71 | if (id) {
72 | await prisma.author.update({
73 | data: { ...rest, updatedByUserId: userId },
74 | where: { id }
75 | });
76 | } else {
77 | await prisma.author.create({
78 | data: { ...rest, updatedByUserId: userId }
79 | });
80 | }
81 | }),
82 |
83 | delete: t.procedure
84 | .use(logger)
85 | .use(auth) // 👈 use auth middleware
86 | .input(z.string())
87 | .mutation(async ({ input: id }) => {
88 | await prisma.author.delete({ where: { id } });
89 | })
90 | });
91 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/trpc/routes/books.ts:
--------------------------------------------------------------------------------
1 | import prisma from '$lib/prisma';
2 | import { auth } from '$lib/trpc/middleware/auth';
3 | import { logger } from '$lib/trpc/middleware/logger';
4 | import { t } from '$lib/trpc/t';
5 | import { z } from 'zod';
6 |
7 | export const books = t.router({
8 | list: t.procedure
9 | .use(logger)
10 | .input(z.string().optional())
11 | .query(({ input }) =>
12 | prisma.book
13 | .findMany({
14 | select: {
15 | id: true,
16 | title: true,
17 | price: true,
18 | updatedAt: true,
19 | author: { select: { firstName: true, lastName: true } },
20 | _count: { select: { stores: true } }
21 | },
22 | orderBy: { updatedAt: 'desc' },
23 | where: input
24 | ? {
25 | OR: [
26 | { title: { contains: input } },
27 | { excerpt: { contains: input } },
28 | { author: { firstName: { contains: input } } },
29 | { author: { lastName: { contains: input } } }
30 | ]
31 | }
32 | : undefined
33 | })
34 | .then((books) => books.map((book) => ({ ...book, price: book.price.toJSON() })))
35 | ),
36 |
37 | load: t.procedure
38 | .use(logger)
39 | .use(auth) // 👈 use auth middleware
40 | .input(z.string())
41 | .query(({ input }) =>
42 | prisma.book
43 | .findUniqueOrThrow({
44 | select: {
45 | id: true,
46 | title: true,
47 | price: true,
48 | excerpt: true,
49 | author: { select: { id: true } },
50 | stores: { select: { id: true } },
51 | updatedAt: true,
52 | updatedBy: { select: { name: true } }
53 | },
54 | where: { id: input }
55 | })
56 | .then(({ author, price, stores, ...rest }) => ({
57 | ...rest,
58 | price: price.toJSON(),
59 | authorId: author.id,
60 | storeIds: stores.map((store) => store.id)
61 | }))
62 | ),
63 |
64 | save: t.procedure
65 | .use(logger)
66 | .use(auth) // 👈 use auth middleware
67 | .input(
68 | z.object({
69 | id: z.string().nullable(),
70 | title: z.string(),
71 | // price: z.custom(),
72 | price: z.string(),
73 | excerpt: z.string().nullable(),
74 | authorId: z.string(),
75 | storeIds: z.array(z.string())
76 | })
77 | )
78 | .mutation(async ({ input: { id, storeIds, ...rest }, ctx: { userId } }) => {
79 | if (id) {
80 | await prisma.book.update({
81 | data: {
82 | ...rest,
83 | stores: { connect: storeIds.map((id) => ({ id })) },
84 | updatedByUserId: userId
85 | },
86 | where: { id }
87 | });
88 | } else {
89 | await prisma.book.create({
90 | data: {
91 | ...rest,
92 | stores: { connect: storeIds.map((id) => ({ id })) },
93 | updatedByUserId: userId
94 | }
95 | });
96 | }
97 | }),
98 |
99 | delete: t.procedure
100 | .use(logger)
101 | .use(auth) // 👈 use auth middleware
102 | .input(z.string())
103 | .mutation(async ({ input: id }) => {
104 | await prisma.book.delete({ where: { id } });
105 | })
106 | });
107 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/trpc/routes/stores.ts:
--------------------------------------------------------------------------------
1 | import prisma from '$lib/prisma';
2 | import { auth } from '$lib/trpc/middleware/auth';
3 | import { logger } from '$lib/trpc/middleware/logger';
4 | import { t } from '$lib/trpc/t';
5 | import { z } from 'zod';
6 |
7 | export const stores = t.router({
8 | list: t.procedure
9 | .use(logger)
10 | .input(z.string().optional())
11 | .query(({ input }) =>
12 | prisma.store.findMany({
13 | select: {
14 | id: true,
15 | name: true,
16 | updatedAt: true,
17 | _count: { select: { books: true } }
18 | },
19 | orderBy: { updatedAt: 'desc' },
20 | where: input ? { name: { contains: input } } : undefined
21 | })
22 | ),
23 |
24 | loadOptions: t.procedure
25 | .use(logger)
26 | .query(() =>
27 | prisma.store
28 | .findMany({ select: { id: true, name: true }, orderBy: { name: 'asc' } })
29 | .then((stores) => stores.map(({ id, name }) => ({ label: name, value: id })))
30 | ),
31 |
32 | load: t.procedure
33 | .use(logger)
34 | .use(auth)
35 | .input(z.string())
36 | .query(({ input }) =>
37 | prisma.store.findUniqueOrThrow({
38 | select: {
39 | id: true,
40 | name: true,
41 | updatedAt: true,
42 | updatedBy: { select: { name: true } }
43 | },
44 | where: { id: input }
45 | })
46 | ),
47 |
48 | save: t.procedure
49 | .use(logger)
50 | .use(auth)
51 | .input(z.object({ id: z.string().nullable(), name: z.string() }))
52 | .mutation(async ({ input: { id, ...rest }, ctx: { userId } }) => {
53 | if (id) {
54 | await prisma.store.update({
55 | data: { ...rest, updatedByUserId: userId },
56 | where: { id }
57 | });
58 | } else {
59 | await prisma.store.create({
60 | data: { ...rest, updatedByUserId: userId }
61 | });
62 | }
63 | }),
64 |
65 | delete: t.procedure
66 | .use(logger)
67 | .use(auth)
68 | .input(z.string())
69 | .mutation(async ({ input: id }) => {
70 | await prisma.store.delete({ where: { id } });
71 | })
72 | });
73 |
--------------------------------------------------------------------------------
/examples/bookstall/src/lib/trpc/t.ts:
--------------------------------------------------------------------------------
1 | import type { Context } from '$lib/trpc/context';
2 | import { initTRPC } from '@trpc/server';
3 | import transformer from 'trpc-transformer';
4 |
5 | export const t = initTRPC.context().create({ transformer });
6 |
--------------------------------------------------------------------------------
/examples/bookstall/src/routes/+layout.server.ts:
--------------------------------------------------------------------------------
1 | import { JWT_SECRET } from '$env/static/private';
2 | import jwt from 'jsonwebtoken';
3 | import type { LayoutServerLoad } from './$types';
4 |
5 | export const load: LayoutServerLoad = async ({ cookies }) => {
6 | try {
7 | const { name: userName } = jwt.verify(cookies.get('jwt') || '', JWT_SECRET) as { name: string };
8 | return { isAuthenticated: true, userName };
9 | } catch {
10 | return { isAuthenticated: false, userName: '' };
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/examples/bookstall/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
72 |
--------------------------------------------------------------------------------
/examples/bookstall/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | A tRPC-SvelteKit example application • Bookstall
10 |
11 |
12 |
13 |
😎
14 |
15 | Welcome to Bookstall, a sample SvelteKit application built to illustrate the usage of ✨
16 | trpc-sveltekit .
19 |
20 | No REST API routes are being used white you're managing books, authors and stores — all data is transferred
21 | through:
22 |
23 |
24 |
25 |
26 | tRPC
27 |
28 |
29 |
30 | You are {data.isAuthenticated ? '' : 'not'} authenticated{data.userName
31 | ? ` as ${data.userName}`
32 | : ''}.
33 |
34 | {#if data.isAuthenticated}
35 | You will be able to browse and edit the books, authors and stores.
36 | {:else}
37 | You will be able to browse the books, authors and stores,
38 |
39 | but you'll need to authenticate in order to edit them.
40 | {/if}
41 |
42 |
43 |
44 |
61 |
--------------------------------------------------------------------------------
/examples/bookstall/src/routes/authors/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from '$lib/trpc/context';
2 | import { createCaller } from '$lib/trpc/router';
3 | import type { PageServerLoad } from './$types';
4 |
5 | export const load: PageServerLoad = async (event) => {
6 | return {
7 | authors: await createCaller(await createContext(event)).authors.list(
8 | event.url.searchParams.get('q') || undefined
9 | )
10 | };
11 | };
12 |
--------------------------------------------------------------------------------
/examples/bookstall/src/routes/authors/+page.svelte:
--------------------------------------------------------------------------------
1 |
79 |
80 |
81 | Authors • Bookstall
82 |
83 |
84 | `${firstName} ${lastName}`
93 | },
94 | {
95 | title: 'Books',
96 | align: 'right',
97 | accessor: (author) => author._count.books
98 | }
99 | ]}
100 | on:add={handleAdd}
101 | on:edit={handleEdit}
102 | on:delete={handleDelete}
103 | />
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | (needsAuthorization = false)} />
114 |
--------------------------------------------------------------------------------
/examples/bookstall/src/routes/books/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from '$lib/trpc/context';
2 | import { createCaller } from '$lib/trpc/router';
3 | import type { PageServerLoad } from './$types';
4 |
5 | export const load: PageServerLoad = async (event) => ({
6 | books: await createCaller(await createContext(event)).books.list(
7 | event.url.searchParams.get('q') || undefined
8 | )
9 | });
10 |
--------------------------------------------------------------------------------
/examples/bookstall/src/routes/login/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { JWT_SECRET } from '$env/static/private';
2 | import prisma from '$lib/prisma';
3 | import { fail } from '@sveltejs/kit';
4 | import { md5 } from 'hash-wasm';
5 | import jwt from 'jsonwebtoken';
6 | import type { Actions } from './$types';
7 |
8 | export const actions: Actions = {
9 | default: async ({ request, cookies }) => {
10 | try {
11 | const data = await request.formData();
12 | const email = data.get('email') as string;
13 | const password = data.get('password') as string;
14 |
15 | // 👇 replace this with a non-naiive hashing algorithm
16 | const passwordHash = await md5(password);
17 |
18 | const { id, name } = await prisma.user.findFirstOrThrow({
19 | where: { email, passwordHash },
20 | select: { id: true, name: true }
21 | });
22 |
23 | cookies.set('jwt', jwt.sign({ id, name }, JWT_SECRET), { path: '/', secure: false });
24 |
25 | return { success: true };
26 | // 👆 or, if we're using HTTP headers based auth, we could return the token,
27 | // and let the client set the header on subsequent requests
28 | } catch {
29 | return fail(401, { error: 'Authentication failed' });
30 | }
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/examples/bookstall/src/routes/login/+page.svelte:
--------------------------------------------------------------------------------
1 |
33 |
34 |
35 | Login • Bookstall
36 |
37 |
38 |
63 |
64 |
65 |
66 |
67 | Please check your credentials and try again.
68 |
71 |
72 |
73 |
74 |
80 |
--------------------------------------------------------------------------------
/examples/bookstall/src/routes/logout/+server.ts:
--------------------------------------------------------------------------------
1 | import type { RequestHandler } from './$types';
2 |
3 | export const POST: RequestHandler = ({ cookies }) => {
4 | cookies.delete('jwt', { path: '/', secure: false });
5 | return new Response();
6 | };
7 |
--------------------------------------------------------------------------------
/examples/bookstall/src/routes/stores/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from '$lib/trpc/context';
2 | import { createCaller } from '$lib/trpc/router';
3 | import type { PageServerLoad } from './$types';
4 |
5 | export const load: PageServerLoad = async (event) => ({
6 | stores: await createCaller(await createContext(event)).stores.list(
7 | event.url.searchParams.get('q') || undefined
8 | )
9 | });
10 |
--------------------------------------------------------------------------------
/examples/bookstall/src/routes/stores/+page.svelte:
--------------------------------------------------------------------------------
1 |
74 |
75 |
76 | Stores • Bookstall
77 |
78 |
79 | store._count.books }
86 | ]}
87 | on:add={handleAdd}
88 | on:edit={handleEdit}
89 | on:delete={handleDelete}
90 | />
91 |
92 |
93 |
94 |
95 |
96 | (needsAuthorization = false)} />
97 |
--------------------------------------------------------------------------------
/examples/bookstall/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/examples/bookstall/static/favicon.png
--------------------------------------------------------------------------------
/examples/bookstall/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import preprocess from 'svelte-preprocess';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://github.com/sveltejs/svelte-preprocess
7 | // for more information about preprocessors
8 | preprocess: preprocess(),
9 | kit: {
10 | adapter: adapter(),
11 | // this is only needed for the example app in trpc-sveltekit monorepo
12 | // you can remove this in your own app, since you'll be installing the package from npm
13 | alias: {
14 | 'trpc-sveltekit':
15 | process.env.NODE_ENV === 'production' ? '../../package/dist' : '../../package/src'
16 | }
17 | }
18 | };
19 |
20 | export default config;
21 |
--------------------------------------------------------------------------------
/examples/bookstall/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | }
13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
14 | //
15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
16 | // from the referenced tsconfig.json - TypeScript does not merge them in
17 | }
18 |
--------------------------------------------------------------------------------
/examples/bookstall/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import type { UserConfig } from 'vite';
3 |
4 | const config: UserConfig = {
5 | plugins: [sveltekit()]
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/examples/simple/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/examples/simple/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
5 | plugins: ['svelte3', '@typescript-eslint'],
6 | ignorePatterns: ['*.cjs'],
7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
8 | settings: {
9 | 'svelte3/typescript': () => require('typescript')
10 | },
11 | parserOptions: {
12 | sourceType: 'module',
13 | ecmaVersion: 2020
14 | },
15 | env: {
16 | browser: true,
17 | es2017: true,
18 | node: true
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/examples/simple/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 |
7 | .env
8 | .env.*
9 | !.env.example
10 |
11 | vite.config.ts.timestamp-*.*
12 |
--------------------------------------------------------------------------------
/examples/simple/.prettierignore:
--------------------------------------------------------------------------------
1 | /.svelte-kit
2 | /package
3 |
--------------------------------------------------------------------------------
/examples/simple/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2022, Ionut-Cristian Florescu
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/examples/simple/README.md:
--------------------------------------------------------------------------------
1 | # 😎 tRPC-SvelteKit example: Simple
2 |
3 | [![License][license-image]][license-url]
4 |
5 | A sample SvelteKit application built to illustrate the usage of [trpc-sveltekit](https://icflorescu.github.io/trpc-sveltekit/).
6 |
7 | ## Screenshot
8 |
9 | 
10 |
11 | ## License
12 |
13 | The [ISC License](LICENSE).
14 |
15 | [license-image]: http://img.shields.io/npm/l/trpc-sveltekit.svg?style=flat-square
16 | [license-url]: LICENSE
17 |
--------------------------------------------------------------------------------
/examples/simple/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-simple",
3 | "version": "0.0.0",
4 | "author": {
5 | "name": "Ionut-Cristian Florescu",
6 | "email": "ionut.florescu@gmail.com",
7 | "url": "https://github.com/icflorescu"
8 | },
9 | "private": true,
10 | "scripts": {
11 | "dev": "vite --port 3001 --clearScreen false",
12 | "build": "vite build",
13 | "preview": "vite preview --port 3001",
14 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
15 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
16 | "lint": "prettier --plugin-search-dir . --check . && eslint .",
17 | "format": "prettier --plugin-search-dir . --write ."
18 | },
19 | "devDependencies": {
20 | "@picocss/pico": "^1.5.12",
21 | "@sveltejs/adapter-auto": "^3.2.2",
22 | "@sveltejs/kit": "^2.18.0",
23 | "sass": "^1.85.1",
24 | "svelte": "^4.2.18",
25 | "svelte-check": "^3.8.0",
26 | "svelte-preprocess": "^6.0.3",
27 | "tslib": "^2.8.1",
28 | "typescript": "^5.8.2",
29 | "vite": "^4.5.2"
30 | },
31 | "type": "module",
32 | "dependencies": {
33 | "@trpc/client": "^10.45.2",
34 | "@trpc/server": "^10.45.2",
35 | "delay": "^6.0.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/examples/simple/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | // and what to do when importing types
4 | declare namespace App {
5 | // interface Locals {}
6 | // interface PageData {}
7 | // interface Error {}
8 | // interface Platform {}
9 | }
10 |
--------------------------------------------------------------------------------
/examples/simple/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/simple/src/hooks.server.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from '$lib/trpc/context';
2 | import { router } from '$lib/trpc/router';
3 | import type { Handle } from '@sveltejs/kit';
4 | import { createTRPCHandle } from 'trpc-sveltekit';
5 |
6 | export const handle: Handle = createTRPCHandle({ router, createContext });
7 |
--------------------------------------------------------------------------------
/examples/simple/src/lib/trpc/client.ts:
--------------------------------------------------------------------------------
1 | import type { Router } from '$lib/trpc/router';
2 | import { createTRPCClient, type TRPCClientInit } from 'trpc-sveltekit';
3 |
4 | let browserClient: ReturnType>;
5 |
6 | export function trpc(init?: TRPCClientInit) {
7 | const isBrowser = typeof window !== 'undefined';
8 | if (isBrowser && browserClient) return browserClient;
9 | const client = createTRPCClient({ init });
10 | if (isBrowser) browserClient = client;
11 | return client;
12 | }
13 |
--------------------------------------------------------------------------------
/examples/simple/src/lib/trpc/context.ts:
--------------------------------------------------------------------------------
1 | import type { RequestEvent } from '@sveltejs/kit';
2 |
3 | export async function createContext(event: RequestEvent) {
4 | return {
5 | event // 👈 `event` is now available in your context
6 | };
7 | }
8 |
9 | export type Context = Awaited>;
10 |
--------------------------------------------------------------------------------
/examples/simple/src/lib/trpc/router.ts:
--------------------------------------------------------------------------------
1 | import type { Context } from '$lib/trpc/context';
2 | import { initTRPC } from '@trpc/server';
3 | import delay from 'delay';
4 |
5 | export const t = initTRPC.context().create();
6 |
7 | export const router = t.router({
8 | greeting: t.procedure.query(async () => {
9 | await delay(500); // 👈 simulate an expensive operation
10 | return `Hello tRPC v10 @ ${new Date().toLocaleTimeString()}`;
11 | })
12 | });
13 |
14 | export const createCaller = t.createCallerFactory(router);
15 |
16 | export type Router = typeof router;
17 |
--------------------------------------------------------------------------------
/examples/simple/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 | A simple tRPC-SvelteKit Example Application
16 |
17 |
18 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
101 |
--------------------------------------------------------------------------------
/examples/simple/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 | Loading data in+page.svelte
16 |
17 | Load
24 | {greeting}
25 |
--------------------------------------------------------------------------------
/examples/simple/src/routes/page-data/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 | Data loaded in+page.ts
8 | {data.greeting}
9 |
--------------------------------------------------------------------------------
/examples/simple/src/routes/page-data/+page.ts:
--------------------------------------------------------------------------------
1 | import { trpc } from '$lib/trpc/client';
2 | import type { PageLoad } from './$types';
3 |
4 | // 👇 this method will be invoked on BOTH the server and the client, as needed ⚠️
5 | export const load: PageLoad = async (event) => ({
6 | greeting: await trpc(event).greeting.query()
7 | });
8 |
--------------------------------------------------------------------------------
/examples/simple/src/routes/page-server-data/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from '$lib/trpc/context';
2 | import { createCaller } from '$lib/trpc/router';
3 | import type { PageServerLoad } from './$types';
4 |
5 | // 👇 since this is only called on the server, we can bypass HTTP 💡
6 | export const load: PageServerLoad = async (event) => ({
7 | greeting: await createCaller(await createContext(event)).greeting()
8 | });
9 |
--------------------------------------------------------------------------------
/examples/simple/src/routes/page-server-data/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 | Data loaded in+page.ts
8 | {data.greeting}
9 |
--------------------------------------------------------------------------------
/examples/simple/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/examples/simple/static/favicon.png
--------------------------------------------------------------------------------
/examples/simple/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import preprocess from 'svelte-preprocess';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://github.com/sveltejs/svelte-preprocess
7 | // for more information about preprocessors
8 | preprocess: preprocess(),
9 | kit: {
10 | adapter: adapter(),
11 | // this is only needed for the example app in trpc-sveltekit monorepo
12 | // you can remove this in your own app, since you'll be installing the package from npm
13 | alias: {
14 | 'trpc-sveltekit':
15 | process.env.NODE_ENV === 'production' ? '../../package/dist' : '../../package/src'
16 | }
17 | }
18 | };
19 |
20 | export default config;
21 |
--------------------------------------------------------------------------------
/examples/simple/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | }
13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
14 | //
15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
16 | // from the referenced tsconfig.json - TypeScript does not merge them in
17 | }
18 |
--------------------------------------------------------------------------------
/examples/simple/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import type { UserConfig } from 'vite';
3 |
4 | const config: UserConfig = {
5 | plugins: [sveltekit()]
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/examples/websocket/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/examples/websocket/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
5 | plugins: ['svelte3', '@typescript-eslint'],
6 | ignorePatterns: ['*.cjs'],
7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
8 | settings: {
9 | 'svelte3/typescript': () => require('typescript')
10 | },
11 | parserOptions: {
12 | sourceType: 'module',
13 | ecmaVersion: 2020
14 | },
15 | env: {
16 | browser: true,
17 | es2017: true,
18 | node: true
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/examples/websocket/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 |
7 | .env
8 | .env.*
9 | !.env.example
10 |
11 | vite.config.ts.timestamp-*.*
12 |
--------------------------------------------------------------------------------
/examples/websocket/.prettierignore:
--------------------------------------------------------------------------------
1 | /.svelte-kit
2 | /package
3 |
--------------------------------------------------------------------------------
/examples/websocket/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2022, Ionut-Cristian Florescu
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/examples/websocket/README.md:
--------------------------------------------------------------------------------
1 | # 😎 tRPC-SvelteKit example: Websocket
2 |
3 | [![License][license-image]][license-url]
4 |
5 | **STATE:** Experimental
6 |
7 | - [x] Websocket works in development, with wroken hot reload
8 | - [x] Fix hot reload
9 | - [x] Websocket works in production
10 | - [x] Document usage & implementation
11 | - [ ] Website docs
12 |
13 | ## License
14 |
15 | The [ISC License](LICENSE).
16 |
17 | [license-image]: http://img.shields.io/npm/l/trpc-sveltekit.svg?style=flat-square
18 | [license-url]: LICENSE
19 |
--------------------------------------------------------------------------------
/examples/websocket/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-websocket",
3 | "version": "0.0.0",
4 | "author": "https://github.com/SrZorro",
5 | "private": true,
6 | "scripts": {
7 | "dev": "vite --port 3003 --clearScreen false",
8 | "build": "vite build",
9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
11 | "lint": "prettier --plugin-search-dir . --check . && eslint .",
12 | "format": "prettier --plugin-search-dir . --write .",
13 | "start": "node ./wsServer"
14 | },
15 | "devDependencies": {
16 | "@picocss/pico": "^1.5.12",
17 | "@sveltejs/adapter-node": "^5.1.0",
18 | "@sveltejs/kit": "^2.18.0",
19 | "sass": "^1.85.1",
20 | "svelte": "^4.2.18",
21 | "svelte-check": "^3.8.0",
22 | "svelte-preprocess": "^6.0.3",
23 | "tslib": "^2.8.1",
24 | "typescript": "^5.8.2",
25 | "vite": "^4.5.2"
26 | },
27 | "type": "module",
28 | "dependencies": {
29 | "@trpc/client": "^10.45.2",
30 | "@trpc/server": "^10.45.2",
31 | "delay": "^6.0.0",
32 | "ws": "^8.18.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/examples/websocket/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | // and what to do when importing types
4 | declare namespace App {
5 | // interface Locals {}
6 | // interface PageData {}
7 | // interface Error {}
8 | // interface Platform {}
9 | }
10 |
--------------------------------------------------------------------------------
/examples/websocket/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/websocket/src/hooks.server.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from '$lib/trpc/context';
2 | import { router } from '$lib/trpc/router';
3 | import { createTRPCWebSocketServer } from 'trpc-sveltekit/websocket';
4 |
5 | import { building } from '$app/environment';
6 |
7 | if (!building) createTRPCWebSocketServer({ router, createContext });
8 |
--------------------------------------------------------------------------------
/examples/websocket/src/lib/trpc/client.ts:
--------------------------------------------------------------------------------
1 | import type { Router } from '$lib/trpc/router';
2 | import { createTRPCWebSocketClient } from 'trpc-sveltekit/websocket';
3 |
4 | let browserClient: ReturnType>;
5 |
6 | export function trpc() {
7 | const isBrowser = typeof window !== 'undefined';
8 | if (isBrowser && browserClient) return browserClient;
9 | const client = createTRPCWebSocketClient();
10 | if (isBrowser) browserClient = client;
11 | return client;
12 | }
13 |
--------------------------------------------------------------------------------
/examples/websocket/src/lib/trpc/context.ts:
--------------------------------------------------------------------------------
1 | import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
2 | import type { CreateWSSContextFnOptions } from '@trpc/server/adapters/ws';
3 |
4 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
5 | export async function createContext(opts: CreateHTTPContextOptions | CreateWSSContextFnOptions) {
6 | return {
7 | // context information
8 | };
9 | }
10 |
11 | export type Context = Awaited>;
12 |
--------------------------------------------------------------------------------
/examples/websocket/src/lib/trpc/router.ts:
--------------------------------------------------------------------------------
1 | import type { Context } from '$lib/trpc/context';
2 | import { initTRPC } from '@trpc/server';
3 | import { observable } from '@trpc/server/observable';
4 | import { EventEmitter } from 'events';
5 |
6 | export const t = initTRPC.context().create();
7 |
8 | const ee = new EventEmitter();
9 |
10 | export const router = t.router({
11 | allMessages: t.procedure.subscription(() => {
12 | return observable((emit) => {
13 | const onAdd = (message: string) => {
14 | emit.next(`${new Date().toLocaleTimeString()}: ${message}`);
15 | };
16 |
17 | ee.on('add', onAdd);
18 |
19 | return () => {
20 | ee.off('add', onAdd);
21 | };
22 | });
23 | }),
24 | addMessage: t.procedure
25 | .input((input: unknown) => {
26 | if (typeof input === 'string') return input;
27 |
28 | throw new Error('Invalid input type');
29 | })
30 | .mutation(async ({ input: message }) => {
31 | ee.emit('add', message);
32 |
33 | return { success: true };
34 | })
35 | });
36 |
37 | export type Router = typeof router;
38 |
--------------------------------------------------------------------------------
/examples/websocket/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
18 |
--------------------------------------------------------------------------------
/examples/websocket/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 | WebSocket example
15 |
16 | Open {$page.url}messages in a new window/tab.
17 |
18 |
19 |
23 |
24 |
33 |
--------------------------------------------------------------------------------
/examples/websocket/src/routes/messages/+page.svelte:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
✨ WebSocket Messages ✨
27 |
28 | {#each messages as message, i}
29 | {#if i === messages.length - 1}
30 |
{message}
31 | {:else}
32 | {message}
33 | {/if}
34 | {/each}
35 |
36 |
37 |
38 |
61 |
--------------------------------------------------------------------------------
/examples/websocket/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icflorescu/trpc-sveltekit/3a80f791f1fde969d51159862bd23c2328f3fdb8/examples/websocket/static/favicon.png
--------------------------------------------------------------------------------
/examples/websocket/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-node';
2 | import preprocess from 'svelte-preprocess';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://github.com/sveltejs/svelte-preprocess
7 | // for more information about preprocessors
8 | preprocess: preprocess(),
9 | kit: {
10 | adapter: adapter(),
11 | // this is only needed for the example app in trpc-sveltekit monorepo
12 | // you can remove this in your own app, since you'll be installing the package from npm
13 | alias: {
14 | 'trpc-sveltekit':
15 | process.env.NODE_ENV === 'production' ? '../../package/dist' : '../../package/src'
16 | }
17 | }
18 | };
19 |
20 | export default config;
21 |
--------------------------------------------------------------------------------
/examples/websocket/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | }
13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
14 | //
15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
16 | // from the referenced tsconfig.json - TypeScript does not merge them in
17 | }
18 |
--------------------------------------------------------------------------------
/examples/websocket/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import type { UserConfig } from 'vite';
3 |
4 | import { vitePluginTrpcWebSocket } from '../../package/src/websocket';
5 | // Replace the above in your project with:
6 | // import { vitePluginTrpcWebSocket } from 'trpc-sveltekit/websocket';
7 |
8 | const config: UserConfig = {
9 | plugins: [sveltekit(), vitePluginTrpcWebSocket]
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/examples/websocket/wsServer.js:
--------------------------------------------------------------------------------
1 | import { SvelteKitTRPCWSServer } from 'trpc-sveltekit/websocket';
2 |
3 | SvelteKitTRPCWSServer(import.meta.url);
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "trpc-sveltekit-monorepo",
3 | "version": "0.0.0",
4 | "description": "SvelteKit adapter for trpc.io",
5 | "author": {
6 | "name": "Ionut-Cristian Florescu",
7 | "email": "ionut.florescu@gmail.com",
8 | "url": "https://github.com/icflorescu"
9 | },
10 | "license": "ISC",
11 | "private": true,
12 | "type": "module",
13 | "workspaces": [
14 | "package",
15 | "docs",
16 | "examples/*"
17 | ],
18 | "scripts": {
19 | "dev": "turbo dev",
20 | "build": "turbo build",
21 | "start": "turbo preview",
22 | "lint": "turbo lint",
23 | "format": "turbo format"
24 | },
25 | "devDependencies": {
26 | "@typescript-eslint/eslint-plugin": "^7.13.0",
27 | "@typescript-eslint/parser": "^7.13.0",
28 | "eslint": "^8.57.0",
29 | "eslint-config-prettier": "^9.1.0",
30 | "eslint-plugin-svelte3": "^4.0.0",
31 | "prettier": "^3.3.2",
32 | "prettier-plugin-svelte": "^3.2.4",
33 | "turbo": "^1.12.4"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/package/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # Ignore files for PNPM, NPM and YARN
6 | pnpm-lock.yaml
7 | package-lock.json
8 | yarn.lock
9 |
--------------------------------------------------------------------------------
/package/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
5 | plugins: ['svelte3', '@typescript-eslint'],
6 | ignorePatterns: ['*.cjs'],
7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
8 | settings: {
9 | 'svelte3/typescript': () => require('typescript')
10 | },
11 | parserOptions: {
12 | sourceType: 'module',
13 | ecmaVersion: 2020
14 | },
15 | env: {
16 | browser: true,
17 | es2017: true,
18 | node: true
19 | },
20 | rules: {
21 | '@typescript-eslint/no-explicit-any': 'off'
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/package/.gitignore:
--------------------------------------------------------------------------------
1 | README.md
2 | LICENSE
3 | dist
4 |
--------------------------------------------------------------------------------
/package/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "trpc-sveltekit",
3 | "version": "3.6.3",
4 | "description": "SvelteKit adapter for tRPC.io, working with Node.js, Vercel and Netlify",
5 | "keywords": [
6 | "trpc",
7 | "svelte",
8 | "sveltekit",
9 | "kit",
10 | "adapter",
11 | "handle",
12 | "typescript",
13 | "rpc",
14 | "vercel",
15 | "netlify",
16 | "node"
17 | ],
18 | "repository": "icflorescu/trpc-sveltekit",
19 | "homepage": "https://icflorescu.github.io/trpc-sveltekit/",
20 | "bugs": {
21 | "url": "https://github.com/icflorescu/trpc-sveltekit/issues"
22 | },
23 | "author": {
24 | "name": "Ionut-Cristian Florescu",
25 | "email": "ionut.florescu@gmail.com",
26 | "url": "https://github.com/icflorescu"
27 | },
28 | "funding": {
29 | "type": "github",
30 | "url": "https://github.com/sponsors/icflorescu"
31 | },
32 | "license": "ISC",
33 | "type": "module",
34 | "module": "dist/index.js",
35 | "types": "dist/index.d.ts",
36 | "files": [
37 | "dist",
38 | "README.md",
39 | "LICENSE"
40 | ],
41 | "exports": {
42 | ".": {
43 | "types": "./dist/index.d.ts",
44 | "import": "./dist/index.js"
45 | },
46 | "./websocket": {
47 | "types": "./dist/websocket/index.d.ts",
48 | "import": "./dist/websocket/index.js"
49 | },
50 | "./package.json": "./package.json"
51 | },
52 | "typesVersions": {
53 | "*": {
54 | "websocket": [
55 | "dist/websocket/index.d.ts"
56 | ]
57 | }
58 | },
59 | "scripts": {
60 | "build": "tsc && cp ../README.md README.md && cp ../LICENSE LICENSE",
61 | "lint": "prettier --plugin-search-dir . --check . && eslint .",
62 | "format": "prettier --plugin-search-dir . --write ."
63 | },
64 | "devDependencies": {
65 | "@sveltejs/kit": "^1.27.0",
66 | "@trpc/client": "^10.45.2",
67 | "@trpc/server": "^10.45.2",
68 | "@types/ws": "^8.18.0",
69 | "typescript": "^5.8.2"
70 | },
71 | "peerDependencies": {
72 | "@sveltejs/adapter-node": ">=1.2",
73 | "@trpc/client": "^10.0.0",
74 | "@trpc/server": "^10.0.0",
75 | "ws": ">=8"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/package/src/ValidRoute.ts:
--------------------------------------------------------------------------------
1 | type HasTrailingSlash = S extends `${string}/`
2 | ? IfTrue
3 | : IfFalse;
4 |
5 | type ValidateRouteStart = HasTrailingSlash<
6 | S,
7 | IfInvalid,
8 | S extends `/${string}` ? IfValid : IfInvalid
9 | >;
10 |
11 | type ValidateRouteEnd = string & {
12 | __errorMsg: `${T} is not a valid route because ${HasTrailingSlash<
13 | T,
14 | 'it has a trailing slash',
15 | 'it does not start with a slash'
16 | >}`;
17 | };
18 |
19 | export type ValidRoute = ValidateRouteStart> | '/trpc';
20 |
--------------------------------------------------------------------------------
/package/src/client.ts:
--------------------------------------------------------------------------------
1 | import {
2 | httpBatchLink,
3 | createTRPCProxyClient as internalCreateTRPCClient,
4 | type HTTPHeaders,
5 | type TRPCLink
6 | } from '@trpc/client';
7 | import type { AnyRouter } from '@trpc/server';
8 |
9 | export type TRPCClientInit = { fetch?: typeof window.fetch; url: { origin: string } };
10 |
11 | type CreateTRPCClientOptions = (
12 | | {
13 | links?: never;
14 |
15 | /**
16 | * The tRPC api endpoint URL.
17 | * @default '/trpc'
18 | */
19 | url?: `/${string}`;
20 |
21 | /**
22 | * A page store or SvelteKit load event.
23 | * @see https://kit.svelte.dev/docs/modules#$app-stores
24 | * @see https://kit.svelte.dev/docs/load
25 | */
26 | init?: TRPCClientInit;
27 |
28 | /**
29 | * Additional headers to send with the request. Can be a function that returns headers.
30 | * @see https://developer.mozilla.org/en-US/docs/Web/API/Headers
31 | */
32 | headers?: HTTPHeaders | (() => HTTPHeaders | Promise);
33 | }
34 | | {
35 | /**
36 | * A custom list of links to use for the tRPC Client instead of the default one.
37 | * @see https://trpc.io/docs/links
38 | */
39 | links: TRPCLink[];
40 |
41 | url?: never;
42 | init?: never;
43 | headers?: never;
44 | }
45 | ) & {
46 | /**
47 | * A function that transforms the data before transferring it.
48 | * @see https://trpc.io/docs/data-transformers
49 | */
50 | transformer?: Router['_def']['_config']['transformer'];
51 | };
52 |
53 | /**
54 | * Create a tRPC client.
55 | * @see https://trpc.io/docs/vanilla
56 | */
57 | export function createTRPCClient(
58 | { links, url = '/trpc', transformer, init, headers }: CreateTRPCClientOptions = {
59 | url: '/trpc'
60 | }
61 | ) {
62 | if (links) return internalCreateTRPCClient({ transformer, links });
63 |
64 | if (typeof window === 'undefined' && !init) {
65 | throw new Error(
66 | 'Calling createTRPCClient() on the server requires passing a valid LoadEvent argument'
67 | );
68 | }
69 |
70 | return internalCreateTRPCClient({
71 | transformer,
72 | links: [
73 | httpBatchLink({
74 | url:
75 | typeof window === 'undefined' ? `${init.url.origin}${url}` : `${location.origin}${url}`,
76 | fetch: typeof window === 'undefined' ? init.fetch : init?.fetch ?? window.fetch,
77 | headers
78 | })
79 | ]
80 | });
81 | }
82 |
--------------------------------------------------------------------------------
/package/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './client.js';
2 | export * from './server.js';
3 |
--------------------------------------------------------------------------------
/package/src/websocket/client.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CreateTRPCClientOptions,
3 | createTRPCProxyClient,
4 | createWSClient,
5 | wsLink
6 | } from '@trpc/client';
7 | import { AnyRouter } from '@trpc/server';
8 |
9 | export function createTRPCWebSocketClient(): ReturnType<
10 | typeof createTRPCProxyClient
11 | > {
12 | if (typeof location === 'undefined') return;
13 |
14 | const uri = `${location.protocol === 'http:' ? 'ws:' : 'wss:'}//${location.host}/trpc`;
15 |
16 | const wsClient = createWSClient({
17 | url: uri
18 | });
19 |
20 | return createTRPCProxyClient({
21 | links: [wsLink({ client: wsClient })]
22 | } as CreateTRPCClientOptions);
23 | }
24 |
--------------------------------------------------------------------------------
/package/src/websocket/index.ts:
--------------------------------------------------------------------------------
1 | export * from './client.js';
2 | export * from './server.js';
3 | export * from './svelteKitServer.js';
4 | export * from './vitePlugin.js';
5 |
--------------------------------------------------------------------------------
/package/src/websocket/server.ts:
--------------------------------------------------------------------------------
1 | import { AnyRouter, inferRouterContext } from '@trpc/server';
2 | import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
3 | import type { CreateWSSContextFnOptions } from '@trpc/server/adapters/ws';
4 | import { applyWSSHandler } from '@trpc/server/adapters/ws';
5 | import type { Server } from 'ws';
6 | import { GlobalThisWSS } from './svelteKitServer.js';
7 |
8 | export async function createTRPCWebSocketServer({
9 | router,
10 | createContext
11 | }: {
12 | /**
13 | * The tRPC router to use.
14 | * @see https://trpc.io/docs/router
15 | */
16 | router: Router;
17 |
18 | /**
19 | * An async function that returns the tRPC context.
20 | * @see https://trpc.io/docs/context
21 | */
22 | createContext?: (
23 | opts: CreateHTTPContextOptions | CreateWSSContextFnOptions
24 | ) => Promise>;
25 | }) {
26 | const wss = globalThis[GlobalThisWSS] as Server;
27 | if (typeof wss === 'undefined') {
28 | // Websocket server not created
29 | console.error("WebSocket server not found but 'createTRPCWebSocketServer' had been called");
30 | // Prerendering with websockets is not implemented
31 | // TODO: Fallback to REST for non subscriptions?
32 | process.exit(1);
33 | } else {
34 | wss.removeAllListeners();
35 | applyWSSHandler({
36 | createContext,
37 | router,
38 | wss
39 | });
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/package/src/websocket/svelteKitServer.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as url from 'url';
3 | import type { Server } from 'ws';
4 | import { WebSocketServer } from 'ws';
5 |
6 | export const GlobalThisWSS = Symbol.for('trpc.sveltekit.wss');
7 |
8 | export function onHttpServerUpgrade(req, sock, head) {
9 | const pathname = url.parse(req.url).pathname;
10 | if (pathname !== '/trpc') return;
11 |
12 | const wss = globalThis[GlobalThisWSS] as Server;
13 |
14 | wss.handleUpgrade(req, sock, head, function done(ws) {
15 | wss.emit('connection', ws, req);
16 | });
17 | }
18 |
19 | export function createWSSGlobalInstance() {
20 | const wss = new WebSocketServer({
21 | noServer: true
22 | });
23 |
24 | globalThis[GlobalThisWSS] = wss;
25 |
26 | return wss;
27 | }
28 |
29 | export async function SvelteKitTRPCWSServer(import_meta_url: string) {
30 | const __filename = url.fileURLToPath(import_meta_url);
31 | const __dirname = path.dirname(__filename);
32 |
33 | createWSSGlobalInstance();
34 |
35 | const { server } = await import(/* @vite-ignore */ path.resolve(__dirname, './build/index.js'));
36 | server.server.on('upgrade', onHttpServerUpgrade);
37 | }
38 |
--------------------------------------------------------------------------------
/package/src/websocket/vitePlugin.ts:
--------------------------------------------------------------------------------
1 | import type { PluginOption } from 'vite';
2 |
3 | import { createWSSGlobalInstance, onHttpServerUpgrade } from './svelteKitServer.js';
4 |
5 | export const vitePluginTrpcWebSocket: PluginOption = {
6 | name: 'TrpcWebSocketServer',
7 | configureServer(server) {
8 | createWSSGlobalInstance();
9 | server.httpServer?.on('upgrade', onHttpServerUpgrade);
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/package/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "lib": ["esnext", "DOM", "DOM.Iterable"],
5 | "moduleResolution": "Node",
6 | "declaration": true,
7 | "skipLibCheck": true,
8 | "module": "ESNext",
9 | "target": "ESNext",
10 | "outDir": "dist",
11 | "rootDirs": ["src", "node_modules/@sveltejs/kit/src/runtime/components"],
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "pipeline": {
4 | "dev": { "cache": false },
5 | "docs#dev": { "cache": false },
6 | "example-simple#dev": { "cache": false },
7 | "example-bookstall#dev": { "cache": false },
8 | "example-websocket#dev": { "cache": false },
9 | "trpc-sveltekit#build": { "outputs": ["dist/**"] },
10 | "docs#build": { "dependsOn": ["trpc-sveltekit#build"], "outputs": ["build/**"] },
11 | "preview": { "dependsOn": ["^build"] },
12 | "lint": { "outputs": [] },
13 | "format": { "outputs": [] }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------