vasp-cli 0.4.0 → 1.0.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 (137) hide show
  1. package/README.md +57 -2
  2. package/dist/vasp +201 -35
  3. package/package.json +2 -2
  4. package/starters/minimal.vasp +1 -1
  5. package/starters/recipe.vasp +11 -20
  6. package/starters/todo-auth-ssr.vasp +33 -20
  7. package/starters/todo.vasp +15 -8
  8. package/templates/shared/.gitignore.hbs +1 -0
  9. package/templates/shared/auth/server/index.hbs +4 -8
  10. package/templates/shared/auth/server/middleware.hbs +33 -15
  11. package/templates/{templates/shared → shared}/auth/server/plugin.hbs +0 -2
  12. package/templates/shared/auth/server/providers/github.hbs +1 -1
  13. package/templates/shared/auth/server/providers/google.hbs +1 -1
  14. package/templates/shared/auth/server/providers/usernameAndPassword.hbs +3 -6
  15. package/templates/shared/bunfig.toml.hbs +3 -0
  16. package/templates/shared/drizzle/schema.hbs +38 -19
  17. package/templates/shared/package.json.hbs +11 -3
  18. package/templates/shared/server/db/client.hbs +19 -1
  19. package/templates/shared/server/db/seed.hbs +16 -0
  20. package/templates/shared/server/index.hbs +47 -0
  21. package/templates/shared/server/middleware/errorHandler.hbs +75 -0
  22. package/templates/shared/server/middleware/logger.hbs +74 -0
  23. package/templates/shared/server/middleware/rateLimit.hbs +2 -2
  24. package/templates/shared/server/routes/_vasp.hbs +37 -0
  25. package/templates/shared/server/routes/actions/_action.hbs +5 -1
  26. package/templates/shared/server/routes/api/_api.hbs +24 -0
  27. package/templates/shared/server/routes/crud/_crud.hbs +58 -10
  28. package/templates/shared/server/routes/queries/_query.hbs +5 -1
  29. package/templates/shared/shared/types.hbs +58 -0
  30. package/templates/shared/shared/validation.hbs +20 -0
  31. package/templates/shared/tests/actions/_action.test.js.hbs +7 -0
  32. package/templates/shared/tests/actions/_action.test.ts.hbs +7 -0
  33. package/templates/shared/tests/auth/login.test.js.hbs +7 -0
  34. package/templates/shared/tests/auth/login.test.ts.hbs +7 -0
  35. package/templates/shared/tests/crud/_entity.test.js.hbs +7 -0
  36. package/templates/shared/tests/crud/_entity.test.ts.hbs +7 -0
  37. package/templates/shared/tests/queries/_query.test.js.hbs +7 -0
  38. package/templates/shared/tests/queries/_query.test.ts.hbs +7 -0
  39. package/templates/shared/tests/setup.js.hbs +5 -0
  40. package/templates/shared/tests/setup.ts.hbs +5 -0
  41. package/templates/shared/tests/vitest.config.js.hbs +8 -0
  42. package/templates/shared/tests/vitest.config.ts.hbs +8 -0
  43. package/templates/shared/tsconfig.json.hbs +2 -1
  44. package/templates/spa/js/src/App.vue.hbs +9 -1
  45. package/templates/spa/js/src/components/VaspErrorBoundary.vue.hbs +33 -0
  46. package/templates/spa/js/src/components/VaspNotifications.vue.hbs +60 -0
  47. package/templates/spa/js/src/vasp/auth.js.hbs +31 -15
  48. package/templates/spa/js/src/vasp/client/actions.js.hbs +7 -1
  49. package/templates/spa/js/src/vasp/client/crud.js.hbs +94 -5
  50. package/templates/spa/js/src/vasp/useVaspNotifications.js.hbs +35 -0
  51. package/templates/spa/js/vite.config.js.hbs +1 -0
  52. package/templates/spa/ts/src/App.vue.hbs +9 -1
  53. package/templates/spa/ts/src/components/VaspErrorBoundary.vue.hbs +33 -0
  54. package/templates/spa/ts/src/components/VaspNotifications.vue.hbs +60 -0
  55. package/templates/spa/ts/src/vasp/auth.ts.hbs +31 -15
  56. package/templates/spa/ts/src/vasp/client/actions.ts.hbs +7 -1
  57. package/templates/spa/ts/src/vasp/client/crud.ts.hbs +96 -10
  58. package/templates/spa/ts/src/vasp/client/types.ts.hbs +14 -28
  59. package/templates/spa/ts/src/vasp/useVaspNotifications.ts.hbs +41 -0
  60. package/templates/spa/ts/vite.config.ts.hbs +1 -0
  61. package/templates/ssr/js/error.vue.hbs +23 -0
  62. package/templates/ssr/js/nuxt.config.js.hbs +1 -0
  63. package/templates/ssr/ts/error.vue.hbs +26 -0
  64. package/templates/ssr/ts/nuxt.config.ts.hbs +1 -0
  65. package/templates/starters/minimal.vasp +15 -0
  66. package/templates/starters/recipe.vasp +70 -0
  67. package/templates/starters/todo-auth-ssr.vasp +65 -0
  68. package/templates/starters/todo.vasp +42 -0
  69. package/templates/templates/shared/.env.example.hbs +0 -14
  70. package/templates/templates/shared/.gitignore.hbs +0 -8
  71. package/templates/templates/shared/auth/client/Login.vue.hbs +0 -46
  72. package/templates/templates/shared/auth/client/Register.vue.hbs +0 -42
  73. package/templates/templates/shared/auth/server/index.hbs +0 -46
  74. package/templates/templates/shared/auth/server/middleware.hbs +0 -33
  75. package/templates/templates/shared/auth/server/providers/github.hbs +0 -48
  76. package/templates/templates/shared/auth/server/providers/google.hbs +0 -53
  77. package/templates/templates/shared/auth/server/providers/usernameAndPassword.hbs +0 -66
  78. package/templates/templates/shared/bunfig.toml.hbs +0 -5
  79. package/templates/templates/shared/drizzle/drizzle.config.hbs +0 -10
  80. package/templates/templates/shared/drizzle/schema.hbs +0 -48
  81. package/templates/templates/shared/jobs/_job.hbs +0 -34
  82. package/templates/templates/shared/jobs/boss.hbs +0 -15
  83. package/templates/templates/shared/package.json.hbs +0 -38
  84. package/templates/templates/shared/server/db/client.hbs +0 -12
  85. package/templates/templates/shared/server/index.hbs +0 -73
  86. package/templates/templates/shared/server/middleware/csrf.hbs +0 -34
  87. package/templates/templates/shared/server/middleware/rateLimit.hbs +0 -44
  88. package/templates/templates/shared/server/routes/actions/_action.hbs +0 -20
  89. package/templates/templates/shared/server/routes/crud/_crud.hbs +0 -86
  90. package/templates/templates/shared/server/routes/jobs/_schedule.hbs +0 -12
  91. package/templates/templates/shared/server/routes/queries/_query.hbs +0 -20
  92. package/templates/templates/shared/server/routes/realtime/_channel.hbs +0 -78
  93. package/templates/templates/shared/server/routes/realtime/index.hbs +0 -9
  94. package/templates/templates/shared/tsconfig.json.hbs +0 -21
  95. package/templates/templates/spa/js/index.html.hbs +0 -12
  96. package/templates/templates/spa/js/src/App.vue.hbs +0 -3
  97. package/templates/templates/spa/js/src/main.js.hbs +0 -9
  98. package/templates/templates/spa/js/src/router/index.js.hbs +0 -41
  99. package/templates/templates/spa/js/src/vasp/auth.js.hbs +0 -45
  100. package/templates/templates/spa/js/src/vasp/client/actions.js.hbs +0 -15
  101. package/templates/templates/spa/js/src/vasp/client/crud.js.hbs +0 -30
  102. package/templates/templates/spa/js/src/vasp/client/index.js.hbs +0 -16
  103. package/templates/templates/spa/js/src/vasp/client/queries.js.hbs +0 -15
  104. package/templates/templates/spa/js/src/vasp/client/realtime.js.hbs +0 -51
  105. package/templates/templates/spa/js/src/vasp/plugin.js.hbs +0 -11
  106. package/templates/templates/spa/js/vite.config.js.hbs +0 -26
  107. package/templates/templates/spa/ts/index.html.hbs +0 -12
  108. package/templates/templates/spa/ts/src/App.vue.hbs +0 -3
  109. package/templates/templates/spa/ts/src/main.ts.hbs +0 -9
  110. package/templates/templates/spa/ts/src/router/index.ts.hbs +0 -41
  111. package/templates/templates/spa/ts/src/vasp/auth.ts.hbs +0 -53
  112. package/templates/templates/spa/ts/src/vasp/client/actions.ts.hbs +0 -19
  113. package/templates/templates/spa/ts/src/vasp/client/crud.ts.hbs +0 -37
  114. package/templates/templates/spa/ts/src/vasp/client/index.ts.hbs +0 -17
  115. package/templates/templates/spa/ts/src/vasp/client/queries.ts.hbs +0 -19
  116. package/templates/templates/spa/ts/src/vasp/client/realtime.ts.hbs +0 -56
  117. package/templates/templates/spa/ts/src/vasp/client/types.ts.hbs +0 -33
  118. package/templates/templates/spa/ts/src/vasp/plugin.ts.hbs +0 -12
  119. package/templates/templates/spa/ts/vite.config.ts.hbs +0 -26
  120. package/templates/templates/ssr/js/_page.vue.hbs +0 -10
  121. package/templates/templates/ssr/js/app.vue.hbs +0 -3
  122. package/templates/templates/ssr/js/composables/useAuth.js.hbs +0 -52
  123. package/templates/templates/ssr/js/composables/useVasp.js.hbs +0 -6
  124. package/templates/templates/ssr/js/middleware/auth.js.hbs +0 -8
  125. package/templates/templates/ssr/js/nuxt.config.js.hbs +0 -15
  126. package/templates/templates/ssr/js/plugins/vasp.client.js.hbs +0 -27
  127. package/templates/templates/ssr/js/plugins/vasp.server.js.hbs +0 -33
  128. package/templates/templates/ssr/ts/_page.vue.hbs +0 -10
  129. package/templates/templates/ssr/ts/app.vue.hbs +0 -3
  130. package/templates/templates/ssr/ts/composables/useAuth.ts.hbs +0 -56
  131. package/templates/templates/ssr/ts/composables/useVasp.ts.hbs +0 -10
  132. package/templates/templates/ssr/ts/middleware/auth.ts.hbs +0 -8
  133. package/templates/templates/ssr/ts/nuxt.config.ts.hbs +0 -19
  134. package/templates/templates/ssr/ts/plugins/vasp.client.ts.hbs +0 -27
  135. package/templates/templates/ssr/ts/plugins/vasp.server.ts.hbs +0 -33
  136. /package/templates/{templates/shared → shared}/.env.hbs +0 -0
  137. /package/templates/{templates/shared → shared}/README.md.hbs +0 -0
