vasp-cli 0.1.2 → 0.1.4

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 (130) hide show
  1. package/dist/vasp +23 -19
  2. package/package.json +3 -5
  3. package/templates/shared/.env.example.hbs +14 -0
  4. package/templates/shared/.gitignore.hbs +8 -0
  5. package/templates/shared/auth/client/Login.vue.hbs +46 -0
  6. package/templates/shared/auth/client/Register.vue.hbs +42 -0
  7. package/templates/shared/auth/server/index.hbs +51 -0
  8. package/templates/shared/auth/server/middleware.hbs +33 -0
  9. package/templates/shared/auth/server/providers/github.hbs +48 -0
  10. package/templates/shared/auth/server/providers/google.hbs +53 -0
  11. package/templates/shared/auth/server/providers/usernameAndPassword.hbs +69 -0
  12. package/templates/shared/bunfig.toml.hbs +2 -0
  13. package/templates/shared/drizzle/schema.hbs +37 -0
  14. package/templates/shared/jobs/_job.hbs +24 -0
  15. package/templates/shared/jobs/boss.hbs +15 -0
  16. package/templates/shared/package.json.hbs +32 -0
  17. package/templates/shared/server/db/client.hbs +12 -0
  18. package/templates/shared/server/index.hbs +52 -0
  19. package/templates/shared/server/routes/actions/_action.hbs +20 -0
  20. package/templates/shared/server/routes/crud/_crud.hbs +42 -0
  21. package/templates/shared/server/routes/jobs/_schedule.hbs +12 -0
  22. package/templates/shared/server/routes/queries/_query.hbs +20 -0
  23. package/templates/shared/server/routes/realtime/_channel.hbs +30 -0
  24. package/templates/shared/server/routes/realtime/index.hbs +9 -0
  25. package/templates/shared/tsconfig.json.hbs +21 -0
  26. package/templates/spa/js/index.html.hbs +12 -0
  27. package/templates/spa/js/src/App.vue.hbs +3 -0
  28. package/templates/spa/js/src/main.js.hbs +9 -0
  29. package/templates/spa/js/src/router/index.js.hbs +41 -0
  30. package/templates/spa/js/src/vasp/auth.js.hbs +45 -0
  31. package/templates/spa/js/src/vasp/client/actions.js.hbs +15 -0
  32. package/templates/spa/js/src/vasp/client/crud.js.hbs +30 -0
  33. package/templates/spa/js/src/vasp/client/index.js.hbs +16 -0
  34. package/templates/spa/js/src/vasp/client/queries.js.hbs +15 -0
  35. package/templates/spa/js/src/vasp/client/realtime.js.hbs +51 -0
  36. package/templates/spa/js/src/vasp/plugin.js.hbs +11 -0
  37. package/templates/spa/js/vite.config.js.hbs +26 -0
  38. package/templates/spa/ts/index.html.hbs +12 -0
  39. package/templates/spa/ts/src/App.vue.hbs +3 -0
  40. package/templates/spa/ts/src/main.ts.hbs +9 -0
  41. package/templates/spa/ts/src/router/index.ts.hbs +41 -0
  42. package/templates/spa/ts/src/vasp/auth.ts.hbs +53 -0
  43. package/templates/spa/ts/src/vasp/client/actions.ts.hbs +19 -0
  44. package/templates/spa/ts/src/vasp/client/crud.ts.hbs +37 -0
  45. package/templates/spa/ts/src/vasp/client/index.ts.hbs +17 -0
  46. package/templates/spa/ts/src/vasp/client/queries.ts.hbs +19 -0
  47. package/templates/spa/ts/src/vasp/client/realtime.ts.hbs +56 -0
  48. package/templates/spa/ts/src/vasp/client/types.ts.hbs +33 -0
  49. package/templates/spa/ts/src/vasp/plugin.ts.hbs +12 -0
  50. package/templates/spa/ts/vite.config.ts.hbs +26 -0
  51. package/templates/ssr/js/_page.vue.hbs +10 -0
  52. package/templates/ssr/js/app.vue.hbs +3 -0
  53. package/templates/ssr/js/composables/useAuth.js.hbs +52 -0
  54. package/templates/ssr/js/composables/useVasp.js.hbs +6 -0
  55. package/templates/ssr/js/middleware/auth.js.hbs +8 -0
  56. package/templates/ssr/js/nuxt.config.js.hbs +15 -0
  57. package/templates/ssr/js/plugins/vasp.client.js.hbs +17 -0
  58. package/templates/ssr/js/plugins/vasp.server.js.hbs +33 -0
  59. package/templates/ssr/ts/_page.vue.hbs +10 -0
  60. package/templates/ssr/ts/app.vue.hbs +3 -0
  61. package/templates/ssr/ts/composables/useAuth.ts.hbs +56 -0
  62. package/templates/ssr/ts/composables/useVasp.ts.hbs +10 -0
  63. package/templates/ssr/ts/middleware/auth.ts.hbs +8 -0
  64. package/templates/ssr/ts/nuxt.config.ts.hbs +19 -0
  65. package/templates/ssr/ts/plugins/vasp.client.ts.hbs +17 -0
  66. package/templates/ssr/ts/plugins/vasp.server.ts.hbs +33 -0
  67. package/templates/templates/shared/.env.example.hbs +14 -0
  68. package/templates/templates/shared/.gitignore.hbs +8 -0
  69. package/templates/templates/shared/auth/client/Login.vue.hbs +46 -0
  70. package/templates/templates/shared/auth/client/Register.vue.hbs +42 -0
  71. package/templates/templates/shared/auth/server/index.hbs +51 -0
  72. package/templates/templates/shared/auth/server/middleware.hbs +33 -0
  73. package/templates/templates/shared/auth/server/providers/github.hbs +48 -0
  74. package/templates/templates/shared/auth/server/providers/google.hbs +53 -0
  75. package/templates/templates/shared/auth/server/providers/usernameAndPassword.hbs +69 -0
  76. package/templates/templates/shared/bunfig.toml.hbs +2 -0
  77. package/templates/templates/shared/drizzle/schema.hbs +37 -0
  78. package/templates/templates/shared/jobs/_job.hbs +24 -0
  79. package/templates/templates/shared/jobs/boss.hbs +15 -0
  80. package/templates/templates/shared/package.json.hbs +32 -0
  81. package/templates/templates/shared/server/db/client.hbs +12 -0
  82. package/templates/templates/shared/server/index.hbs +52 -0
  83. package/templates/templates/shared/server/routes/actions/_action.hbs +20 -0
  84. package/templates/templates/shared/server/routes/crud/_crud.hbs +42 -0
  85. package/templates/templates/shared/server/routes/jobs/_schedule.hbs +12 -0
  86. package/templates/templates/shared/server/routes/queries/_query.hbs +20 -0
  87. package/templates/templates/shared/server/routes/realtime/_channel.hbs +30 -0
  88. package/templates/templates/shared/server/routes/realtime/index.hbs +9 -0
  89. package/templates/templates/shared/tsconfig.json.hbs +21 -0
  90. package/templates/templates/spa/js/index.html.hbs +12 -0
  91. package/templates/templates/spa/js/src/App.vue.hbs +3 -0
  92. package/templates/templates/spa/js/src/main.js.hbs +9 -0
  93. package/templates/templates/spa/js/src/router/index.js.hbs +41 -0
  94. package/templates/templates/spa/js/src/vasp/auth.js.hbs +45 -0
  95. package/templates/templates/spa/js/src/vasp/client/actions.js.hbs +15 -0
  96. package/templates/templates/spa/js/src/vasp/client/crud.js.hbs +30 -0
  97. package/templates/templates/spa/js/src/vasp/client/index.js.hbs +16 -0
  98. package/templates/templates/spa/js/src/vasp/client/queries.js.hbs +15 -0
  99. package/templates/templates/spa/js/src/vasp/client/realtime.js.hbs +51 -0
  100. package/templates/templates/spa/js/src/vasp/plugin.js.hbs +11 -0
  101. package/templates/templates/spa/js/vite.config.js.hbs +26 -0
  102. package/templates/templates/spa/ts/index.html.hbs +12 -0
  103. package/templates/templates/spa/ts/src/App.vue.hbs +3 -0
  104. package/templates/templates/spa/ts/src/main.ts.hbs +9 -0
  105. package/templates/templates/spa/ts/src/router/index.ts.hbs +41 -0
  106. package/templates/templates/spa/ts/src/vasp/auth.ts.hbs +53 -0
  107. package/templates/templates/spa/ts/src/vasp/client/actions.ts.hbs +19 -0
  108. package/templates/templates/spa/ts/src/vasp/client/crud.ts.hbs +37 -0
  109. package/templates/templates/spa/ts/src/vasp/client/index.ts.hbs +17 -0
  110. package/templates/templates/spa/ts/src/vasp/client/queries.ts.hbs +19 -0
  111. package/templates/templates/spa/ts/src/vasp/client/realtime.ts.hbs +56 -0
  112. package/templates/templates/spa/ts/src/vasp/client/types.ts.hbs +33 -0
  113. package/templates/templates/spa/ts/src/vasp/plugin.ts.hbs +12 -0
  114. package/templates/templates/spa/ts/vite.config.ts.hbs +26 -0
  115. package/templates/templates/ssr/js/_page.vue.hbs +10 -0
  116. package/templates/templates/ssr/js/app.vue.hbs +3 -0
  117. package/templates/templates/ssr/js/composables/useAuth.js.hbs +52 -0
  118. package/templates/templates/ssr/js/composables/useVasp.js.hbs +6 -0
  119. package/templates/templates/ssr/js/middleware/auth.js.hbs +8 -0
  120. package/templates/templates/ssr/js/nuxt.config.js.hbs +15 -0
  121. package/templates/templates/ssr/js/plugins/vasp.client.js.hbs +17 -0
  122. package/templates/templates/ssr/js/plugins/vasp.server.js.hbs +33 -0
  123. package/templates/templates/ssr/ts/_page.vue.hbs +10 -0
  124. package/templates/templates/ssr/ts/app.vue.hbs +3 -0
  125. package/templates/templates/ssr/ts/composables/useAuth.ts.hbs +56 -0
  126. package/templates/templates/ssr/ts/composables/useVasp.ts.hbs +10 -0
  127. package/templates/templates/ssr/ts/middleware/auth.ts.hbs +8 -0
  128. package/templates/templates/ssr/ts/nuxt.config.ts.hbs +19 -0
  129. package/templates/templates/ssr/ts/plugins/vasp.client.ts.hbs +17 -0
  130. package/templates/templates/ssr/ts/plugins/vasp.server.ts.hbs +33 -0
