vasp-cli 0.3.1 → 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 (140) hide show
  1. package/dist/vasp +80 -31
  2. package/package.json +2 -2
  3. package/starters/minimal.vasp +1 -1
  4. package/starters/recipe.vasp +70 -0
  5. package/starters/todo-auth-ssr.vasp +33 -20
  6. package/starters/todo.vasp +15 -8
  7. package/templates/shared/.gitignore.hbs +1 -0
  8. package/templates/shared/README.md.hbs +53 -0
  9. package/templates/shared/auth/client/Login.vue.hbs +1 -1
  10. package/templates/shared/auth/client/Register.vue.hbs +1 -1
  11. package/templates/shared/auth/server/index.hbs +4 -8
  12. package/templates/shared/auth/server/middleware.hbs +33 -15
  13. package/templates/shared/auth/server/plugin.hbs +7 -0
  14. package/templates/shared/auth/server/providers/github.hbs +1 -1
  15. package/templates/shared/auth/server/providers/google.hbs +1 -1
  16. package/templates/shared/auth/server/providers/usernameAndPassword.hbs +3 -6
  17. package/templates/shared/bunfig.toml.hbs +3 -0
  18. package/templates/shared/drizzle/schema.hbs +39 -9
  19. package/templates/shared/jobs/_job.hbs +12 -2
  20. package/templates/shared/package.json.hbs +14 -4
  21. package/templates/shared/server/db/client.hbs +19 -1
  22. package/templates/shared/server/db/seed.hbs +16 -0
  23. package/templates/shared/server/index.hbs +48 -0
  24. package/templates/shared/server/middleware/errorHandler.hbs +75 -0
  25. package/templates/shared/server/middleware/logger.hbs +74 -0
  26. package/templates/{templates/shared → shared}/server/middleware/rateLimit.hbs +2 -2
  27. package/templates/shared/server/routes/_vasp.hbs +37 -0
  28. package/templates/shared/server/routes/actions/_action.hbs +5 -1
  29. package/templates/shared/server/routes/api/_api.hbs +24 -0
  30. package/templates/shared/server/routes/crud/_crud.hbs +103 -11
  31. package/templates/shared/server/routes/queries/_query.hbs +5 -1
  32. package/templates/shared/server/routes/realtime/_channel.hbs +58 -10
  33. package/templates/shared/shared/types.hbs +58 -0
  34. package/templates/shared/shared/validation.hbs +20 -0
  35. package/templates/shared/tests/actions/_action.test.js.hbs +7 -0
  36. package/templates/shared/tests/actions/_action.test.ts.hbs +7 -0
  37. package/templates/shared/tests/auth/login.test.js.hbs +7 -0
  38. package/templates/shared/tests/auth/login.test.ts.hbs +7 -0
  39. package/templates/shared/tests/crud/_entity.test.js.hbs +7 -0
  40. package/templates/shared/tests/crud/_entity.test.ts.hbs +7 -0
  41. package/templates/shared/tests/queries/_query.test.js.hbs +7 -0
  42. package/templates/shared/tests/queries/_query.test.ts.hbs +7 -0
  43. package/templates/shared/tests/setup.js.hbs +5 -0
  44. package/templates/shared/tests/setup.ts.hbs +5 -0
  45. package/templates/shared/tests/vitest.config.js.hbs +8 -0
  46. package/templates/shared/tests/vitest.config.ts.hbs +8 -0
  47. package/templates/shared/tsconfig.json.hbs +2 -1
  48. package/templates/spa/js/src/App.vue.hbs +9 -1
  49. package/templates/spa/js/src/components/VaspErrorBoundary.vue.hbs +33 -0
  50. package/templates/spa/js/src/components/VaspNotifications.vue.hbs +60 -0
  51. package/templates/spa/js/src/vasp/auth.js.hbs +31 -15
  52. package/templates/spa/js/src/vasp/client/actions.js.hbs +7 -1
  53. package/templates/spa/js/src/vasp/client/crud.js.hbs +94 -5
  54. package/templates/spa/js/src/vasp/useVaspNotifications.js.hbs +35 -0
  55. package/templates/spa/js/vite.config.js.hbs +1 -0
  56. package/templates/spa/ts/src/App.vue.hbs +9 -1
  57. package/templates/spa/ts/src/components/VaspErrorBoundary.vue.hbs +33 -0
  58. package/templates/spa/ts/src/components/VaspNotifications.vue.hbs +60 -0
  59. package/templates/spa/ts/src/vasp/auth.ts.hbs +31 -15
  60. package/templates/spa/ts/src/vasp/client/actions.ts.hbs +7 -1
  61. package/templates/spa/ts/src/vasp/client/crud.ts.hbs +96 -10
  62. package/templates/spa/ts/src/vasp/client/types.ts.hbs +14 -28
  63. package/templates/spa/ts/src/vasp/useVaspNotifications.ts.hbs +41 -0
  64. package/templates/spa/ts/vite.config.ts.hbs +1 -0
  65. package/templates/ssr/js/error.vue.hbs +23 -0
  66. package/templates/ssr/js/nuxt.config.js.hbs +1 -0
  67. package/templates/ssr/js/plugins/vasp.client.js.hbs +11 -1
  68. package/templates/ssr/ts/error.vue.hbs +26 -0
  69. package/templates/ssr/ts/nuxt.config.ts.hbs +1 -0
  70. package/templates/ssr/ts/plugins/vasp.client.ts.hbs +11 -1
  71. package/templates/starters/minimal.vasp +15 -0
  72. package/templates/starters/recipe.vasp +70 -0
  73. package/templates/starters/todo-auth-ssr.vasp +65 -0
  74. package/templates/starters/todo.vasp +42 -0
  75. package/templates/templates/shared/.gitignore.hbs +0 -8
  76. package/templates/templates/shared/auth/client/Login.vue.hbs +0 -46
  77. package/templates/templates/shared/auth/client/Register.vue.hbs +0 -42
  78. package/templates/templates/shared/auth/server/index.hbs +0 -51
  79. package/templates/templates/shared/auth/server/middleware.hbs +0 -33
  80. package/templates/templates/shared/auth/server/providers/github.hbs +0 -48
  81. package/templates/templates/shared/auth/server/providers/google.hbs +0 -53
  82. package/templates/templates/shared/auth/server/providers/usernameAndPassword.hbs +0 -69
  83. package/templates/templates/shared/bunfig.toml.hbs +0 -2
  84. package/templates/templates/shared/drizzle/schema.hbs +0 -48
  85. package/templates/templates/shared/jobs/_job.hbs +0 -34
  86. package/templates/templates/shared/jobs/boss.hbs +0 -15
  87. package/templates/templates/shared/package.json.hbs +0 -35
  88. package/templates/templates/shared/server/db/client.hbs +0 -12
  89. package/templates/templates/shared/server/index.hbs +0 -60
  90. package/templates/templates/shared/server/routes/actions/_action.hbs +0 -20
  91. package/templates/templates/shared/server/routes/crud/_crud.hbs +0 -86
  92. package/templates/templates/shared/server/routes/jobs/_schedule.hbs +0 -12
  93. package/templates/templates/shared/server/routes/queries/_query.hbs +0 -20
  94. package/templates/templates/shared/server/routes/realtime/_channel.hbs +0 -78
  95. package/templates/templates/shared/server/routes/realtime/index.hbs +0 -9
  96. package/templates/templates/shared/tsconfig.json.hbs +0 -21
  97. package/templates/templates/spa/js/index.html.hbs +0 -12
  98. package/templates/templates/spa/js/src/App.vue.hbs +0 -3
  99. package/templates/templates/spa/js/src/main.js.hbs +0 -9
  100. package/templates/templates/spa/js/src/router/index.js.hbs +0 -41
  101. package/templates/templates/spa/js/src/vasp/auth.js.hbs +0 -45
  102. package/templates/templates/spa/js/src/vasp/client/actions.js.hbs +0 -15
  103. package/templates/templates/spa/js/src/vasp/client/crud.js.hbs +0 -30
  104. package/templates/templates/spa/js/src/vasp/client/index.js.hbs +0 -16
  105. package/templates/templates/spa/js/src/vasp/client/queries.js.hbs +0 -15
  106. package/templates/templates/spa/js/src/vasp/client/realtime.js.hbs +0 -51
  107. package/templates/templates/spa/js/src/vasp/plugin.js.hbs +0 -11
  108. package/templates/templates/spa/js/vite.config.js.hbs +0 -26
  109. package/templates/templates/spa/ts/index.html.hbs +0 -12
  110. package/templates/templates/spa/ts/src/App.vue.hbs +0 -3
  111. package/templates/templates/spa/ts/src/main.ts.hbs +0 -9
  112. package/templates/templates/spa/ts/src/router/index.ts.hbs +0 -41
  113. package/templates/templates/spa/ts/src/vasp/auth.ts.hbs +0 -53
  114. package/templates/templates/spa/ts/src/vasp/client/actions.ts.hbs +0 -19
  115. package/templates/templates/spa/ts/src/vasp/client/crud.ts.hbs +0 -37
  116. package/templates/templates/spa/ts/src/vasp/client/index.ts.hbs +0 -17
  117. package/templates/templates/spa/ts/src/vasp/client/queries.ts.hbs +0 -19
  118. package/templates/templates/spa/ts/src/vasp/client/realtime.ts.hbs +0 -56
  119. package/templates/templates/spa/ts/src/vasp/client/types.ts.hbs +0 -33
  120. package/templates/templates/spa/ts/src/vasp/plugin.ts.hbs +0 -12
  121. package/templates/templates/spa/ts/vite.config.ts.hbs +0 -26
  122. package/templates/templates/ssr/js/_page.vue.hbs +0 -10
  123. package/templates/templates/ssr/js/app.vue.hbs +0 -3
  124. package/templates/templates/ssr/js/composables/useAuth.js.hbs +0 -52
  125. package/templates/templates/ssr/js/composables/useVasp.js.hbs +0 -6
  126. package/templates/templates/ssr/js/middleware/auth.js.hbs +0 -8
  127. package/templates/templates/ssr/js/nuxt.config.js.hbs +0 -15
  128. package/templates/templates/ssr/js/plugins/vasp.client.js.hbs +0 -27
  129. package/templates/templates/ssr/js/plugins/vasp.server.js.hbs +0 -33
  130. package/templates/templates/ssr/ts/_page.vue.hbs +0 -10
  131. package/templates/templates/ssr/ts/app.vue.hbs +0 -3
  132. package/templates/templates/ssr/ts/composables/useAuth.ts.hbs +0 -56
  133. package/templates/templates/ssr/ts/composables/useVasp.ts.hbs +0 -10
  134. package/templates/templates/ssr/ts/middleware/auth.ts.hbs +0 -8
  135. package/templates/templates/ssr/ts/nuxt.config.ts.hbs +0 -19
  136. package/templates/templates/ssr/ts/plugins/vasp.client.ts.hbs +0 -27
  137. package/templates/templates/ssr/ts/plugins/vasp.server.ts.hbs +0 -33
  138. /package/templates/{templates/shared/.env.example.hbs → shared/.env.hbs} +0 -0
  139. /package/templates/{templates/shared → shared}/drizzle/drizzle.config.hbs +0 -0
  140. /package/templates/{templates/shared → shared}/server/middleware/csrf.hbs +0 -0
