@kevinmarrec/create-app 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +12 -11
  3. package/template/.vscode/extensions.json +2 -1
  4. package/template/.vscode/settings.json +4 -0
  5. package/template/backend/.env.development +3 -1
  6. package/template/backend/package.json +9 -8
  7. package/template/backend/src/auth/index.ts +23 -0
  8. package/template/backend/src/database/drizzle/config.ts +9 -0
  9. package/template/backend/src/database/index.ts +4 -4
  10. package/template/backend/src/database/migrations/0000_violet_enchantress.sql +49 -0
  11. package/template/backend/src/database/migrations/meta/0000_snapshot.json +292 -7
  12. package/template/backend/src/database/migrations/meta/_journal.json +2 -2
  13. package/template/backend/src/database/schema/accounts.ts +20 -0
  14. package/template/backend/src/database/schema/index.ts +3 -0
  15. package/template/backend/src/database/schema/sessions.ts +15 -0
  16. package/template/backend/src/database/schema/users.ts +7 -3
  17. package/template/backend/src/database/schema/verifications.ts +11 -0
  18. package/template/backend/{env.d.ts → src/env.d.ts} +2 -0
  19. package/template/backend/src/{server.ts → main.ts} +13 -19
  20. package/template/backend/src/orpc/index.ts +65 -0
  21. package/template/backend/src/orpc/middlewares/auth.ts +33 -0
  22. package/template/backend/src/orpc/middlewares/index.ts +1 -0
  23. package/template/backend/src/orpc/router/auth.ts +54 -0
  24. package/template/backend/src/{router → orpc/router}/index.ts +2 -2
  25. package/template/backend/src/{logger.ts → utils/logger.ts} +1 -3
  26. package/template/frontend/package.json +12 -13
  27. package/template/frontend/src/App.vue +96 -10
  28. package/template/frontend/src/composables/auth.ts +31 -0
  29. package/template/frontend/src/composables/index.ts +1 -0
  30. package/template/frontend/src/lib/orpc.ts +6 -1
  31. package/template/knip.config.ts +1 -1
  32. package/template/package.json +9 -9
  33. package/template/backend/src/config/database.ts +0 -11
  34. package/template/backend/src/config/drizzle.ts +0 -10
  35. package/template/backend/src/config/logger.ts +0 -19
  36. package/template/backend/src/config/server.ts +0 -40
  37. package/template/backend/src/database/migrations/0000_white_bishop.sql +0 -6
  38. package/template/backend/src/lib/orpc.ts +0 -10
  39. package/template/backend/src/router/welcome.ts +0 -11
  40. package/template/frontend/src/queries/index.ts +0 -1
  41. package/template/frontend/src/queries/welcome.ts +0 -10
  42. package/template/frontend/src/utils.ts +0 -1
  43. /package/template/frontend/{env.d.ts → src/env.d.ts} +0 -0
@@ -1,30 +1,22 @@
1
1
  import process from 'node:process'
2
2
 
3
- import { onError } from '@orpc/server'
4
- import { RPCHandler } from '@orpc/server/fetch'
5
- import { CORSPlugin } from '@orpc/server/plugins'
6
-
7
- import { cors, hostname, port } from './config/server'
8
- import { db } from './database'
9
- import { logger } from './logger'
10
- import { router } from './router'
11
-
12
- const rpcHandler = new RPCHandler(router, {
13
- plugins: [new CORSPlugin(cors)],
14
- interceptors: [
15
- onError((error) => {
16
- logger.error(error)
17
- }),
18
- ],
19
- })
3
+ import { createBetterAuth } from '@backend/auth'
4
+ import { db } from '@backend/database'
5
+ import { createRpcHandler } from '@backend/orpc'
6
+ import { router } from '@backend/orpc/router'
7
+ import { logger } from '@backend/utils/logger'
8
+
9
+ const auth = createBetterAuth({ db, logger })
10
+ const rpcHandler = createRpcHandler(router)
20
11
 
