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.
- package/README.md +57 -2
- package/dist/vasp +201 -35
- package/package.json +2 -2
- package/starters/minimal.vasp +1 -1
- package/starters/recipe.vasp +11 -20
- package/starters/todo-auth-ssr.vasp +33 -20
- package/starters/todo.vasp +15 -8
- package/templates/shared/.gitignore.hbs +1 -0
- package/templates/shared/auth/server/index.hbs +4 -8
- package/templates/shared/auth/server/middleware.hbs +33 -15
- package/templates/{templates/shared → shared}/auth/server/plugin.hbs +0 -2
- package/templates/shared/auth/server/providers/github.hbs +1 -1
- package/templates/shared/auth/server/providers/google.hbs +1 -1
- package/templates/shared/auth/server/providers/usernameAndPassword.hbs +3 -6
- package/templates/shared/bunfig.toml.hbs +3 -0
- package/templates/shared/drizzle/schema.hbs +38 -19
- package/templates/shared/package.json.hbs +11 -3
- package/templates/shared/server/db/client.hbs +19 -1
- package/templates/shared/server/db/seed.hbs +16 -0
- package/templates/shared/server/index.hbs +47 -0
- package/templates/shared/server/middleware/errorHandler.hbs +75 -0
- package/templates/shared/server/middleware/logger.hbs +74 -0
- package/templates/shared/server/middleware/rateLimit.hbs +2 -2
- package/templates/shared/server/routes/_vasp.hbs +37 -0
- package/templates/shared/server/routes/actions/_action.hbs +5 -1
- package/templates/shared/server/routes/api/_api.hbs +24 -0
- package/templates/shared/server/routes/crud/_crud.hbs +58 -10
- package/templates/shared/server/routes/queries/_query.hbs +5 -1
- package/templates/shared/shared/types.hbs +58 -0
- package/templates/shared/shared/validation.hbs +20 -0
- package/templates/shared/tests/actions/_action.test.js.hbs +7 -0
- package/templates/shared/tests/actions/_action.test.ts.hbs +7 -0
- package/templates/shared/tests/auth/login.test.js.hbs +7 -0
- package/templates/shared/tests/auth/login.test.ts.hbs +7 -0
- package/templates/shared/tests/crud/_entity.test.js.hbs +7 -0
- package/templates/shared/tests/crud/_entity.test.ts.hbs +7 -0
- package/templates/shared/tests/queries/_query.test.js.hbs +7 -0
- package/templates/shared/tests/queries/_query.test.ts.hbs +7 -0
- package/templates/shared/tests/setup.js.hbs +5 -0
- package/templates/shared/tests/setup.ts.hbs +5 -0
- package/templates/shared/tests/vitest.config.js.hbs +8 -0
- package/templates/shared/tests/vitest.config.ts.hbs +8 -0
- package/templates/shared/tsconfig.json.hbs +2 -1
- package/templates/spa/js/src/App.vue.hbs +9 -1
- package/templates/spa/js/src/components/VaspErrorBoundary.vue.hbs +33 -0
- package/templates/spa/js/src/components/VaspNotifications.vue.hbs +60 -0
- package/templates/spa/js/src/vasp/auth.js.hbs +31 -15
- package/templates/spa/js/src/vasp/client/actions.js.hbs +7 -1
- package/templates/spa/js/src/vasp/client/crud.js.hbs +94 -5
- package/templates/spa/js/src/vasp/useVaspNotifications.js.hbs +35 -0
- package/templates/spa/js/vite.config.js.hbs +1 -0
- package/templates/spa/ts/src/App.vue.hbs +9 -1
- package/templates/spa/ts/src/components/VaspErrorBoundary.vue.hbs +33 -0
- package/templates/spa/ts/src/components/VaspNotifications.vue.hbs +60 -0
- package/templates/spa/ts/src/vasp/auth.ts.hbs +31 -15
- package/templates/spa/ts/src/vasp/client/actions.ts.hbs +7 -1
- package/templates/spa/ts/src/vasp/client/crud.ts.hbs +96 -10
- package/templates/spa/ts/src/vasp/client/types.ts.hbs +14 -28
- package/templates/spa/ts/src/vasp/useVaspNotifications.ts.hbs +41 -0
- package/templates/spa/ts/vite.config.ts.hbs +1 -0
- package/templates/ssr/js/error.vue.hbs +23 -0
- package/templates/ssr/js/nuxt.config.js.hbs +1 -0
- package/templates/ssr/ts/error.vue.hbs +26 -0
- package/templates/ssr/ts/nuxt.config.ts.hbs +1 -0
- package/templates/starters/minimal.vasp +15 -0
- package/templates/starters/recipe.vasp +70 -0
- package/templates/starters/todo-auth-ssr.vasp +65 -0
- package/templates/starters/todo.vasp +42 -0
- package/templates/templates/shared/.env.example.hbs +0 -14
- package/templates/templates/shared/.gitignore.hbs +0 -8
- package/templates/templates/shared/auth/client/Login.vue.hbs +0 -46
- package/templates/templates/shared/auth/client/Register.vue.hbs +0 -42
- package/templates/templates/shared/auth/server/index.hbs +0 -46
- package/templates/templates/shared/auth/server/middleware.hbs +0 -33
- package/templates/templates/shared/auth/server/providers/github.hbs +0 -48
- package/templates/templates/shared/auth/server/providers/google.hbs +0 -53
- package/templates/templates/shared/auth/server/providers/usernameAndPassword.hbs +0 -66
- package/templates/templates/shared/bunfig.toml.hbs +0 -5
- package/templates/templates/shared/drizzle/drizzle.config.hbs +0 -10
- package/templates/templates/shared/drizzle/schema.hbs +0 -48
- package/templates/templates/shared/jobs/_job.hbs +0 -34
- package/templates/templates/shared/jobs/boss.hbs +0 -15
- package/templates/templates/shared/package.json.hbs +0 -38
- package/templates/templates/shared/server/db/client.hbs +0 -12
- package/templates/templates/shared/server/index.hbs +0 -73
- package/templates/templates/shared/server/middleware/csrf.hbs +0 -34
- package/templates/templates/shared/server/middleware/rateLimit.hbs +0 -44
- package/templates/templates/shared/server/routes/actions/_action.hbs +0 -20
- package/templates/templates/shared/server/routes/crud/_crud.hbs +0 -86
- package/templates/templates/shared/server/routes/jobs/_schedule.hbs +0 -12
- package/templates/templates/shared/server/routes/queries/_query.hbs +0 -20
- package/templates/templates/shared/server/routes/realtime/_channel.hbs +0 -78
- package/templates/templates/shared/server/routes/realtime/index.hbs +0 -9
- package/templates/templates/shared/tsconfig.json.hbs +0 -21
- package/templates/templates/spa/js/index.html.hbs +0 -12
- package/templates/templates/spa/js/src/App.vue.hbs +0 -3
- package/templates/templates/spa/js/src/main.js.hbs +0 -9
- package/templates/templates/spa/js/src/router/index.js.hbs +0 -41
- package/templates/templates/spa/js/src/vasp/auth.js.hbs +0 -45
- package/templates/templates/spa/js/src/vasp/client/actions.js.hbs +0 -15
- package/templates/templates/spa/js/src/vasp/client/crud.js.hbs +0 -30
- package/templates/templates/spa/js/src/vasp/client/index.js.hbs +0 -16
- package/templates/templates/spa/js/src/vasp/client/queries.js.hbs +0 -15
- package/templates/templates/spa/js/src/vasp/client/realtime.js.hbs +0 -51
- package/templates/templates/spa/js/src/vasp/plugin.js.hbs +0 -11
- package/templates/templates/spa/js/vite.config.js.hbs +0 -26
- package/templates/templates/spa/ts/index.html.hbs +0 -12
- package/templates/templates/spa/ts/src/App.vue.hbs +0 -3
- package/templates/templates/spa/ts/src/main.ts.hbs +0 -9
- package/templates/templates/spa/ts/src/router/index.ts.hbs +0 -41
- package/templates/templates/spa/ts/src/vasp/auth.ts.hbs +0 -53
- package/templates/templates/spa/ts/src/vasp/client/actions.ts.hbs +0 -19
- package/templates/templates/spa/ts/src/vasp/client/crud.ts.hbs +0 -37
- package/templates/templates/spa/ts/src/vasp/client/index.ts.hbs +0 -17
- package/templates/templates/spa/ts/src/vasp/client/queries.ts.hbs +0 -19
- package/templates/templates/spa/ts/src/vasp/client/realtime.ts.hbs +0 -56
- package/templates/templates/spa/ts/src/vasp/client/types.ts.hbs +0 -33
- package/templates/templates/spa/ts/src/vasp/plugin.ts.hbs +0 -12
- package/templates/templates/spa/ts/vite.config.ts.hbs +0 -26
- package/templates/templates/ssr/js/_page.vue.hbs +0 -10
- package/templates/templates/ssr/js/app.vue.hbs +0 -3
- package/templates/templates/ssr/js/composables/useAuth.js.hbs +0 -52
- package/templates/templates/ssr/js/composables/useVasp.js.hbs +0 -6
- package/templates/templates/ssr/js/middleware/auth.js.hbs +0 -8
- package/templates/templates/ssr/js/nuxt.config.js.hbs +0 -15
- package/templates/templates/ssr/js/plugins/vasp.client.js.hbs +0 -27
- package/templates/templates/ssr/js/plugins/vasp.server.js.hbs +0 -33
- package/templates/templates/ssr/ts/_page.vue.hbs +0 -10
- package/templates/templates/ssr/ts/app.vue.hbs +0 -3
- package/templates/templates/ssr/ts/composables/useAuth.ts.hbs +0 -56
- package/templates/templates/ssr/ts/composables/useVasp.ts.hbs +0 -10
- package/templates/templates/ssr/ts/middleware/auth.ts.hbs +0 -8
- package/templates/templates/ssr/ts/nuxt.config.ts.hbs +0 -19
- package/templates/templates/ssr/ts/plugins/vasp.client.ts.hbs +0 -27
- package/templates/templates/ssr/ts/plugins/vasp.server.ts.hbs +0 -33
- /package/templates/{templates/shared → shared}/.env.hbs +0 -0
- /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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}}[]> =>
|
|
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:
|
|
22
|
-
|
|
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
|
-
|
|
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:
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
export type {{pascalCase name}}Return = unknown
|
|
16
|
-
|
|
12
|
+
{{pascalCase name}}Args,
|
|
13
|
+
{{pascalCase name}}Return,
|
|
17
14
|
{{/each}}
|
|
18
15
|
{{#each actions}}
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
31
|
-
User,
|
|
32
|
-
{{/if}}
|
|
33
|
-
}
|
|
19
|
+
} from '@shared/types.js'
|