@@ -0,0 +1,33 @@
1
+ // Auto-generated by Vasp — entity types inferred from Drizzle schema
2
+ import type {
3
+ {{#each cruds}}
4
+ {{pascalCase entity}},
5
+ New{{pascalCase entity}},
6
+ {{/each}}
7
+ {{#if hasAuth}}
8
+ User,
9
+ {{/if}}
10
+ } from '../../../../../../drizzle/schema.js'
11
+
12
+ {{#each queries}}
13
+ // Arguments and return type for '{{name}}' — customize as needed
14
+ export type {{pascalCase name}}Args = Record<string, unknown>
15
+ export type {{pascalCase name}}Return = unknown
16
+
17
+ {{/each}}
18
+ {{#each actions}}
19
+ // Arguments and return type for '{{name}}' — customize as needed
20
+ export type {{pascalCase name}}Args = Record<string, unknown>
21
+ export type {{pascalCase name}}Return = unknown
22
+
23
+ {{/each}}
24
+ // Re-export entity types for convenience
25
+ export type {
26
+ {{#each cruds}}
27
+ {{pascalCase entity}},
28
+ New{{pascalCase entity}},
29
+ {{/each}}
30
+ {{#if hasAuth}}
31
+ User,
32
+ {{/if}}
33
+ }
@@ -0,0 +1,12 @@
1
+ import type { App } from 'vue'
2
+ import { createVaspClient } from '@vasp-framework/runtime'
3
+
4
+ export const vaspPlugin = {
5
+ install(app: App) {
6
+ const client = createVaspClient({
7
+ baseURL: import.meta.env.VITE_API_URL || '/api',
8
+ })
9
+ app.provide('$vasp', client)
10
+ app.config.globalProperties.$vasp = client
11
+ },
12
+ }
@@ -0,0 +1,26 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+ import { fileURLToPath, URL } from 'node:url'
4
+
5
+ export default defineConfig({
6
+ plugins: [vue()],
7
+ resolve: {
8
+ alias: {
9
+ '@src': fileURLToPath(new URL('./src', import.meta.url)),
10
+ '@vasp-framework/client': fileURLToPath(new URL('./src/vasp/client', import.meta.url)),
11
+ },
12
+ },
13
+ server: {
14
+ port: {{frontendPort}},
15
+ proxy: {
16
+ '/api': {
17
+ target: 'http://localhost:{{backendPort}}',
18
+ changeOrigin: true,
19
+ },
20
+ '/ws': {
21
+ target: 'ws://localhost:{{backendPort}}',
22
+ ws: true,
23
+ },
24
+ },
25
+ },
26
+ })
@@ -0,0 +1,10 @@
1
+ <template>
2
+ <{{componentName}} />
3
+ </template>
4
+
5
+ <script setup>
6
+ import {{componentName}} from '{{componentSource}}'
7
+ {{#if hasAuth}}
8
+ definePageMeta({ middleware: 'auth' })
9
+ {{/if}}
10
+ </script>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <NuxtPage />
3
+ </template>
@@ -0,0 +1,52 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ // SSR-aware auth composable — uses cookies (httpOnly) and Nuxt's useRequestHeaders for server-side access
3
+ import { $fetch } from 'ofetch'
4
+
5
+ const _user = ref(null)
6
+ let _checked = false
7
+
8
+ export function useAuth() {
9
+ const config = useRuntimeConfig()
10
+ const baseURL = config.public.apiBase
11
+
12
+ // On server, forward cookies from the incoming request
13
+ const headers = import.meta.server ? useRequestHeaders(['cookie']) : {}
14
+
15
+ async function checkAuth() {
16
+ if (_checked) return
17
+ _checked = true
18
+ try {
19
+ _user.value = await $fetch(`${baseURL}/auth/me`, { headers, credentials: 'include' })
20
+ } catch {
21
+ _user.value = null
22
+ }
23
+ }
24
+
25
+ async function login(username, password) {
26
+ _user.value = await $fetch(`${baseURL}/auth/login`, {
27
+ method: 'POST',
28
+ body: { username, password },
29
+ credentials: 'include',
30
+ })
31
+ _checked = true
32
+ return _user.value
33
+ }
34
+
35
+ async function register(username, password, email) {
36
+ _user.value = await $fetch(`${baseURL}/auth/register`, {
37
+ method: 'POST',
38
+ body: { username, password, email },
39
+ credentials: 'include',
40
+ })
41
+ _checked = true
42
+ return _user.value
43
+ }
44
+
45
+ async function logout() {
46
+ await $fetch(`${baseURL}/auth/logout`, { method: 'POST', credentials: 'include' })
47
+ _user.value = null
48
+ _checked = false
49
+ }
50
+
51
+ return { user: _user, checkAuth, login, register, logout }
52
+ }
@@ -0,0 +1,6 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ // Thin wrapper — the actual $vasp client is provided by vasp.server.js or vasp.client.js plugin
3
+ export const useVasp = () => {
4
+ const { $vasp } = useNuxtApp()
5
+ return { $vasp }
6
+ }
@@ -0,0 +1,8 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ export default defineNuxtRouteMiddleware((to) => {
3
+ const { user } = useAuth()
4
+ const publicPaths = ['/login', '/register']
5
+ if (!user.value && !publicPaths.includes(to.path)) {
6
+ return navigateTo('/login')
7
+ }
8
+ })
@@ -0,0 +1,15 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ // https://nuxt.com/docs/api/configuration/nuxt-config
3
+ export default defineNuxtConfig({
4
+ compatibilityDate: '2024-11-01',
5
+ devtools: { enabled: true },
6
+ alias: {
7
+ '@src': '~/src',
8
+ },
9
+ runtimeConfig: {
10
+ backendUrl: process.env.BACKEND_URL || 'http://localhost:{{backendPort}}',
11
+ public: {
12
+ apiBase: process.env.API_BASE || 'http://localhost:{{backendPort}}/api',
13
+ },
14
+ },
15
+ })
@@ -0,0 +1,17 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ // Runs only on the client after hydration — routes calls to the Elysia backend via ofetch
3
+ import { $fetch } from 'ofetch'
4
+
5
+ export default defineNuxtPlugin((nuxtApp) => {
6
+ const config = useRuntimeConfig()
7
+ const baseURL = config.public.apiBase
8
+
9
+ nuxtApp.provide('vasp', {
10
+ async query(name, args) {
11
+ return $fetch(`/queries/${name}`, { baseURL, method: 'GET', query: args })
12
+ },
13
+ async action(name, args) {
14
+ return $fetch(`/actions/${name}`, { baseURL, method: 'POST', body: args })
15
+ },
16
+ })
17
+ })
@@ -0,0 +1,33 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ // Runs only on the server during SSR render — calls query/action functions directly (zero HTTP overhead)
3
+ {{#each queries}}
4
+ import { {{importName fn}} } from '{{fn.source}}'
5
+ {{/each}}
6
+ {{#each actions}}
7
+ import { {{importName fn}} } from '{{fn.source}}'
8
+ {{/each}}
9
+
10
+ export default defineNuxtPlugin((nuxtApp) => {
11
+ const queryFns = {
12
+ {{#each queries}}
13
+ {{camelCase name}}: {{importName fn}},
14
+ {{/each}}
15
+ }
16
+
17
+ const actionFns = {
18
+ {{#each actions}}
19
+ {{camelCase name}}: {{importName fn}},
20
+ {{/each}}
21
+ }
22
+
23
+ nuxtApp.provide('vasp', {
24
+ async query(name, args) {
25
+ if (!queryFns[name]) throw new Error(`[Vasp] Unknown query: ${name}`)
26
+ return queryFns[name](args)
27
+ },
28
+ async action(name, args) {
29
+ if (!actionFns[name]) throw new Error(`[Vasp] Unknown action: ${name}`)
30
+ return actionFns[name](args)
31
+ },
32
+ })
33
+ })
@@ -0,0 +1,10 @@
1
+ <template>
2
+ <{{componentName}} />
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import {{componentName}} from '{{componentSource}}'
7
+ {{#if hasAuth}}
8
+ definePageMeta({ middleware: 'auth' })
9
+ {{/if}}
10
+ </script>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <NuxtPage />
3
+ </template>
@@ -0,0 +1,56 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ // SSR-aware auth composable — uses cookies (httpOnly) and Nuxt's useRequestHeaders for server-side access
3
+ import { $fetch } from 'ofetch'
4
+
5
+ interface AuthUser {
6
+ id: number
7
+ username: string
8
+ email?: string
9
+ }
10
+
11
+ const _user = ref<AuthUser | null>(null)
12
+ let _checked = false
13
+
14
+ export function useAuth() {
15
+ const config = useRuntimeConfig()
16
+ const baseURL = config.public.apiBase as string
17
+ const headers = import.meta.server ? useRequestHeaders(['cookie']) : {}
18
+
19
+ async function checkAuth(): Promise<void> {
20
+ if (_checked) return
21
+ _checked = true
22
+ try {
23
+ _user.value = await $fetch<AuthUser>(`${baseURL}/auth/me`, { headers, credentials: 'include' })
24
+ } catch {
25
+ _user.value = null
26
+ }
27
+ }
28
+
29
+ async function login(username: string, password: string): Promise<AuthUser> {
30
+ _user.value = await $fetch<AuthUser>(`${baseURL}/auth/login`, {
31
+ method: 'POST',
32
+ body: { username, password },
33
+ credentials: 'include',
34
+ })
35
+ _checked = true
36
+ return _user.value!
37
+ }
38
+
39
+ async function register(username: string, password: string, email?: string): Promise<AuthUser> {
40
+ _user.value = await $fetch<AuthUser>(`${baseURL}/auth/register`, {
41
+ method: 'POST',
42
+ body: { username, password, email },
43
+ credentials: 'include',
44
+ })
45
+ _checked = true
46
+ return _user.value!
47
+ }
48
+
49
+ async function logout(): Promise<void> {
50
+ await $fetch(`${baseURL}/auth/logout`, { method: 'POST', credentials: 'include' })
51
+ _user.value = null
52
+ _checked = false
53
+ }
54
+
55
+ return { user: _user, checkAuth, login, register, logout }
56
+ }
@@ -0,0 +1,10 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ interface VaspClient {
3
+ query<T = unknown>(name: string, args?: unknown): Promise<T>
4
+ action<T = unknown>(name: string, args?: unknown): Promise<T>
5
+ }
6
+
7
+ export const useVasp = (): { $vasp: VaspClient } => {
8
+ const { $vasp } = useNuxtApp()
9
+ return { $vasp: $vasp as VaspClient }
10
+ }
@@ -0,0 +1,8 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ export default defineNuxtRouteMiddleware((to) => {
3
+ const { user } = useAuth()
4
+ const publicPaths = ['/login', '/register']
5
+ if (!user.value && !publicPaths.includes(to.path)) {
6
+ return navigateTo('/login')
7
+ }
8
+ })
@@ -0,0 +1,19 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ // https://nuxt.com/docs/api/configuration/nuxt-config
3
+ export default defineNuxtConfig({
4
+ compatibilityDate: '2024-11-01',
5
+ devtools: { enabled: true },
6
+ typescript: {
7
+ strict: true,
8
+ typeCheck: true,
9
+ },
10
+ alias: {
11
+ '@src': '~/src',
12
+ },
13
+ runtimeConfig: {
14
+ backendUrl: process.env.BACKEND_URL || 'http://localhost:{{backendPort}}',
15
+ public: {
16
+ apiBase: process.env.API_BASE || 'http://localhost:{{backendPort}}/api',
17
+ },
18
+ },
19
+ })
@@ -0,0 +1,17 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ // Runs only on the client after hydration — routes calls to the Elysia backend via ofetch
3
+ import { $fetch } from 'ofetch'
4
+
5
+ export default defineNuxtPlugin((nuxtApp) => {
6
+ const config = useRuntimeConfig()
7
+ const baseURL = config.public.apiBase as string
8
+
9
+ nuxtApp.provide('vasp', {
10
+ async query<T = unknown>(name: string, args?: unknown): Promise<T> {
11
+ return $fetch<T>(`/queries/${name}`, { baseURL, method: 'GET', query: args as Record<string, unknown> })
12
+ },
13
+ async action<T = unknown>(name: string, args?: unknown): Promise<T> {
14
+ return $fetch<T>(`/actions/${name}`, { baseURL, method: 'POST', body: args })
15
+ },
16
+ })
17
+ })
@@ -0,0 +1,33 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ // Runs only on the server during SSR render — calls query/action functions directly (zero HTTP overhead)
3
+ {{#each queries}}
4
+ import { {{importName fn}} } from '{{fn.source}}'
5
+ {{/each}}
6
+ {{#each actions}}
7
+ import { {{importName fn}} } from '{{fn.source}}'
8
+ {{/each}}
9
+
10
+ export default defineNuxtPlugin((nuxtApp) => {
11
+ const queryFns: Record<string, (args?: unknown) => Promise<unknown>> = {
12
+ {{#each queries}}
13
+ {{camelCase name}}: {{importName fn}} as (args?: unknown) => Promise<unknown>,
14
+ {{/each}}
15
+ }
16
+
17
+ const actionFns: Record<string, (args?: unknown) => Promise<unknown>> = {
18
+ {{#each actions}}
19
+ {{camelCase name}}: {{importName fn}} as (args?: unknown) => Promise<unknown>,
20
+ {{/each}}
21
+ }
22
+
23
+ nuxtApp.provide('vasp', {
24
+ async query<T = unknown>(name: string, args?: unknown): Promise<T> {
25
+ if (!queryFns[name]) throw new Error(`[Vasp] Unknown query: ${name}`)
26
+ return queryFns[name](args) as Promise<T>
27
+ },
28
+ async action<T = unknown>(name: string, args?: unknown): Promise<T> {
29
+ if (!actionFns[name]) throw new Error(`[Vasp] Unknown action: ${name}`)
30
+ return actionFns[name](args) as Promise<T>
31
+ },
32
+ })
33
+ })
@@ -0,0 +1,14 @@
1
+ DATABASE_URL=postgres://user:password@localhost:5432/{{kebabCase appName}}
2
+ PORT={{backendPort}}
3
+ VITE_API_URL=http://localhost:{{backendPort}}/api
4
+ {{#if hasAuth}}
5
+ JWT_SECRET=change-me-in-production
6
+ {{#if (includes authMethods "google")}}
7
+ GOOGLE_CLIENT_ID=
8
+ GOOGLE_CLIENT_SECRET=
9
+ {{/if}}
10
+ {{#if (includes authMethods "github")}}
11
+ GITHUB_CLIENT_ID=
12
+ GITHUB_CLIENT_SECRET=
13
+ {{/if}}
14
+ {{/if}}
@@ -0,0 +1,8 @@
1
+ node_modules
2
+ dist
3
+ .output
4
+ .nuxt
5
+ .vasp-gen
6
+ .env
7
+ .env.local
8
+ *.log
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <div class="auth-form">
3
+ <h2>Login</h2>
4
+ <form @submit.prevent="handleLogin">
5
+ <input v-model="username" type="text" placeholder="Username" required />
6
+ <input v-model="password" type="password" placeholder="Password" required />
7
+ <button type="submit" :disabled="loading">
8
+ \{{ loading ? 'Logging in...' : 'Login' }}
9
+ </button>
10
+ <p v-if="error" class="error">\{{ error }}</p>
11
+ </form>
12
+ <p>Don't have an account? <RouterLink to="/register">Register</RouterLink></p>
13
+ {{#if (includes authMethods "google")}}
14
+ <a href="/auth/google" class="oauth-btn">Continue with Google</a>
15
+ {{/if}}
16
+ {{#if (includes authMethods "github")}}
17
+ <a href="/auth/github" class="oauth-btn">Continue with GitHub</a>
18
+ {{/if}}
19
+ </div>
20
+ </template>
21
+
22
+ <script setup>
23
+ import { ref } from 'vue'
24
+ import { useRouter } from 'vue-router'
25
+ import { useAuth } from '../vasp/auth.js'
26
+
27
+ const router = useRouter()
28
+ const { login } = useAuth()
29
+ const username = ref('')
30
+ const password = ref('')
31
+ const loading = ref(false)
32
+ const error = ref('')
33
+
34
+ async function handleLogin() {
35
+ loading.value = true
36
+ error.value = ''
37
+ try {
38
+ await login(username.value, password.value)
39
+ router.push('/')
40
+ } catch (err) {
41
+ error.value = err?.data?.error || 'Login failed'
42
+ } finally {
43
+ loading.value = false
44
+ }
45
+ }
46
+ </script>
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <div class="auth-form">
3
+ <h2>Create Account</h2>
4
+ <form @submit.prevent="handleRegister">
5
+ <input v-model="username" type="text" placeholder="Username" required />
6
+ <input v-model="email" type="email" placeholder="Email (optional)" />
7
+ <input v-model="password" type="password" placeholder="Password (min 8 chars)" required />
8
+ <button type="submit" :disabled="loading">
9
+ \{{ loading ? 'Creating account...' : 'Register' }}
10
+ </button>
11
+ <p v-if="error" class="error">\{{ error }}</p>
12
+ </form>
13
+ <p>Already have an account? <RouterLink to="/login">Login</RouterLink></p>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup>
18
+ import { ref } from 'vue'
19
+ import { useRouter } from 'vue-router'
20
+ import { useAuth } from '../vasp/auth.js'
21
+
22
+ const router = useRouter()
23
+ const { register } = useAuth()
24
+ const username = ref('')
25
+ const email = ref('')
26
+ const password = ref('')
27
+ const loading = ref(false)
28
+ const error = ref('')
29
+
30
+ async function handleRegister() {
31
+ loading.value = true
32
+ error.value = ''
33
+ try {
34
+ await register(username.value, password.value, email.value || undefined)
35
+ router.push('/')
36
+ } catch (err) {
37
+ error.value = err?.data?.error || 'Registration failed'
38
+ } finally {
39
+ loading.value = false
40
+ }
41
+ }
42
+ </script>
@@ -0,0 +1,51 @@
1
+ import { Elysia } from 'elysia'
2
+ import { jwt } from '@elysiajs/jwt'
3
+ import { cookie } from '@elysiajs/cookie'
4
+ import { db } from '../db/client.{{ext}}'
5
+ import { users } from '../../drizzle/schema.{{ext}}'
6
+ import { eq } from 'drizzle-orm'
7
+ {{#if (includes authMethods "usernameAndPassword")}}
8
+ import { usernameAndPasswordRoutes } from './providers/usernameAndPassword.{{ext}}'
9
+ {{/if}}
10
+ {{#if (includes authMethods "google")}}
11
+ import { googleRoutes } from './providers/google.{{ext}}'
12
+ {{/if}}
13
+ {{#if (includes authMethods "github")}}
14
+ import { githubRoutes } from './providers/github.{{ext}}'
15
+ {{/if}}
16
+
17
+ const JWT_SECRET = process.env.JWT_SECRET || 'change-me-in-production'
18
+
19
+ export const authPlugin = new Elysia({ name: 'auth-plugin' })
20
+ .use(jwt({ name: 'jwt', secret: JWT_SECRET }))
21
+ .use(cookie())
22
+
23
+ export const authRoutes = new Elysia({ prefix: '/auth' })
24
+ .use(authPlugin)
25
+ {{#if (includes authMethods "usernameAndPassword")}}
26
+ .use(usernameAndPasswordRoutes)
27
+ {{/if}}
28
+ {{#if (includes authMethods "google")}}
29
+ .use(googleRoutes)
30
+ {{/if}}
31
+ {{#if (includes authMethods "github")}}
32
+ .use(githubRoutes)
33
+ {{/if}}
34
+ .get('/me', async ({ jwt, cookie: { token }, set }) => {
35
+ const payload = await jwt.verify(token?.value ?? '')
36
+ if (!payload || typeof payload.userId !== 'number') {
37
+ set.status = 401
38
+ return { error: 'Unauthorized' }
39
+ }
40
+ const [user] = await db.select().from(users).where(eq(users.id, payload.userId)).limit(1)
41
+ if (!user) {
42
+ set.status = 401
43
+ return { error: 'User not found' }
44
+ }
45
+ const { passwordHash: _ph, ...safeUser } = user
46
+ return safeUser
47
+ })
48
+ .post('/logout', ({ cookie: { token }, set }) => {
49
+ token?.remove()
50
+ return { ok: true }
51
+ })
@@ -0,0 +1,33 @@
1
+ import { Elysia } from 'elysia'
2
+ import { jwt } from '@elysiajs/jwt'
3
+ import { cookie } from '@elysiajs/cookie'
4
+ import { db } from '../db/client.{{ext}}'
5
+ import { users } from '../../drizzle/schema.{{ext}}'
6
+ import { eq } from 'drizzle-orm'
7
+
8
+ const JWT_SECRET = process.env.JWT_SECRET || 'change-me-in-production'
9
+
10
+ /**
11
+ * requireAuth — Elysia plugin that verifies the JWT cookie and injects `user` into the context.
12
+ * Use on any route that requires authentication.
13
+ *
14
+ * @example
15
+ * new Elysia().use(requireAuth).get('/protected', ({ user }) => user)
16
+ */
17
+ export const requireAuth = new Elysia({ name: 'require-auth' })
18
+ .use(jwt({ name: 'jwt', secret: JWT_SECRET }))
19
+ .use(cookie())
20
+ .derive(async ({ jwt, cookie: { token }, set }) => {
21
+ const payload = await jwt.verify(token?.value ?? '')
22
+ if (!payload || typeof payload.userId !== 'number') {
23
+ set.status = 401
24
+ throw new Error('Unauthorized')
25
+ }
26
+ const [user] = await db.select().from(users).where(eq(users.id, payload.userId)).limit(1)
27
+ if (!user) {
28
+ set.status = 401
29
+ throw new Error('User not found')
30
+ }
31
+ const { passwordHash: _ph, ...safeUser } = user
32
+ return { user: safeUser }
33
+ })
@@ -0,0 +1,48 @@
1
+ import { Elysia } from 'elysia'
2
+ import { db } from '../../db/client.{{ext}}'
3
+ import { users } from '../../../drizzle/schema.{{ext}}'
4
+ import { eq } from 'drizzle-orm'
5
+ import { authPlugin } from '../index.{{ext}}'
6
+
7
+ const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || ''
8
+ const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET || ''
9
+ const REDIRECT_URI = process.env.GITHUB_REDIRECT_URI || 'http://localhost:{{backendPort}}/auth/github/callback'
10
+
11
+ export const githubRoutes = new Elysia()
12
+ .use(authPlugin)
13
+ .get('/github', ({ set }) => {
14
+ const params = new URLSearchParams({
15
+ client_id: GITHUB_CLIENT_ID,
16
+ redirect_uri: REDIRECT_URI,
17
+ scope: 'read:user user:email',
18
+ })
19
+ set.redirect = `https://github.com/login/oauth/authorize?${params}`
20
+ })
21
+ .get('/github/callback', async ({ query, jwt, cookie: { token }, set }) => {
22
+ const { code } = query
23
+ if (!code) { set.status = 400; return { error: 'Missing code' } }
24
+
25
+ const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
26
+ method: 'POST',
27
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
28
+ body: JSON.stringify({ client_id: GITHUB_CLIENT_ID, client_secret: GITHUB_CLIENT_SECRET, code }),
29
+ })
30
+ const { access_token } = await tokenRes.json()
31
+
32
+ const userRes = await fetch('https://api.github.com/user', {
33
+ headers: { Authorization: `Bearer ${access_token}`, Accept: 'application/json' },
34
+ })
35
+ const ghUser = await userRes.json()
36
+ const githubId = String(ghUser.id)
37
+ const email = ghUser.email || `${ghUser.login}@github.local`
38
+
39
+ let [user] = await db.select().from(users).where(eq(users.githubId, githubId)).limit(1)
40
+ if (!user) {
41
+ ;[user] = await db.insert(users).values({ username: ghUser.login, email, githubId }).returning()
42
+ }
43
+ if (!user) { set.status = 500; return { error: 'Failed to create user' } }
44
+
45
+ const tokenValue = await jwt.sign({ userId: user.id })
46
+ token.set({ value: tokenValue, httpOnly: true, sameSite: 'lax', path: '/' })
47
+ set.redirect = '/'
48
+ })