@@ -0,0 +1,60 @@
1
+ <script setup>
2
+ import { notifications, removeNotification } from '../vasp/useVaspNotifications.js'
3
+ </script>
4
+
5
+ <template>
6
+ <div class="vasp-notifications">
7
+ <div
8
+ v-for="item in notifications"
9
+ :key="item.id"
10
+ class="vasp-notification"
11
+ :class="`is-${item.type}`"
12
+ @click="removeNotification(item.id)"
13
+ >
14
+ {{ item.message }}
15
+ </div>
16
+ </div>
17
+ </template>
18
+
19
+ <style scoped>
20
+ .vasp-notifications {
21
+ position: fixed;
22
+ top: 16px;
23
+ right: 16px;
24
+ z-index: 1000;
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: 8px;
28
+ }
29
+
30
+ .vasp-notification {
31
+ min-width: 220px;
32
+ padding: 10px 12px;
33
+ border-radius: 8px;
34
+ color: #111827;
35
+ background: #e5e7eb;
36
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
37
+ cursor: pointer;
38
+ animation: slide-in 0.2s ease-out;
39
+ }
40
+
41
+ .vasp-notification.is-error {
42
+ background: #fee2e2;
43
+ color: #991b1b;
44
+ }
45
+
46
+ .vasp-notification.is-success {
47
+ background: #dcfce7;
48
+ color: #166534;
49
+ }
50
+
51
+ .vasp-notification.is-info {
52
+ background: #dbeafe;
53
+ color: #1e40af;
54
+ }
55
+
56
+ @keyframes slide-in {
57
+ from { transform: translateX(20px); opacity: 0; }
58
+ to { transform: translateX(0); opacity: 1; }
59
+ }
60
+ </style>
@@ -1,5 +1,6 @@
1
1
  import { ref } from 'vue'