@@ -1,35 +1,121 @@
1
1
  // Auto-generated by Vasp — do not edit
2
2
  import { $fetch } from 'ofetch'
3
+ import { safeParse } from 'valibot'
4
+ import { ref } from 'vue'
3
5
  import type {
4
6
  {{#each cruds}}
5
7
  {{pascalCase entity}},
6
- New{{pascalCase entity}},
8
+ Create{{pascalCase entity}}Input,
9
+ Update{{pascalCase entity}}Input,
7
10
  {{/each}}
8
11
  } from './types.js'
12
+ import {
13
+ {{#each cruds}}
14
+ Create{{pascalCase entity}}Schema,
15
+ Update{{pascalCase entity}}Schema,
16
+ {{/each}}
17
+ } from '@shared/validation.js'
18
+ import { notifyError } from '../useVaspNotifications.js'
9
19
 
10
20
  const API = import.meta.env.VITE_API_URL || '/api'
11
21
 
12
22
  {{#each cruds}}
13
23
  export function use{{pascalCase entity}}Crud() {
14
24
  const base = `${API}/crud/{{camelCase entity}}`
25
+ const loading = ref(false)
26
+ const error = ref<string | null>(null)
27
+
28
+ const fail = (err: unknown) => {
29
+ error.value = err instanceof Error ? err.message : 'Request failed'
30
+ notifyError(err)
31
+ }
15
32
 
16
33
  return {
34
+ loading,
35
+ error,
17
36
  {{#if (includes operations "list")}}
18
- list: (): Promise<{{pascalCase entity}}[]> => $fetch(base, { credentials: 'include' }),
37
+ list: async (): Promise<{{pascalCase entity}}[]> => {
38
+ loading.value = true
39
+ error.value = null
40
+ try {
41
+ return await $fetch(base, { credentials: 'include' })
42
+ } catch (err) {
43
+ fail(err)
44
+ throw err
45
+ } finally {
46
+ loading.value = false
47
+ }
48
+ },
19
49
  {{/if}}
20
50
  {{#if (includes operations "create")}}
21
- create: (data: New{{pascalCase entity}}): Promise<{{pascalCase entity}}> =>
22
- $fetch(base, { method: 'POST', body: data, credentials: 'include' }),
51
+ create: async (data: Create{{pascalCase entity}}Input): Promise<{{pascalCase entity}}> => {
52
+ const parsed = safeParse(Create{{pascalCase entity}}Schema, data)
53
+ if (!parsed.success) {
54
+ const msg = parsed.issues?.[0]?.message ?? 'Invalid input'
55
+ const err = new Error(msg)
56
+ fail(err)
57
+ throw err
58
+ }
59
+
60
+ loading.value = true
61
+ error.value = null
62
+ try {
63
+ return await $fetch(base, { method: 'POST', body: parsed.output, credentials: 'include' })
64
+ } catch (err) {
65
+ fail(err)
66
+ throw err
67
+ } finally {
68
+ loading.value = false
69
+ }
70
+ },
23
71
  {{/if}}
24
- get: (id: number): Promise<{{pascalCase entity}}> =>
25
- $fetch(`${base}/${id}`, { credentials: 'include' }),
72
+ get: async (id: number): Promise<{{pascalCase entity}}> => {
73
+ loading.value = true
74
+ error.value = null
75
+ try {
76
+ return await $fetch(`${base}/${id}`, { credentials: 'include' })
77
+ } catch (err) {
78
+ fail(err)
79
+ throw err
80
+ } finally {
81
+ loading.value = false
82
+ }
83
+ },
26
84
  {{#if (includes operations "update")}}
27
- update: (id: number, data: Partial<New{{pascalCase entity}}>): Promise<{{pascalCase entity}}> =>
28
- $fetch(`${base}/${id}`, { method: 'PUT', body: data, credentials: 'include' }),
85
+ update: async (id: number, data: Update{{pascalCase entity}}Input): Promise<{{pascalCase entity}}> => {
86
+ const parsed = safeParse(Update{{pascalCase entity}}Schema, data)
87
+ if (!parsed.success) {
88
+ const msg = parsed.issues?.[0]?.message ?? 'Invalid input'
89
+ const err = new Error(msg)
90
+ fail(err)
91
+ throw err
92
+ }
93
+
94
+ loading.value = true
95
+ error.value = null
96
+ try {
97
+ return await $fetch(`${base}/${id}`, { method: 'PUT', body: parsed.output, credentials: 'include' })
98
+ } catch (err) {
99
+ fail(err)
100
+ throw err
101
+ } finally {
102
+ loading.value = false
103
+ }
104
+ },
29
105
  {{/if}}
30
106
  {{#if (includes operations "delete")}}
31
- remove: (id: number): Promise<{ ok: boolean }> =>
32
- $fetch(`${base}/${id}`, { method: 'DELETE', credentials: 'include' }),
107
+ remove: async (id: number): Promise<{ ok: boolean }> => {
108
+ loading.value = true
109
+ error.value = null
110
+ try {
111
+ return await $fetch(`${base}/${id}`, { method: 'DELETE', credentials: 'include' })
112
+ } catch (err) {
113
+ fail(err)
114
+ throw err
115
+ } finally {
116
+ loading.value = false
117
+ }
118
+ },
33
119
  {{/if}}
34
120
  }
35
121
  }
@@ -1,33 +1,19 @@
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'
1
+ // Auto-generated by Vasp — re-exports entity types from shared
2
+ // Entity interfaces and input types are generated in shared/types.ts
3
+ // Query/action type stubs can be customized there
11
4
 
5
+ export type {
6
+ {{#each entities}}
7
+ {{pascalCase name}},
8
+ Create{{pascalCase name}}Input,
9
+ Update{{pascalCase name}}Input,
10
+ {{/each}}
12
11
  {{#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
-
12
+ {{pascalCase name}}Args,
13
+ {{pascalCase name}}Return,
17
14
  {{/each}}
18
15
  {{#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}},
16
+ {{pascalCase name}}Args,
17
+ {{pascalCase name}}Return,
29
18
  {{/each}}
30
- {{#if hasAuth}}
31
- User,
32
- {{/if}}
33
- }
19
+ } from '@shared/types.js'
@@ -0,0 +1,41 @@
1
+ import { ref } from 'vue'
2
+
3
+ export type VaspNotification = {
4
+ id: number
5
+ type: 'error' | 'success' | 'info'
6
+ message: string
7
+ }
8
+
9
+ export const notifications = ref<VaspNotification[]>([])
10
+
11
+ export function pushNotification(type: VaspNotification['type'], message: string, timeoutMs = 3500): number {
12
+ const id = Date.now() + Math.floor(Math.random() * 1000)
13
+ notifications.value.push({ id, type, message })
14
+
15
+ if (timeoutMs > 0) {
16
+ setTimeout(() => removeNotification(id), timeoutMs)
17
+ }
18
+
19
+ return id
20
+ }
21
+
22
+ export function removeNotification(id: number): void {
23
+ notifications.value = notifications.value.filter((item) => item.id !== id)
24
+ }
25
+
26
+ export function notifyError(error: unknown): void {
27
+ if (error instanceof Error && error.message) {
28
+ pushNotification('error', error.message)
29
+ return
30
+ }
31
+ pushNotification('error', 'Request failed')
32
+ }
33
+
34
+ export function useVaspNotifications() {
35
+ return {
36
+ notifications,
37
+ pushNotification,
38
+ removeNotification,
39
+ notifyError,
40
+ }
41
+ }
@@ -7,6 +7,7 @@ export default defineConfig({
7
7
  resolve: {
8
8
  alias: {
9
9
  '@src': fileURLToPath(new URL('./src', import.meta.url)),
10
+ '@shared': fileURLToPath(new URL('./shared', import.meta.url)),
10
11
  '@vasp-framework/client': fileURLToPath(new URL('./src/vasp/client', import.meta.url)),
11
12
  },
12
13
  },
@@ -0,0 +1,23 @@
1
+ <script setup>
2
+ import { computed } from 'vue'
3
+
4
+ const props = defineProps({
5
+ error: {
6
+ type: Object,
7
+ required: true,
8
+ },
9
+ })
10
+
11
+ const messageText = computed(() => props.error.statusMessage || props.error.message || 'Unexpected server error')
12
+ </script>
13
+
14
+ <template>
15
+ <div style="padding:24px; max-width:720px; margin:0 auto;">
16
+ <h1 v-if="props.error.statusCode === 404">Page not found</h1>
17
+ <h1 v-else-if="props.error.statusCode === 401">Authentication required</h1>
18
+ <h1 v-else>Something went wrong</h1>
19
+
20
+ <p>{{ messageText }}</p>
21
+ <NuxtLink to="/">Go back home</NuxtLink>
22
+ </div>
23
+ </template>
@@ -5,6 +5,7 @@ export default defineNuxtConfig({
5
5
  devtools: { enabled: true },
6
6
  alias: {
7
7
  '@src': '~/src',
8
+ '@shared': '~/shared',
8
9
  },
9
10
  runtimeConfig: {
10
11
  backendUrl: process.env.BACKEND_URL || 'http://localhost:{{backendPort}}',
@@ -2,6 +2,11 @@
2
2
  // Runs only on the client after hydration — routes calls to the Elysia backend via ofetch
3
3
  import { $fetch } from 'ofetch'
4
4
 
5
+ function getCsrfToken() {
6
+ const match = document.cookie.match(/(?:^|;\s*)vasp-csrf=([^;]*)/)
7
+ return match ? decodeURIComponent(match[1]) : undefined
8
+ }
9
+
5
10
  export default defineNuxtPlugin((nuxtApp) => {
6
11
  const config = useRuntimeConfig()
7
12
  const baseURL = config.public.apiBase
@@ -11,7 +16,12 @@ export default defineNuxtPlugin((nuxtApp) => {
11
16
  return $fetch(`/queries/${name}`, { baseURL, method: 'GET', query: args })
12
17
  },
13
18
  async action(name, args) {
14
- return $fetch(`/actions/${name}`, { baseURL, method: 'POST', body: args })
19
+ return $fetch(`/actions/${name}`, {
20
+ baseURL,
21
+ method: 'POST',
22
+ body: args,
23
+ headers: { 'x-csrf-token': getCsrfToken() ?? '' },
24
+ })
15
25
  },
16
26
  })
17
27
  })
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ const props = defineProps<{
5
+ error: {
6
+ statusCode?: number
7
+ statusMessage?: string
8
+ message?: string
9
+ }
10
+ }>()
11
+
12
+ const messageText = computed(() => {
13
+ return props.error.statusMessage ?? props.error.message ?? 'Unexpected server error'
14
+ })
15
+ </script>
16
+
17
+ <template>
18
+ <div style="padding:24px; max-width:720px; margin:0 auto;">
19
+ <h1 v-if="props.error.statusCode === 404">Page not found</h1>
20
+ <h1 v-else-if="props.error.statusCode === 401">Authentication required</h1>
21
+ <h1 v-else>Something went wrong</h1>
22
+
23
+ <p>{{ messageText }}</p>
24
+ <NuxtLink to="/">Go back home</NuxtLink>
25
+ </div>
26
+ </template>
@@ -9,6 +9,7 @@ export default defineNuxtConfig({
9
9
  },
10
10
  alias: {
11
11
  '@src': '~/src',
12
+ '@shared': '~/shared',
12
13
  },
13
14
  runtimeConfig: {
14
15
  backendUrl: process.env.BACKEND_URL || 'http://localhost:{{backendPort}}',
@@ -2,6 +2,11 @@
2
2
  // Runs only on the client after hydration — routes calls to the Elysia backend via ofetch
3
3
  import { $fetch } from 'ofetch'
4
4
 
5
+ function getCsrfToken(): string | undefined {
6
+ const match = document.cookie.match(/(?:^|;\s*)vasp-csrf=([^;]*)/)
7
+ return match ? decodeURIComponent(match[1]) : undefined
8
+ }
9
+
5
10
  export default defineNuxtPlugin((nuxtApp) => {
6
11
  const config = useRuntimeConfig()
7
12
  const baseURL = config.public.apiBase as string
@@ -11,7 +16,12 @@ export default defineNuxtPlugin((nuxtApp) => {
11
16
  return $fetch<T>(`/queries/${name}`, { baseURL, method: 'GET', query: args as Record<string, unknown> })
12
17
  },
13
18
  async action<T = unknown>(name: string, args?: unknown): Promise<T> {
14
- return $fetch<T>(`/actions/${name}`, { baseURL, method: 'POST', body: args })
19
+ return $fetch<T>(`/actions/${name}`, {
20
+ baseURL,
21
+ method: 'POST',
22
+ body: args,
23
+ headers: { 'x-csrf-token': getCsrfToken() ?? '' },
24
+ })
15
25
  },
16
26
  })
17
27
  })
@@ -0,0 +1,15 @@
1
+ app MinimalApp {
2
+ title: "Minimal App"
3
+ db: Drizzle
4
+ ssr: false
5
+ typescript: false
6
+ }
7
+
8
+ route HomeRoute {
9
+ path: "/"
10
+ to: HomePage
11
+ }
12
+
13
+ page HomePage {
14
+ component: import Home from "@src/pages/Home.vue"
15
+ }
@@ -0,0 +1,70 @@
1
+ app RecipeApp {
2
+ title: "Recipe App"
3
+ db: Drizzle
4
+ ssr: false
5
+ typescript: false
6
+ }
7
+
8
+ auth RecipeAuth {
9
+ userEntity: User
10
+ methods: [usernameAndPassword]
11
+ }
12
+
13
+ entity Recipe {
14
+ id: Int @id
15
+ title: String
16
+ description: String
17
+ ingredients: String
18
+ instructions: String
19
+ author: String
20
+ }
21
+
22
+ route HomeRoute {
23
+ path: "/"
24
+ to: HomePage
25
+ }
26
+
27
+ route RecipesRoute {
28
+ path: "/recipes"
29
+ to: RecipesPage
30
+ }
31
+
32
+ route AddRecipeRoute {
33
+ path: "/recipes/new"
34
+ to: AddRecipePage
35
+ }
36
+
37
+ page HomePage {
38
+ component: import HomePage from "@src/pages/HomePage.vue"
39
+ }
40
+
41
+ page RecipesPage {
42
+ component: import RecipesPage from "@src/pages/RecipesPage.vue"
43
+ }
44
+
45
+ page AddRecipePage {
46
+ component: import AddRecipePage from "@src/pages/AddRecipePage.vue"
47
+ }
48
+
49
+ query getRecipes {
50
+ fn: import { getRecipes } from "@src/queries.js"
51
+ entities: [Recipe]
52
+ auth: true
53
+ }
54
+
55
+ action createRecipe {
56
+ fn: import { createRecipe } from "@src/actions.js"
57
+ entities: [Recipe]
58
+ auth: true
59
+ }
60
+
61
+ action deleteRecipe {
62
+ fn: import { deleteRecipe } from "@src/actions.js"
63
+ entities: [Recipe]
64
+ auth: true
65
+ }
66
+
67
+ crud Recipe {
68
+ entity: Recipe
69
+ operations: [list, create, update, delete]
70
+ }
@@ -0,0 +1,65 @@
1
+ app TodoAuthSsrApp {
2
+ title: "Todo App"
3
+ db: Drizzle
4
+ ssr: true
5
+ typescript: false
6
+ }
7
+
8
+ auth TodoAuth {
9
+ userEntity: User
10
+ methods: [usernameAndPassword]
11
+ }
12
+
13
+ entity User {
14
+ id: Int @id
15
+ username: String @unique
16
+ email: String @unique
17
+ }
18
+
19
+ entity Todo {
20
+ id: Int @id
21
+ title: String
22
+ done: Boolean
23
+ createdAt: DateTime @default(now)
24
+ }
25
+
26
+ route HomeRoute {
27
+ path: "/"
28
+ to: HomePage
29
+ }
30
+
31
+ route TodoRoute {
32
+ path: "/todos"
33
+ to: TodoPage
34
+ }
35
+
36
+ page HomePage {
37
+ component: import Home from "@src/pages/Home.vue"
38
+ }
39
+
40
+ page TodoPage {
41
+ component: import TodoList from "@src/pages/TodoList.vue"
42
+ }
43
+
44
+ query getTodos {
45
+ fn: import { getTodos } from "@src/queries.js"
46
+ entities: [Todo]
47
+ auth: true
48
+ }
49
+
50
+ action createTodo {
51
+ fn: import { createTodo } from "@src/actions.js"
52
+ entities: [Todo]
53
+ auth: true
54
+ }
55
+
56
+ action deleteTodo {
57
+ fn: import { deleteTodo } from "@src/actions.js"
58
+ entities: [Todo]
59
+ auth: true
60
+ }
61
+
62
+ crud Todo {
63
+ entity: Todo
64
+ operations: [list, create, update, delete]
65
+ }
@@ -0,0 +1,42 @@
1
+ app TodoApp {
2
+ title: "Todo App"
3
+ db: Drizzle
4
+ ssr: false
5
+ typescript: false
6
+ }
7
+
8
+ entity Todo {
9
+ id: Int @id
10
+ title: String
11
+ done: Boolean
12
+ createdAt: DateTime @default(now)
13
+ }
14
+
15
+ route HomeRoute {
16
+ path: "/"
17
+ to: HomePage
18
+ }
19
+
20
+ page HomePage {
21
+ component: import Home from "@src/pages/Home.vue"
22
+ }
23
+
24
+ query getTodos {
25
+ fn: import { getTodos } from "@src/queries.js"
26
+ entities: [Todo]
27
+ }
28
+
29
+ action createTodo {
30
+ fn: import { createTodo } from "@src/actions.js"
31
+ entities: [Todo]
32
+ }
33
+
34
+ action deleteTodo {
35
+ fn: import { deleteTodo } from "@src/actions.js"
36
+ entities: [Todo]
37
+ }
38
+
39
+ crud Todo {
40
+ entity: Todo
41
+ operations: [list, create, update, delete]
42
+ }
@@ -1,8 +0,0 @@
1
- node_modules
2
- dist
3
- .output
4
- .nuxt
5
- .vasp-gen
6
- .env
7
- .env.local
8
- *.log
@@ -1,46 +0,0 @@
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 || err?.message || 'Login failed'
42
- } finally {
43
- loading.value = false
44
- }
45
- }
46
- </script>
@@ -1,42 +0,0 @@
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 || err?.message || 'Registration failed'
38
- } finally {
39
- loading.value = false
40
- }
41
- }
42
- </script>