@kevinmarrec/create-app 0.7.0 → 0.9.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 (65) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +1 -3
  3. package/package.json +21 -19
  4. package/template/.docker/traefik/dynamic/tls.yml +4 -0
  5. package/template/.github/scripts/build-stats.md.ts +71 -0
  6. package/template/.github/workflows/ci.yml +19 -3
  7. package/template/{gitignore → .gitignore} +2 -0
  8. package/template/{npmrc → .npmrc} +0 -1
  9. package/template/.vscode/settings.json +1 -1
  10. package/template/api/.env.development +4 -0
  11. package/template/{backend → api}/package.json +9 -9
  12. package/template/{backend → api}/src/auth/index.ts +2 -0
  13. package/template/{backend → api}/src/database/index.ts +1 -1
  14. package/template/{backend → api}/src/env.d.ts +2 -2
  15. package/template/{backend → api}/src/main.ts +10 -20
  16. package/template/api/src/orpc/context.ts +11 -0
  17. package/template/api/src/orpc/index.ts +27 -0
  18. package/template/api/src/orpc/middlewares/auth.ts +24 -0
  19. package/template/api/src/orpc/plugins/error.ts +17 -0
  20. package/template/api/src/orpc/router/index.ts +31 -0
  21. package/template/api/src/utils/cors.ts +26 -0
  22. package/template/app/.env +1 -0
  23. package/template/{frontend → app}/package.json +13 -12
  24. package/template/{frontend → app}/src/App.vue +10 -2
  25. package/template/app/src/composables/auth.ts +39 -0
  26. package/template/app/src/composables/content.ts +13 -0
  27. package/template/{frontend → app}/src/composables/index.ts +1 -0
  28. package/template/{frontend → app}/src/lib/orpc.ts +3 -2
  29. package/template/{frontend → app}/src/main.ts +1 -1
  30. package/template/{frontend → app}/vite.config.ts +1 -1
  31. package/template/compose.yaml +65 -12
  32. package/template/docs/local-tls.md +152 -0
  33. package/template/knip.config.ts +5 -4
  34. package/template/package.json +14 -13
  35. package/template/tsconfig.json +2 -2
  36. package/template/backend/.env.development +0 -4
  37. package/template/backend/src/orpc/index.ts +0 -65
  38. package/template/backend/src/orpc/middlewares/auth.ts +0 -33
  39. package/template/backend/src/orpc/middlewares/index.ts +0 -1
  40. package/template/backend/src/orpc/router/auth.ts +0 -54
  41. package/template/backend/src/orpc/router/index.ts +0 -9
  42. package/template/frontend/.env.development +0 -1
  43. package/template/frontend/src/composables/auth.ts +0 -31
  44. package/template/scripts/dev.ts +0 -8
  45. /package/template/{backend → api}/src/database/drizzle/config.ts +0 -0
  46. /package/template/{backend → api}/src/database/migrations/0000_init.sql +0 -0
  47. /package/template/{backend → api}/src/database/migrations/meta/0000_snapshot.json +0 -0
  48. /package/template/{backend → api}/src/database/migrations/meta/_journal.json +0 -0
  49. /package/template/{backend → api}/src/database/schema/accounts.ts +0 -0
  50. /package/template/{backend → api}/src/database/schema/index.ts +0 -0
  51. /package/template/{backend → api}/src/database/schema/sessions.ts +0 -0
  52. /package/template/{backend → api}/src/database/schema/users.ts +0 -0
  53. /package/template/{backend → api}/src/database/schema/verifications.ts +0 -0
  54. /package/template/{backend → api}/src/utils/logger.ts +0 -0
  55. /package/template/{backend → api}/tsconfig.json +0 -0
  56. /package/template/{frontend → app}/index.html +0 -0
  57. /package/template/{frontend → app}/public/favicon.svg +0 -0
  58. /package/template/{frontend → app}/public/robots.txt +0 -0
  59. /package/template/{frontend → app}/src/components/.gitkeep +0 -0
  60. /package/template/{frontend → app}/src/env.d.ts +0 -0
  61. /package/template/{frontend → app}/src/locales/en.yml +0 -0
  62. /package/template/{frontend → app}/src/locales/fr.yml +0 -0
  63. /package/template/{frontend → app}/tsconfig.json +0 -0
  64. /package/template/{frontend → app}/uno.config.ts +0 -0
  65. /package/template/{frontend → app}/wrangler.json +0 -0