2
2
  import { $fetch } from 'ofetch'
3
+ import { notifyError } from './useVaspNotifications.js'
3
4
 
4
5
  const user = ref(null)
5
6
  let checked = false
@@ -18,27 +19,42 @@ export function useAuth() {
18
19
  }
19
20
 
20
21
  async function login(username, password) {
21
- user.value = await $fetch(`${API}/auth/login`, {
22
- method: 'POST',
23
- body: { username, password },
24
- credentials: 'include',
25
- })
26
- return user.value
22
+ try {
23
+ user.value = await $fetch(`${API}/auth/login`, {
24
+ method: 'POST',
25
+ body: { username, password },
26
+ credentials: 'include',
27
+ })
28
+ return user.value
29
+ } catch (error) {
30
+ notifyError(error)
31
+ throw error
32
+ }
27
33
  }
28
34
 
29
35
  async function register(username, password, email) {
30
- user.value = await $fetch(`${API}/auth/register`, {
31
- method: 'POST',
32
- body: { username, password, email },
33
- credentials: 'include',
34
- })
35
- return user.value
36
+ try {
37
+ user.value = await $fetch(`${API}/auth/register`, {
38
+ method: 'POST',
39
+ body: { username, password, email },
40
+ credentials: 'include',
41
+ })
42
+ return user.value
43
+ } catch (error) {
44
+ notifyError(error)
45
+ throw error
46
+ }
36
47
  }