21
12
  const server = Bun.serve({
22
- hostname,
23
- port,
13
+ hostname: import.meta.env.HOST ?? '0.0.0.0',
14
+ port: import.meta.env.PORT ?? 4000,
24
15
  async fetch(request) {
25
16
  const { matched, response } = await rpcHandler.handle(request, {
26
17
  prefix: '/rpc',
27
18
  context: {
19
+ auth,
28
20
  db,
29
21
  logger,
30
22
  },
@@ -43,6 +35,8 @@ const server = Bun.serve({
43
35
 
44
36
  logger.info(`Listening on ${server.url}`)
45
37
 
38
+ // Graceful Shutdown
39
+
46
40
  async function gracefulShutdown() {
47
41
  logger.info('Gracefully shutting down...')
48
42
  await server.stop()
@@ -0,0 +1,65 @@
1
+ import type { Auth } from '@backend/auth'
2
+ import type { Database } from '@backend/database'
3
+ import { requiredAuthMiddleware } from '@backend/orpc/middlewares'
4
+ import type { Logger } from '@backend/utils/logger'
5
+ import { onError, ORPCError, os, type Router } from '@orpc/server'
6
+ import { RPCHandler } from '@orpc/server/fetch'
7
+ import {
8
+ CORSPlugin,
9
+ RequestHeadersPlugin,
10
+ type RequestHeadersPluginContext,
11
+ ResponseHeadersPlugin,
12
+ type ResponseHeadersPluginContext,
13
+ } from '@orpc/server/plugins'
14
+ import { APIError } from 'better-auth/api'
15
+
16
+ /* Context */
17
+
18
+ export interface Context extends RequestHeadersPluginContext, ResponseHeadersPluginContext {
19
+ auth: Auth
20
+ db: Database
21
+ logger: Logger
22
+ }
23
+
24
+ /* RPC Handler */
25
+
26
+ export function createRpcHandler<T extends Context>(router: Router<any, T>) {
27
+ return new RPCHandler<T>(router, {
28
+ plugins: [
29
+ new CORSPlugin({
30
+ credentials: true,
31
+ maxAge: 7200, // 2 hours https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Max-Age
32
+ }),
33
+ new RequestHeadersPlugin(),
34
+ new ResponseHeadersPlugin(),
35
+ ],
36
+ clientInterceptors: [
37
+ onError((error, { context }) => {
38
+ if (error instanceof APIError) {
39
+ throw new ORPCError(error.body?.code ?? 'INTERNAL_SERVER_ERROR', {
40
+ status: error.statusCode,
41
+ message: error.body?.message,
42
+ })
43
+ }
44
+
45
+ if (error instanceof ORPCError) {
46
+ throw error
47
+ }
48
+
49
+ context.logger.error(error)
50
+ }),
51
+ ],
52
+ })
53
+ }
54
+
55
+ /* Builder */
56
+
57
+ export const pub = os
58
+ .$context<Context>()
59
+ .errors({
60
+ UNAUTHORIZED: { status: 401 },
61
+ })
62
+
63
+ /** @beta */
64
+ export const authed = pub
65
+ .use(requiredAuthMiddleware)
@@ -0,0 +1,33 @@
1
+ import type { Context } from '@backend/orpc'
2
+ import { ORPCError, os } from '@orpc/server'
3
+
4
+ export const authMiddleware = os
5
+ .$context<Context>()
6
+ .middleware(async ({ context, next }) => {
7
+ const { headers, response: session } = await context.auth.api.getSession({
8
+ headers: context.reqHeaders ?? new Headers(),
9
+ returnHeaders: true,
10
+ })
11
+
12
+ headers.forEach((v, k) => context.resHeaders?.append(k, v))
13
+
14
+ return next({
15
+ context: {
16
+ user: session ? session.user : null,
17
+ },
18
+ })
19
+ })
20
+
21
+ export const requiredAuthMiddleware = authMiddleware
22
+ .concat(async ({ context, next }) => {
23
+ if (!context.user) {
24
+ throw new ORPCError('UNAUTHORIZED')
25
+ }
26
+
27
+ return next({
28
+ context: {
29
+ user: context.user,
30
+ },
31
+ })
32
+ },
33
+ )
@@ -0,0 +1 @@
1
+ export * from './auth'
@@ -0,0 +1,54 @@
1
+ import { pub } from '@backend/orpc'
2
+ import { authMiddleware } from '@backend/orpc/middlewares'
3
+ import * as v from 'valibot'
4
+
5
+ export const getCurrentUser = pub
6
+ .use(authMiddleware)
7
+ .handler(async ({ context }) => context.user)
8
+
9
+ export const signUp = pub
10
+ .input(v.object({
11
+ name: v.string(),
12
+ email: v.pipe(v.string(), v.email()),
13
+ password: v.string(),
14
+ rememberMe: v.optional(v.boolean(), true),
15
+ }))
16
+ .handler(async ({ input, context: { resHeaders, auth } }) => {
17
+ const { headers, response } = await auth.api.signUpEmail({
18
+ body: input,
19
+ returnHeaders: true,
20
+ })
21
+
22
+ headers.forEach((v, k) => resHeaders?.append(k, v))
23
+
24
+ return response.user
25
+ })
26
+
27
+ export const signIn = pub
28
+ .input(v.object({
29
+ email: v.pipe(v.string(), v.email()),
30
+ password: v.string(),
31
+ rememberMe: v.optional(v.boolean(), true),
32
+ }))
33
+ .handler(async ({ input, context: { resHeaders, auth } }) => {
34
+ const { headers, response } = await auth.api.signInEmail({
35
+ body: input,
36
+ returnHeaders: true,
37
+ })
38
+
39
+ headers.forEach((v, k) => resHeaders?.append(k, v))
40
+
41
+ return response.user
42
+ })
43
+
44
+ export const signOut = pub
45
+ .handler(async ({ context: { auth, reqHeaders, resHeaders } }) => {
46
+ const { headers, response } = await auth.api.signOut({
47
+ headers: reqHeaders ?? new Headers(),
48
+ returnHeaders: true,
49
+ })
50
+
51
+ headers.forEach((v, k) => resHeaders?.append(k, v))
52
+
53
+ return response
54
+ })
@@ -1,9 +1,9 @@
1
- import { welcome } from './welcome'
1
+ import * as auth from './auth'
2
2
 
3
3
  export type { RouterClient } from '@orpc/server'
4
4
 
5
5
  export const router = {
6
- welcome,
6
+ auth,
7
7
  }
8
8
 
9
9
  export type Router = typeof router
@@ -1,9 +1,7 @@
1
1
  import pino from 'pino'
2
2
 
3
- import { level } from './config/logger'
4
-
5
3
  export const logger = pino({
6
- level,
4
+ level: import.meta.env.LOG_LEVEL ?? 'info',
7
5
  base: {},
8
6
  })
9
7
 
@@ -9,26 +9,25 @@
9
9
  "preview": "vite preview --open"
10
10
  },
11
11
  "dependencies": {
12
- "@kevinmarrec/vue-i18n": "^1.0.0",
13
- "@orpc/client": "^1.8.6",
12
+ "@kevinmarrec/vue-i18n": "^1.1.0",
13
+ "@orpc/client": "^1.8.9",
14
14
  "@orpc/tanstack-query": "1.6.4",
15
- "@tanstack/vue-query": "^5.85.6",
16
- "@unhead/vue": "^2.0.14",
17
- "@vueuse/core": "^13.9.0",
18
- "unocss": "^66.5.0",
19
- "vue": "^3.5.20"
15
+ "@tanstack/vue-query": "^5.89.0",
16
+ "@unhead/vue": "^2.0.17",
17
+ "unocss": "^66.5.1",
18
+ "vue": "^3.5.21"
20
19
  },
21
20
  "devDependencies": {
22
- "@kevinmarrec/unocss-config": "^1.1.0",
23
- "@kevinmarrec/vite-plugin-dark-mode": "^1.0.0",
21
+ "@kevinmarrec/unocss-config": "^1.3.0",
22
+ "@kevinmarrec/vite-plugin-dark-mode": "^1.1.0",
24
23
  "@modyfi/vite-plugin-yaml": "^1.1.1",
25
- "@unhead/addons": "^2.0.14",
26
- "@unocss/vite": "^66.5.0",
24
+ "@unhead/addons": "^2.0.17",
25
+ "@unocss/vite": "^66.5.1",
27
26
  "@vitejs/plugin-vue": "^6.0.1",
28
27
  "beasties": "^0.3.5",
29
28
  "rollup-plugin-visualizer": "^6.0.3",
30
- "vite": "^7.1.4",
31
- "vite-plugin-vue-devtools": "^8.0.1",
29
+ "vite": "^7.1.6",
30
+ "vite-plugin-vue-devtools": "^8.0.2",
32
31
  "vite-ssg": "^28.1.0",
33
32
  "vite-tsconfig-paths": "^5.1.4"
34
33
  }
@@ -1,25 +1,111 @@
1
1
  <script setup lang="ts">
2
- import { useHead, useI18n } from '@frontend/composables'
3
- import { useWelcome } from '@frontend/queries'
2
+ import { useAuth, useHead, useI18n } from '@frontend/composables'
3
+ import { ref } from 'vue'
4
4
 
5
5
  const { t } = useI18n()
6
-
7
6
  useHead({
8
7
  title: () => t('title'),
9
8
  })
10
9
 
11
- const { data: message } = useWelcome('world')
10
+ const { user, signUp, signIn, signOut } = useAuth()
11
+
12
+ const email = ref('')
13
+ const password = ref('')
12
14
  </script>
13
15
 
14
16
  <template>
15
17
  <div class="grid h-full place-items-center">
16
18
  <div class="flex flex-col gap-4 items-center">
17
- <h1 class="text-4xl font-bold">
18
- {{ t('body.message') }}
19
- </h1>
20
- <h2 class="text-2xl text-gray-500">
21
- {{ message }}
22
- </h2>
19
+ <!-- Login Form (when user is not logged in) -->
20
+ <div v-if="!user" class="mx-auto max-w-md w-full">
21
+ <div class="p-8 rounded-lg bg-white shadow-lg dark:bg-gray-800">
22
+ <h2 class="text-2xl text-gray-900 font-bold mb-6 text-center dark:text-white">
23
+ Welcome Back
24
+ </h2>
25
+
26
+ <form class="space-y-4" @submit.prevent>
27
+ <div>
28
+ <label for="email" class="text-sm text-gray-700 font-medium mb-2 block dark:text-gray-300">
29
+ Email
30
+ </label>
31
+ <input
32
+ id="email"
33
+ v-model="email"
34
+ type="email"
35
+ class="px-3 py-2 border border-gray-300 rounded-md w-full shadow-sm dark:text-white focus:outline-none dark:border-gray-600 focus:border-blue-500 dark:bg-gray-700 focus:ring-2 focus:ring-blue-500"
36
+ placeholder="Enter your email"
37
+ required
38
+ >
39
+ </div>
40
+
41
+ <div>
42
+ <label for="password" class="text-sm text-gray-700 font-medium mb-2 block dark:text-gray-300">
43
+ Password
44
+ </label>
45
+ <input
46
+ id="password"
47
+ v-model="password"
48
+ type="password"
49
+ class="px-3 py-2 border border-gray-300 rounded-md w-full shadow-sm dark:text-white focus:outline-none dark:border-gray-600 focus:border-blue-500 dark:bg-gray-700 focus:ring-2 focus:ring-blue-500"
50
+ placeholder="Enter your password"
51
+ required
52
+ >
53
+ </div>
54
+
55
+ <div v-if="signIn.error.value || signUp.error.value" class="text-sm text-red-600 dark:text-red-400">
56
+ {{ signIn.error.value || signUp.error.value }}
57
+ </div>
58
+
59
+ <div class="flex space-x-3">
60
+ <button
61
+ :disabled="signIn.isPending.value"
62
+ class="text-white font-medium px-4 py-2 rounded-md bg-blue-600 flex-1 transition-colors duration-200 disabled:bg-blue-400 hover:bg-blue-700"
63
+ @click="signIn.mutate({ email, password })"
64
+ >
65
+ <span v-if="signIn.isPending.value">Signing In...</span>
66
+ <span v-else>Sign In</span>
67
+ </button>
68
+
69
+ <button
70
+ :disabled="signUp.isPending.value"
71
+ class="text-white font-medium px-4 py-2 rounded-md bg-green-600 flex-1 transition-colors duration-200 disabled:bg-green-400 hover:bg-green-700"
72
+ @click="signUp.mutate({ email, password, name: 'User' })"
73
+ >
74
+ <span v-if="signUp.isPending.value">Signing Up...</span>
75
+ <span v-else>Sign Up</span>
76
+ </button>
77
+ </div>
78
+ </form>
79
+ </div>
80
+ </div>
81
+
82
+ <!-- User Dashboard (when user is logged in) -->
83
+ <div v-else class="mx-auto max-w-md w-full">
84
+ <div class="p-8 text-center rounded-lg bg-white shadow-lg dark:bg-gray-800">
85
+ <div class="mb-6">
86
+ <div class="mx-auto mb-4 rounded-full flex h-20 w-20 items-center justify-center from-blue-500 to-purple-600 bg-gradient-to-r">
87
+ <span class="text-2xl text-white font-bold">
88
+ {{ user.name?.charAt(0).toUpperCase() || 'U' }}
89
+ </span>
90
+ </div>
91
+ <h2 class="text-2xl text-gray-900 font-bold dark:text-white">
92
+ Welcome, {{ user.name || 'User' }}!
93
+ </h2>
94
+ <p class="text-gray-600 mt-2 dark:text-gray-400">
95
+ {{ user.email }}
96
+ </p>
97
+ </div>
98
+
99
+ <button
100
+ :disabled="signOut.isPending.value"
101
+ class="text-white font-medium px-4 py-2 rounded-md bg-red-600 w-full transition-colors duration-200 disabled:bg-red-400 hover:bg-red-700"
102
+ @click="signOut.mutate({})"
103
+ >
104
+ <span v-if="signOut.isPending.value">Signing Out...</span>
105
+ <span v-else>Sign Out</span>
106
+ </button>
107
+ </div>
108
+ </div>
23
109
  </div>
24
110
  </div>
25
111
  </template>
@@ -0,0 +1,31 @@
1
+ import { orpc } from '@frontend/lib/orpc'
2
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
3
+
4
+ export function useAuth() {
5
+ const qc = useQueryClient()
6
+
7
+ const { data: user } = useQuery(orpc.auth.getCurrentUser.queryOptions({
8
+ retry: false,
9
+ }))
10
+
11
+ const signUp = useMutation(orpc.auth.signUp.mutationOptions({
12
+ onSuccess: () => qc.invalidateQueries({ queryKey: orpc.auth.getCurrentUser.queryKey() }),
13
+ }))
14
+
15
+ const signIn = useMutation(orpc.auth.signIn.mutationOptions({
16
+ onSuccess: () => qc.invalidateQueries({ queryKey: orpc.auth.getCurrentUser.queryKey() }),
17
+ }))
18
+
19
+ const signOut = useMutation(orpc.auth.signOut.mutationOptions({
20
+ onSuccess: () => {
21
+ qc.setQueryData<null>(orpc.auth.getCurrentUser.queryKey(), null)
22
+ },
23
+ }))
24
+
25
+ return {
26
+ user,
27
+ signUp,
28
+ signIn,
29
+ signOut,
30
+ }
31
+ }
@@ -1,2 +1,3 @@
1
+ export { useAuth } from './auth'
1
2
  export { useI18n } from '@kevinmarrec/vue-i18n'
2
3
  export { useHead } from '@unhead/vue'
@@ -1,10 +1,15 @@
1
- import type { Router, RouterClient } from '@backend/router'
1
+ import type { Router, RouterClient } from '@backend/orpc/router'
2
2
  import { createORPCClient } from '@orpc/client'
3
3
  import { RPCLink } from '@orpc/client/fetch'
4
4
  import { createTanstackQueryUtils } from '@orpc/tanstack-query'
5
5
 
6
6
  const link = new RPCLink({
7
7
  url: import.meta.env.VITE_API_URL,
8
+ fetch: (request, init) =>
9
+ globalThis.fetch(request, {
10
+ ...init,
11
+ credentials: 'include',
12
+ }),
8
13
  })
9
14
 
10
15
  const client: RouterClient<Router> = createORPCClient(link)
@@ -12,7 +12,7 @@ export default {
12
12
  },
13
13
  'backend': {
14
14
  drizzle: {
15
- config: ['src/config/drizzle.ts'],
15
+ config: 'src/database/drizzle/config.ts',
16
16
  },
17
17
  ignoreDependencies: ['pino-pretty'],
18
18
  },
@@ -2,7 +2,7 @@
2
2
  "name": "app",
3
3
  "type": "module",
4
4
  "private": true,
5
- "packageManager": "bun@1.2.21",
5
+ "packageManager": "bun@1.2.22",
6
6
  "engines": {
7
7
  "node": "lts/*"
8
8
  },
@@ -21,15 +21,15 @@
21
21
  "lint:inspect": "bunx @eslint/config-inspector"
22
22
  },
23
23
  "devDependencies": {
24
- "@kevinmarrec/eslint-config": "^1.1.0",
25
- "@kevinmarrec/stylelint-config": "^1.0.0",
26
- "@kevinmarrec/tsconfig": "^1.0.0",
24
+ "@kevinmarrec/eslint-config": "^1.5.0",
25
+ "@kevinmarrec/stylelint-config": "^1.2.0",
26
+ "@kevinmarrec/tsconfig": "^1.1.0",
27
27
  "concurrently": "^9.2.1",
28
- "eslint": "^9.34.0",
29
- "knip": "^5.63.0",
30
- "stylelint": "^16.23.1",
31
- "taze": "^19.4.0",
28
+ "eslint": "^9.36.0",
29
+ "knip": "^5.63.1",
30
+ "stylelint": "^16.24.0",
31
+ "taze": "^19.7.0",
32
32
  "typescript": "~5.9.2",
33
- "vue-tsc": "^3.0.6"
33
+ "vue-tsc": "^3.0.7"
34
34
  }
35
35
  }
@@ -1,11 +0,0 @@
1
- import * as v from 'valibot'
2
-
3
- const schema = v.object({
4
- url: v.string(),
5
- })
6
-
7
- const config = v.parse(schema, {
8
- url: import.meta.env.DATABASE_URL,
9
- })
10
-
11
- export const url = config.url
@@ -1,10 +0,0 @@
1
- import { defineConfig } from 'drizzle-kit'
2
-
3
- import { url } from './database'
4
-
5
- export default defineConfig({
6
- dialect: 'sqlite',
7
- dbCredentials: { url },
8
- schema: './src/database/schema',
9
- out: './src/database/migrations',
10
- })
@@ -1,19 +0,0 @@
1
- import * as v from 'valibot'
2
-
3
- const schema = v.object({
4
- level: v.optional(v.union([
5
- v.literal('trace'),
6
- v.literal('debug'),
7
- v.literal('info'),
8
- v.literal('warn'),
9
- v.literal('error'),
10
- v.literal('fatal'),
11
- v.literal('silent'),
12
- ]), 'info'),
13
- })
14
-
15
- const config = v.parse(schema, {
16
- level: import.meta.env.LOG_LEVEL,
17
- })
18
-
19
- export const level = config.level
@@ -1,40 +0,0 @@
1
- import * as v from 'valibot'
2
-
3
- const schema = v.object({
4
- cors: v.object({
5
- origin: v.optional(
6
- v.pipe(
7
- v.string(),
8
- v.minLength(1),
9
- v.transform(value => value.split(',')),
10
- ),
11
- '*',
12
- ),
13
- }),
14
- hostname: v.optional(
15
- v.pipe(
16
- v.string(),
17
- v.minLength(1),
18
- ),
19
- 'localhost',
20
- ),
21
- port: v.optional(v.pipe(
22
- v.string(),
23
- v.transform(value => +value),
24
- v.number(),
25
- v.minValue(3000),
26
- v.maxValue(65535),
27
- ), '4000'),
28
- })
29
-
30
- const config = v.parse(schema, {
31
- cors: {
32
- origin: import.meta.env.ALLOWED_ORIGINS,
33
- },
34
- hostname: import.meta.env.HOST,
35
- port: import.meta.env.PORT,
36
- })
37
-
38
- export const cors = config.cors
39
- export const hostname = config.hostname
40
- export const port = config.port
@@ -1,6 +0,0 @@
1
- CREATE TABLE `users` (
2
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3
- `name` text NOT NULL,
4
- `createdAt` integer NOT NULL,
5
- `updatedAt` integer NOT NULL
6
- );
@@ -1,10 +0,0 @@
1
- import type { Database } from '@backend/database'
2
- import type { Logger } from '@backend/logger'
3
- import { os } from '@orpc/server'
4
-
5
- interface Context {
6
- db: Database
7
- logger: Logger
8
- }
9
-
10
- export const pub = os.$context<Context>()
@@ -1,11 +0,0 @@
1
- import { users } from '@backend/database/schema'
2
- import { pub } from '@backend/lib/orpc'
3
- import * as v from 'valibot'
4
-
5
- export const welcome = pub
6
- .input(v.string())
7
- .output(v.string())
8
- .handler(async ({ input, context: { db } }) => {
9
- const count = await db.$count(users)
10
- return `Hello ${input} (${count})`
11
- })
@@ -1 +0,0 @@
1
- export * from './welcome'
@@ -1,10 +0,0 @@
1
- import { orpc } from '@frontend/lib/orpc'
2
- import { get } from '@frontend/utils'
3
- import { useQuery } from '@tanstack/vue-query'
4
- import { computed, type MaybeRef } from 'vue'
5
-
6
- export function useWelcome(text: MaybeRef<string>) {
7
- return useQuery(computed(() => orpc.welcome.queryOptions({
8
- input: get(text),
9
- })))
10
- }
@@ -1 +0,0 @@
1
- export { get } from '@vueuse/core'
File without changes