package/README.md CHANGED
@@ -12,8 +12,10 @@ CLI that scaffolds an opinionated [Bun](https://bun.sh) & [Vue](https://vuejs.or
12
12
 
13
13
  ## Usage
14
14
 
15
- > Requires [Bun](https://bun.sh) v1.2 _or later_.
15
+ > Requires [Bun](https://bun.sh) v1.3 _or later_.
16
16
 
17
17
  ```sh
18
18
  bun create @kevinmarrec/app
19
+ # OR
20
+ bunx @kevinmarrec/create-app
19
21
  ```
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import { x } from "tinyexec";
8
8
  import fs from "node:fs/promises";
9
9
 
10
10
  //#region package.json
11
- var version = "0.7.0";
11
+ var version = "0.9.0";
12
12
 
13
13
  //#endregion
14
14
  //#region src/utils/fs.ts
@@ -35,8 +35,6 @@ var fs_default = {
35
35
  async function scaffold(root) {
36
36
  await fs_default.exists(root) ? await fs_default.empty(root) : await fs_default.mkdir(root);
37
37
  await fs_default.cp(join(import.meta.dirname, "../template"), root, { recursive: true });
38
- await fs_default.rename(join(root, "gitignore"), join(root, ".gitignore"));
39
- await fs_default.rename(join(root, "npmrc"), join(root, ".npmrc"));
40
38
  }
41
39
 
42
40
  //#endregion
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@kevinmarrec/create-app",
3
3
  "type": "module",
4
- "version": "0.7.0",
5
- "packageManager": "bun@1.3.1",
4
+ "version": "0.9.0",
5
+ "packageManager": "bun@1.3.2",
6
6
  "description": "CLI that scaffolds an opinionated Bun & Vue fullstack application.",
7
7
  "author": "Kevin Marrec <kevin@marrec.io>",
8
8
  "license": "MIT",
@@ -18,26 +18,28 @@
18
18
  ],
19
19
  "workspaces": [
20
20
  "template",
21
- "template/backend",
22
- "template/frontend"
21
+ "template/api",
22
+ "template/app"
23
23
  ],
24
24
  "bin": {
25
25
  "create-app": "dist/index.js"
26
26
  },
27
27
  "files": [
28
28
  "dist",
29
- "template"
29
+ "template",
30
+ "template/.gitignore",
31
+ "template/.npmrc"
30
32
  ],
31
33
  "scripts": {
32
- "build": "tsdown",
33
- "check": "bun run check:unused && bun run check:eslint && bun run check:stylelint && bun run check:types",
34
+ "build": "tsdown --no-fixed-extension",
35
+ "check": "bun run check:eslint && bun run check:stylelint && bun run check:unused && bun run check:types",
34
36
  "check:eslint": "eslint . --cache",
35
37
  "check:stylelint": "stylelint '**/*.{css,scss,vue}' --ignorePath .gitignore --cache",
36
38
  "check:types": "vue-tsc --noEmit",
37
39
  "check:unused": "knip -n",
38
40
  "lint": "bun run check:eslint && bun run check:stylelint",
39
41
  "lint:inspect": "bunx @eslint/config-inspector",
40
- "playground": "bun --cwd template dev",
42
+ "playground": "bun run scripts/transform-compose.ts | docker compose -f -",
41
43
  "release": "bumpp",
42
44
  "update": "bunx taze -I -rwi",
43
45
  "test": "vitest",
@@ -47,22 +49,22 @@
47
49
  "@clack/prompts": "^0.11.0",
48
50
  "ansis": "^4.2.0",
49
51
  "pathe": "^2.0.3",
50
- "tinyexec": "^1.0.1"
52
+ "tinyexec": "^1.0.2"
51
53
  },
52
54
  "devDependencies": {
53
55
  "@faker-js/faker": "^10.1.0",
54
- "@kevinmarrec/eslint-config": "^1.5.3",
55
- "@kevinmarrec/stylelint-config": "^1.2.2",
56
- "@kevinmarrec/tsconfig": "^1.1.0",
57
- "@types/bun": "^1.3.0",
58
- "@vitest/coverage-v8": "^4.0.1",
56
+ "@kevinmarrec/eslint-config": "^1.5.6",
57
+ "@kevinmarrec/stylelint-config": "^1.5.6",
58
+ "@kevinmarrec/tsconfig": "^1.5.6",
59
+ "@types/bun": "^1.3.2",
60
+ "@vitest/coverage-v8": "^4.0.9",
59
61
  "bumpp": "^10.3.1",
60
- "eslint": "^9.38.0",
61
- "knip": "^5.66.0",
62
+ "eslint": "^9.39.1",
63
+ "knip": "^5.69.1",
62
64
  "stylelint": "^16.25.0",
63
- "tsdown": "^0.15.7",
65
+ "tsdown": "^0.16.4",
64
66
  "typescript": "^5.9.3",
65
- "vitest": "^4.0.1",
66
- "vue-tsc": "^3.1.1"
67
+ "vitest": "^4.0.9",
68
+ "vue-tsc": "^3.1.3"
67
69
  }
68
70
  }
@@ -0,0 +1,4 @@
1
+ tls:
2
+ certificates:
3
+ - certFile: /certs/dev.localhost.crt
4
+ keyFile: /certs/dev.localhost.key
@@ -0,0 +1,71 @@
1
+ import * as fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+ import { parseArgs } from 'node:util'
5
+
6
+ import { filesize } from 'filesize'
7
+ import { x } from 'tinyexec'
8
+ import { glob } from 'tinyglobby'
9
+
10
+ interface FileStats {
11
+ file: string
12
+ size: number
13
+ }
14
+
15
+ async function getFileStats(directory: string) {
16
+ const fileStats: FileStats[] = []
17
+
18
+ for (const file of await glob(['**/*'], { cwd: directory })) {
19
+ const size = fs.statSync(path.join(directory, file)).size
20
+ fileStats.push({
21
+ file,
22
+ size,
23
+ })
24
+ }
25
+
26
+ fileStats.sort((a, b) => {
27
+ // Sort so that files starting with 'assets/' come first
28
+ if (a.file.startsWith('assets/') && !b.file.startsWith('assets/')) return -1
29
+ if (!a.file.startsWith('assets/') && b.file.startsWith('assets/')) return 1
30
+
31
+ // Within that, files ending with '.js' come first
32
+ if (a.file.endsWith('.js') && !b.file.endsWith('.js')) return -1
33
+ if (!a.file.endsWith('.js') && b.file.endsWith('.js')) return 1
34
+
35
+ // Otherwise sort alphabetically
36
+ return a.file.localeCompare(b.file)
37
+ })
38
+
39
+ return fileStats
40
+ }
41
+
42
+ async function generateFileStatsMarkdown(directory: string) {
43
+ const fileStats = await getFileStats(directory)
44
+ return [
45
+ '| File | Size |',
46
+ '| :--- | ---: |',
47
+ ...fileStats.map(file => `| ${file.file} | ${filesize(file.size)} |`),
48
+ ].join('\n')
49
+ }
50
+
51
+ async function main() {
52
+ const { positionals: [directory] } = parseArgs({
53
+ args: process.argv.slice(2),
54
+ allowPositionals: true,
55
+ })
56
+
57
+ if (!directory) {
58
+ process.stdout.write('Usage: analyze <directory>\n')
59
+ process.exit(1)
60
+ }
61
+
62
+ await x('bun', ['run', 'build'], { nodeOptions: { cwd: directory } })
63
+
64
+ const markdownTable = await generateFileStatsMarkdown(path.join(directory, 'dist'))
65
+ process.stdout.write(`${markdownTable}\n`)
66
+ }
67
+
68
+ main().catch((error) => {
69
+ console.error(error)
70
+ process.exit(1)
71
+ })
@@ -16,11 +16,27 @@ jobs:
16
16
  - name: Setup Bun
17
17
  uses: kevinmarrec/workflows/setup-bun@v1
18
18
 
19
+ - name: Lint
20
+ run: bun run lint
21
+
19
22
  - name: Check unused files, dependencies, and exports
20
23
  run: bun run check:unused
21
24
 
22
- - name: Lint
23
- run: bun run check:eslint && bun run check:stylelint
24
-
25
25
  - name: Type check
26
26
  run: bun run check:types
27
+ project-size:
28
+ name: Analyze project build size
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - name: Setup Bun
32
+ uses: kevinmarrec/workflows/setup-bun@v1
33
+
34
+ - name: Check api build size
35
+ run: |
36
+ echo "## API" >> $GITHUB_STEP_SUMMARY
37
+ bun run .github/scripts/build-stats.md.ts api >> $GITHUB_STEP_SUMMARY
38
+
39
+ - name: Check app build size
40
+ run: |
41
+ echo "## App" >> $GITHUB_STEP_SUMMARY
42
+ bun run .github/scripts/build-stats.md.ts app >> $GITHUB_STEP_SUMMARY
@@ -1,4 +1,6 @@
1
+ *.crt
1
2
  *.db*
3
+ *.key
2
4
  *.local
3
5
  *.log
4
6
  *.sqlite
@@ -1,4 +1,3 @@
1
1
  public-hoist-pattern[]=@types*
2
2
  public-hoist-pattern[]=*eslint*
3
3
  public-hoist-pattern[]=*stylelint*
4
- public-hoist-pattern[]=pino-pretty
@@ -67,6 +67,6 @@
67
67
  "i18n-ally.enabledFrameworks": ["vue"],
68
68
  "i18n-ally.keystyle": "nested",
69
69
  "i18n-ally.localesPaths": [
70
- "frontend/src/locales"
70
+ "app/src/locales"
71
71
  ]
72
72
  }
@@ -0,0 +1,4 @@
1
+ ALLOWED_ORIGINS=https://app.dev.localhost
2
+ AUTH_SECRET=foo
3
+ DATABASE_URL=.db
4
+ NODE_ENV=development
@@ -1,24 +1,24 @@
1
1
  {
2
- "name": "backend",
2
+ "name": "api",
3
3
  "type": "module",
4
4
  "private": true,
5
5
  "scripts": {
6
- "dev": "bun --watch --no-clear-screen src/main.ts",
7
- "build": "bun build src/main.ts --compile --minify --sourcemap --outfile dist/server",
6
+ "dev": "bun --watch --no-clear-screen src/main.ts | pino-pretty",
7
+ "build": "bun build src/main.ts --compile --minify --sourcemap --outfile dist/api",
8
8
  "db:generate": "bun --bun run drizzle-kit generate --config src/database/drizzle/config.ts",
9
9
  "db:migrate": "bun --bun run drizzle-kit migrate --config src/database/drizzle/config.ts"
10
10
  },
11
11
  "dependencies": {
12
- "@orpc/server": "^1.9.4",
13
- "better-auth": "^1.3.27",
14
- "drizzle-orm": "^0.44.6",
12
+ "@orpc/server": "^1.11.2",
13
+ "better-auth": "^1.3.34",
14
+ "drizzle-orm": "^0.44.7",
15
15
  "pino": "^10.1.0",
16
16
  "valibot": "^1.1.0"
17
17
  },
18
18
  "devDependencies": {
19
- "@electric-sql/pglite": "^0.3.11",
20
- "@types/bun": "^1.3.0",
21
- "drizzle-kit": "^0.31.5",
19
+ "@electric-sql/pglite": "^0.3.14",
20
+ "@types/bun": "^1.3.2",
21
+ "drizzle-kit": "^0.31.7",
22
22
  "pino-pretty": "^13.1.2"
23
23
  }
24
24
  }
@@ -4,6 +4,8 @@ import type { BaseLogger } from 'pino'
4
4
 
5
5
  export function createBetterAuth({ db, logger }: { db: DB, logger: BaseLogger }) {
6
6
  return betterAuth({
7
+ basePath: '/auth',
8
+ trustedOrigins: import.meta.env.ALLOWED_ORIGINS.split(','),
7
9
  database: drizzleAdapter(db, {
8
10
  provider: 'pg',
9
11
  usePlural: true,
@@ -1,7 +1,7 @@
1
- import { logger } from '@backend/utils/logger'
2
1
  import { PGlite } from '@electric-sql/pglite'
3
2
  import { drizzle } from 'drizzle-orm/pglite'
4
3
 
4
+ import { logger } from '../utils/logger'
5
5
  import * as schema from './schema'
6
6
 
7
7
  export const db = drizzle({
@@ -1,6 +1,6 @@
1
1
  interface ImportMetaEnv {
2
- readonly BETTER_AUTH_SECRET: string
3
- readonly BETTER_AUTH_URL: string
2
+ readonly ALLOWED_ORIGINS: string
3
+ readonly AUTH_SECRET: string
4
4
  readonly DATABASE_URL: string
5
5
  readonly LOG_LEVEL: string
6
6
  readonly NODE_ENV: string
@@ -1,31 +1,21 @@
1
1
  import process from 'node:process'
2
2
 
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'
3
+ import { createBetterAuth } from './auth'
4
+ import { db } from './database'
5
+ import { createRpc } from './orpc'
6
+ import { router } from './orpc/router'
7
+ import { cors } from './utils/cors'
8
+ import { logger } from './utils/logger'
8
9
 
9
10
  const auth = createBetterAuth({ db, logger })
10
- const rpcHandler = createRpcHandler(router)
11
+ const rpc = createRpc({ auth, db, logger }, router)
11
12
 
12
13
  const server = Bun.serve({
13
14
  hostname: import.meta.env.HOST ?? '0.0.0.0',
14
15
  port: import.meta.env.PORT ?? 4000,
15
- async fetch(request) {
16
- const { matched, response } = await rpcHandler.handle(request, {
17
- prefix: '/rpc',
18
- context: {
19
- auth,
20
- db,
21
- logger,
22
- },
23
- })
24
-
25
- if (matched)
26
- return response
27
-
28
- return new Response('Not found', { status: 404 })
16
+ routes: {
17
+ '/auth/*': cors(auth.handler),
18
+ '/rpc/*': cors(rpc.handler),
29
19
  },
30
20
  error(error) {
31
21
  logger.error(error)
@@ -0,0 +1,11 @@
1
+ import type { RequestHeadersPluginContext, ResponseHeadersPluginContext } from '@orpc/server/plugins'
2
+
3
+ import type { Auth } from '../auth'
4
+ import type { Database } from '../database'
5
+ import type { Logger } from '../utils/logger'
6
+
7
+ export interface Context extends RequestHeadersPluginContext, ResponseHeadersPluginContext {
8
+ auth: Auth
9
+ db: Database
10
+ logger: Logger
11
+ }
@@ -0,0 +1,27 @@
1
+ import type { Router } from '@orpc/server'
2
+ import { RPCHandler } from '@orpc/server/fetch'
3
+ import { RequestHeadersPlugin, ResponseHeadersPlugin } from '@orpc/server/plugins'
4
+
5
+ import type { Context } from './context'
6
+ import { ErrorPlugin } from './plugins/error'
7
+
8
+ export function createRpc<T extends Context>(context: T, router: Router<any, T>) {
9
+ const handler = new RPCHandler<T>(router, {
10
+ plugins: [
11
+ new ErrorPlugin(),
12
+ new RequestHeadersPlugin(),
13
+ new ResponseHeadersPlugin(),
14
+ ],
15
+ })
16
+
17
+ return {
18
+ handler: async (req: Request) => {
19
+ const { matched, response } = await handler.handle(req, {
20
+ prefix: '/rpc',
21
+ context,
22
+ })
23
+
24
+ return matched ? response : new Response('Not found', { status: 404 })
25
+ },
26
+ }
27
+ }
@@ -0,0 +1,24 @@
1
+ import { ORPCError, os } from '@orpc/server'
2
+
3
+ import type { Context } from '../context'
4
+
5
+ export const authMiddleware = os
6
+ .$context<Context>()
7
+ .middleware(async ({ context, next }) => {
8
+ const { response: session, headers } = await context.auth.api.getSession({
9
+ headers: context.reqHeaders,
10
+ returnHeaders: true,
11
+ })
12
+
13
+ headers.forEach((v, k) => context.resHeaders?.append(k, v))
14
+
15
+ if (!session) {
16
+ throw new ORPCError('UNAUTHORIZED')
17
+ }
18
+
19
+ return next({
20
+ context: {
21
+ user: session.user,
22
+ },
23
+ })
24
+ })
@@ -0,0 +1,17 @@
1
+ import { onError, ORPCError } from '@orpc/server'
2
+ import type { StandardHandlerOptions, StandardHandlerPlugin } from '@orpc/server/standard'
3
+
4
+ import type { Context } from '../context'
5
+
6
+ export class ErrorPlugin<T extends Context> implements StandardHandlerPlugin<T> {
7
+ init(options: StandardHandlerOptions<T>): void {
8
+ options.clientInterceptors ??= []
9
+ options.clientInterceptors.unshift(onError((error, { context }) => {
10
+ if (error instanceof ORPCError) {
11
+ throw error
12
+ }
13
+
14
+ context.logger.error(error)
15
+ }))
16
+ }
17
+ }
@@ -0,0 +1,31 @@
1
+ import { os } from '@orpc/server'
2
+ import * as v from 'valibot'
3
+
4
+ import type { Context } from '../context'
5
+ import { authMiddleware } from '../middlewares/auth'
6
+
7
+ export type { RouterClient } from '@orpc/server'
8
+
9
+ const pub = os
10
+ .$context<Context>()
11
+ .errors({
12
+ UNAUTHORIZED: { status: 401 },
13
+ })
14
+
15
+ const authed = pub
16
+ .use(authMiddleware)
17
+
18
+ export const router = {
19
+ public: pub
20
+ .input(v.any())
21
+ .handler(async () => {
22
+ return 'public'
23
+ }),
24
+ private: authed
25
+ .input(v.any())
26
+ .handler(async () => {
27
+ return 'private'
28
+ }),
29
+ }
30
+
31
+ export type Router = typeof router
@@ -0,0 +1,26 @@
1
+ export function cors(handler: (req: Request) => Promise<Response>) {
2
+ const allowedOrigins = import.meta.env.ALLOWED_ORIGINS.split(',')
3
+
4
+ return async (req: Request) => {
5
+ const origin = req.headers.get('origin') ?? ''
6
+
7
+ if (!origin || !allowedOrigins.includes(origin)) {
8
+ return new Response('Origin not allowed', { status: 403 })
9
+ }
10
+
11
+ const response = req.method === 'OPTIONS'
12
+ ? new Response(undefined, { status: 204 })
13
+ : await handler(req)
14
+
15
+ if (req.method === 'OPTIONS') {
16
+ response.headers.append('Access-Control-Allow-Headers', 'Content-Type')
17
+ response.headers.append('Access-Control-Allow-Methods', 'GET, HEAD, PUT, POST, DELETE, PATCH')
18
+ response.headers.append('Access-Control-Max-Age', '7200') // 2 hours https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Max-Age
19
+ }
20
+
21
+ response.headers.append('Access-Control-Allow-Credentials', 'true')
22
+ response.headers.append('Access-Control-Allow-Origin', origin)
23
+
24
+ return response
25
+ }
26
+ }
@@ -0,0 +1 @@
1
+ VITE_API_URL=https://api.dev.localhost
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "frontend",
2
+ "name": "app",
3
3
  "type": "module",
4
4
  "private": true,
5
5
  "scripts": {
@@ -9,27 +9,28 @@
9
9
  "preview": "vite preview --open"
10
10
  },
11
11
  "dependencies": {
12
- "@kevinmarrec/vue-i18n": "^1.1.2",
13
- "@orpc/client": "^1.9.4",
14
- "@orpc/tanstack-query": "1.9.4",
15
- "@tanstack/query-core": "^5.90.5",
16
- "@tanstack/vue-query": "^5.90.5",
12
+ "@kevinmarrec/vue-i18n": "^1.1.3",
13
+ "@orpc/client": "^1.11.2",
14
+ "@orpc/tanstack-query": "1.11.2",
15
+ "@tanstack/query-core": "^5.90.9",
16
+ "@tanstack/vue-query": "^5.91.1",
17
17
  "@unhead/vue": "^2.0.19",
18
- "unocss": "^66.5.4",
19
- "vue": "^3.5.22"
18
+ "@vueuse/core": "^14.0.0",
19
+ "better-auth": "^1.3.34",
20
+ "unocss": "^66.5.6",
21
+ "vue": "^3.5.24"
20
22
  },
21
23
  "devDependencies": {
22
- "@kevinmarrec/unocss-config": "^1.3.3",
24
+ "@kevinmarrec/unocss-config": "^1.5.6",
23
25
  "@kevinmarrec/vite-plugin-dark-mode": "^1.1.2",
24
26
  "@modyfi/vite-plugin-yaml": "^1.1.1",
25
27
  "@unhead/addons": "^2.0.19",
26
- "@unocss/vite": "^66.5.4",
27
28
  "@vitejs/plugin-vue": "^6.0.1",
28
29
  "beasties": "^0.3.5",
29
30
  "rollup-plugin-visualizer": "^6.0.5",
30
- "vite": "^7.1.10",
31
+ "vite": "^7.2.2",
31
32
  "vite-plugin-vue-devtools": "^8.0.3",
32
- "vite-ssg": "^28.2.1",
33
+ "vite-ssg": "^28.2.2",
33
34
  "vite-tsconfig-paths": "^5.1.4"
34
35
  }
35
36
  }
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
- import { useAuth, useHead, useI18n } from '@frontend/composables'
3
2
  import { ref } from 'vue'
4
3
 
4
+ import { useAuth, useContent, useHead, useI18n } from '~/app/composables'
5
+
5
6
  const { t } = useI18n()
6
7
  useHead({
7
8
  title: () => t('title'),
@@ -12,7 +13,8 @@ useHead({
12
13
  }],
13
14
  })
14
15
 
15
- const { user, signUp, signIn, signOut } = useAuth()
16
+ const { user, signIn, signUp, signOut } = useAuth()
17
+ const { publicContent, privateContent } = useContent()
16
18
 
17
19
  const email = ref('')
18
20
  const password = ref('')
@@ -99,6 +101,12 @@ const password = ref('')
99
101
  <p class="text-gray-600 mt-2 dark:text-gray-400">
100
102
  {{ user.email }}
101
103
  </p>
104
+ <p class="text-gray-600 mt-2 dark:text-gray-400">
105
+ {{ publicContent }}
106
+ </p>
107
+ <p class="text-gray-600 mt-2 dark:text-gray-400">
108
+ {{ privateContent }}
109
+ </p>
102
110
  </div>
103
111
 
104
112
  <button
@@ -0,0 +1,39 @@
1
+ import { useMutation } from '@tanstack/vue-query'
2
+ import { createAuthClient } from 'better-auth/vue'
3
+ import { computed } from 'vue'
4
+
5
+ const authClient = createAuthClient({
6
+ baseURL: `${import.meta.env.VITE_API_URL}/auth`,
7
+ fetchOptions: {
8
+ credentials: 'include',
9
+ },
10
+ })
11
+
12
+ const session = authClient.useSession()
13
+
14
+ function createAuthMutation<T extends (...args: any[]) => any>(fn: T) {
15
+ return useMutation({
16
+ mutationFn: async (input: Parameters<T>[0]) => {
17
+ return fn(input, {
18
+ onError: ({ error }: any) => {
19
+ throw error
20
+ },
21
+ })
22
+ },
23
+ })
24
+ }
25
+
26
+ export function useAuth() {
27
+ const signIn = createAuthMutation(authClient.signIn.email)
28
+ const signUp = createAuthMutation(authClient.signUp.email)
29
+ const signOut = createAuthMutation(authClient.signOut)
30
+
31
+ const user = computed(() => session.value.data?.user)
32
+
33
+ return {
34
+ signIn,
35
+ signUp,
36
+ signOut,
37
+ user,
38
+ }
39
+ }
@@ -0,0 +1,13 @@
1
+ import { useQuery } from '@tanstack/vue-query'
2
+
3
+ import { orpc } from '~/app/lib/orpc'
4
+
5
+ export function useContent() {
6
+ const publicContent = useQuery(orpc.public.queryOptions({})).data
7
+ const privateContent = useQuery(orpc.private.queryOptions({})).data
8
+
9
+ return {
10
+ publicContent,
11
+ privateContent,
12
+ }
13
+ }
@@ -1,3 +1,4 @@
1
1
  export { useAuth } from './auth'
2
+ export { useContent } from './content'
2
3
  export { useI18n } from '@kevinmarrec/vue-i18n'
3
4
  export { useHead } from '@unhead/vue'
@@ -1,10 +1,11 @@
1
- import type { Router, RouterClient } from '@backend/orpc/router'
2
1
  import { createORPCClient } from '@orpc/client'
3
2
  import { RPCLink } from '@orpc/client/fetch'
4
3
  import { createTanstackQueryUtils } from '@orpc/tanstack-query'
5
4
 
5
+ import type { Router, RouterClient } from '~/api/orpc/router'
6
+
6
7
  const link = new RPCLink({
7
- url: import.meta.env.VITE_API_URL,
8
+ url: `${import.meta.env.VITE_API_URL}/rpc`,
8
9
  fetch: (request, init) =>
9
10
  globalThis.fetch(request, {
10
11
  ...init,