37
48
 
38
49
  async function logout() {
39
- await $fetch(`${API}/auth/logout`, { method: 'POST', credentials: 'include' })
40
- user.value = null
41
- checked = false
50
+ try {
51
+ await $fetch(`${API}/auth/logout`, { method: 'POST', credentials: 'include' })
52
+ user.value = null
53
+ checked = false
54
+ } catch (error) {
55
+ notifyError(error)
56
+ throw error
57
+ }
42
58
  }
43
59
 
44
60
  return { user, checkAuth, login, register, logout }
@@ -1,5 +1,6 @@
1
1
  // Auto-generated by Vasp — do not edit
2
2
  import { useVasp } from '@vasp-framework/runtime'
3
+ import { notifyError } from '../useVaspNotifications.js'
3
4
 
4
5
  {{#each actions}}
5
6
  /**
@@ -9,7 +10,12 @@ import { useVasp } from '@vasp-framework/runtime'
9
10
  */
10
11
  export async function {{camelCase name}}(args) {
11
12
  const { $vasp } = useVasp()
12
- return $vasp.action('{{camelCase name}}', args)
13
+ try {
14
+ return await $vasp.action('{{camelCase name}}', args)
15
+ } catch (error) {
16
+ notifyError(error)
17
+ throw error
18
+ }
13
19
  }
14
20
 
15
21
  {{/each}}
@@ -1,5 +1,14 @@
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'
5
+ import {
6
+ {{#each cruds}}
7
+ Create{{pascalCase entity}}Schema,
8
+ Update{{pascalCase entity}}Schema,
9
+ {{/each}}
10
+ } from '@shared/validation.js'
11
+ import { notifyError } from '../useVaspNotifications.js'
3
12
 
4
13
  const API = import.meta.env.VITE_API_URL || '/api'
5
14
 
@@ -9,20 +18,100 @@ const API = import.meta.env.VITE_API_URL || '/api'
9
18
  */
10
19
  export function use{{pascalCase entity}}Crud() {
11
20
  const base = `${API}/crud/{{camelCase entity}}`
21
+ const loading = ref(false)
22
+ const error = ref(null)
23
+
24
+ const fail = (err) => {
25
+ error.value = err instanceof Error ? err.message : 'Request failed'
26
+ notifyError(err)
27
+ }
12
28
 
13
29
  return {
30
+ loading,
31
+ error,
14
32
  {{#if (includes operations "list")}}
15
- list: () => $fetch(base, { credentials: 'include' }),
33
+ list: async () => {
34
+ loading.value = true
35
+ error.value = null
36
+ try {
37
+ return await $fetch(base, { credentials: 'include' })
38
+ } catch (err) {
39
+ fail(err)
40
+ throw err
41
+ } finally {
42
+ loading.value = false
43
+ }
44
+ },
16
45
  {{/if}}
17
46
  {{#if (includes operations "create")}}
18
- create: (data) => $fetch(base, { method: 'POST', body: data, credentials: 'include' }),
47
+ create: async (data) => {
48
+ const parsed = safeParse(Create{{pascalCase entity}}Schema, data)
49
+ if (!parsed.success) {
50
+ const msg = parsed.issues?.[0]?.message ?? 'Invalid input'
51
+ const err = new Error(msg)
52
+ fail(err)
53
+ throw err
54
+ }
55
+
56
+ loading.value = true
57
+ error.value = null
58
+ try {
59
+ return await $fetch(base, { method: 'POST', body: parsed.output, credentials: 'include' })
60
+ } catch (err) {
61
+ fail(err)
62
+ throw err
63
+ } finally {
64
+ loading.value = false
65
+ }
66
+ },
19
67
  {{/if}}
20
- get: (id) => $fetch(`${base}/${id}`, { credentials: 'include' }),
68
+ get: async (id) => {
69
+ loading.value = true
70
+ error.value = null
71
+ try {
72
+ return await $fetch(`${base}/${id}`, { credentials: 'include' })
73
+ } catch (err) {
74
+ fail(err)
75
+ throw err
76
+ } finally {
77
+ loading.value = false
78
+ }
79
+ },
21
80
  {{#if (includes operations "update")}}
22
- update: (id, data) => $fetch(`${base}/${id}`, { method: 'PUT', body: data, credentials: 'include' }),
81
+ update: async (id, data) => {
82
+ const parsed = safeParse(Update{{pascalCase entity}}Schema, data)
83
+ if (!parsed.success) {
84
+ const msg = parsed.issues?.[0]?.message ?? 'Invalid input'
85
+ const err = new Error(msg)
86
+ fail(err)
87
+ throw err
88
+ }
89
+
90
+ loading.value = true
91
+ error.value = null
92
+ try {
93
+ return await $fetch(`${base}/${id}`, { method: 'PUT', body: parsed.output, credentials: 'include' })
94
+ } catch (err) {
95
+ fail(err)
96
+ throw err
97
+ } finally {
98
+ loading.value = false
99
+ }
100
+ },
23
101
  {{/if}}
24
102
  {{#if (includes operations "delete")}}
25
- remove: (id) => $fetch(`${base}/${id}`, { method: 'DELETE', credentials: 'include' }),
103
+ remove: async (id) => {
104
+ loading.value = true
105
+ error.value = null
106
+ try {
107
+ return await $fetch(`${base}/${id}`, { method: 'DELETE', credentials: 'include' })
108
+ } catch (err) {
109
+ fail(err)
110
+ throw err
111
+ } finally {
112
+ loading.value = false
113
+ }
114
+ },
26
115
  {{/if}}
27
116
  }
28
117
  }
@@ -0,0 +1,35 @@
1
+ import { ref } from 'vue'
2
+
3
+ export const notifications = ref([])
4
+
5
+ export function pushNotification(type, message, timeoutMs = 3500) {
6
+ const id = Date.now() + Math.floor(Math.random() * 1000)
7
+ notifications.value.push({ id, type, message })
8
+
9
+ if (timeoutMs > 0) {
10
+ setTimeout(() => removeNotification(id), timeoutMs)
11
+ }
12
+
13
+ return id
14
+ }
15
+
16
+ export function removeNotification(id) {
17
+ notifications.value = notifications.value.filter((item) => item.id !== id)
18
+ }
19
+
20
+ export function notifyError(error) {
21
+ if (error instanceof Error && error.message) {
22
+ pushNotification('error', error.message)
23
+ return
24
+ }
25
+ pushNotification('error', 'Request failed')
26
+ }
27
+
28
+ export function useVaspNotifications() {
29
+ return {
30
+ notifications,
31
+ pushNotification,
32
+ removeNotification,
33
+ notifyError,
34
+ }
35
+ }
@@ -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
  },
@@ -1,3 +1,11 @@
1
+ <script setup lang="ts">
2
+ import VaspErrorBoundary from './components/VaspErrorBoundary.vue'
3
+ import VaspNotifications from './components/VaspNotifications.vue'
4
+ </script>
5
+
1
6
  <template>
2
- <RouterView />
7
+ <VaspErrorBoundary>
8
+ <RouterView />
9
+ </VaspErrorBoundary>
10
+ <VaspNotifications />
3
11
  </template>
@@ -0,0 +1,33 @@
1
+ <script setup lang="ts">
2
+ import { onErrorCaptured, ref } from 'vue'
3
+
4
+ const hasError = ref(false)
5
+ const message = ref('Something went wrong')
6
+
7
+ onErrorCaptured((error) => {
8
+ hasError.value = true
9
+ message.value = import.meta.env.DEV && error instanceof Error
10
+ ? error.message
11
+ : 'Something went wrong'
12
+ return false
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <div v-if="hasError" class="vasp-error-boundary">
18
+ <h2>Unexpected error</h2>
19
+ <p>{{ message }}</p>
20
+ </div>
21
+ <slot v-else />
22
+ </template>
23
+
24
+ <style scoped>
25
+ .vasp-error-boundary {
26
+ margin: 24px;
27
+ padding: 16px;
28
+ border: 1px solid #ef4444;
29
+ border-radius: 8px;
30
+ background: #fef2f2;
31
+ color: #991b1b;
32
+ }
33
+ </style>
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import { notifications, removeNotification } from '../vasp/useVaspNotifications.js'
3
+ </script>
4
+
5
+ <template>
6
+ <div class="vasp-notifications">
7
+ <div
8
+ v-for="item in notifications"
9
+ :key="item.id"
10
+ class="vasp-notification"
11
+ :class="`is-${item.type}`"
12
+ @click="removeNotification(item.id)"
13
+ >
14
+ {{ item.message }}
15
+ </div>
16
+ </div>
17
+ </template>
18
+
19
+ <style scoped>
20
+ .vasp-notifications {
21
+ position: fixed;
22
+ top: 16px;
23
+ right: 16px;
24
+ z-index: 1000;
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: 8px;
28
+ }
29
+
30
+ .vasp-notification {
31
+ min-width: 220px;
32
+ padding: 10px 12px;
33
+ border-radius: 8px;
34
+ color: #111827;
35
+ background: #e5e7eb;
36
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
37
+ cursor: pointer;
38
+ animation: slide-in 0.2s ease-out;
39
+ }
40
+
41
+ .vasp-notification.is-error {
42
+ background: #fee2e2;
43
+ color: #991b1b;
44
+ }
45
+
46
+ .vasp-notification.is-success {
47
+ background: #dcfce7;
48
+ color: #166534;
49
+ }
50
+
51
+ .vasp-notification.is-info {
52
+ background: #dbeafe;
53
+ color: #1e40af;
54
+ }
55
+
56
+ @keyframes slide-in {
57
+ from { transform: translateX(20px); opacity: 0; }
58
+ to { transform: translateX(0); opacity: 1; }
59
+ }
60
+ </style>
@@ -1,5 +1,6 @@
1
1
  import { ref, type Ref } from 'vue'
2
2
  import { $fetch } from 'ofetch'
3
+ import { notifyError } from './useVaspNotifications.js'
3
4
 
4
5
  export interface AuthUser {
5
6
  id: number
@@ -26,27 +27,42 @@ export function useAuth() {
26
27
  }
27
28
 
28
29
  async function login(username: string, password: string): Promise<AuthUser> {
29
- user.value = await $fetch<AuthUser>(`${API}/auth/login`, {
30
- method: 'POST',
31
- body: { username, password },
32
- credentials: 'include',
33
- })
34
- return user.value!
30
+ try {
31
+ user.value = await $fetch<AuthUser>(`${API}/auth/login`, {
32
+ method: 'POST',
33
+ body: { username, password },
34
+ credentials: 'include',
35
+ })
36
+ return user.value!
37
+ } catch (error) {
38
+ notifyError(error)
39
+ throw error
40
+ }
35
41
  }
36
42
 
37
43
  async function register(username: string, password: string, email?: string): Promise<AuthUser> {
38
- user.value = await $fetch<AuthUser>(`${API}/auth/register`, {
39
- method: 'POST',
40
- body: { username, password, email },
41
- credentials: 'include',
42
- })
43
- return user.value!
44
+ try {
45
+ user.value = await $fetch<AuthUser>(`${API}/auth/register`, {
46
+ method: 'POST',
47
+ body: { username, password, email },
48
+ credentials: 'include',
49
+ })
50
+ return user.value!
51
+ } catch (error) {
52
+ notifyError(error)
53
+ throw error
54
+ }
44
55
  }
45
56
 
46
57
  async function logout(): Promise<void> {
47
- await $fetch(`${API}/auth/logout`, { method: 'POST', credentials: 'include' })
48
- user.value = null
49
- checked = false
58
+ try {
59
+ await $fetch(`${API}/auth/logout`, { method: 'POST', credentials: 'include' })
60
+ user.value = null
61
+ checked = false
62
+ } catch (error) {
63
+ notifyError(error)
64
+ throw error
65
+ }
50
66
  }
51
67
 
52
68
  return { user, checkAuth, login, register, logout }
@@ -1,5 +1,6 @@
1
1
  // Auto-generated by Vasp — do not edit
2
2
  import { useVasp } from '@vasp-framework/runtime'
3
+ import { notifyError } from '../useVaspNotifications.js'
3
4
  import type {
4
5
  {{#each actions}}
5
6
  {{pascalCase name}}Args,
@@ -13,7 +14,12 @@ import type {
13
14
  */
14
15
  export async function {{camelCase name}}(args: {{pascalCase name}}Args): Promise<{{pascalCase name}}Return> {
15
16
  const { $vasp } = useVasp()
16
- return $vasp.action('{{camelCase name}}', args) as Promise<{{pascalCase name}}Return>
17
+ try {
18
+ return await $vasp.action('{{camelCase name}}', args) as Promise<{{pascalCase name}}Return>
19
+ } catch (error) {
20
+ notifyError(error)
21
+ throw error
22
+ }
17
23
  }
18
24
 
19
25
  {{/each}}
@@ -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'