vasp-cli 0.1.2 → 0.1.3

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 (66) hide show
  1. package/dist/vasp +23 -18
  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
@@ -0,0 +1,20 @@
1
+ import { Elysia } from 'elysia'
2
+ import { db } from '../../db/client.{{ext}}'
3
+ {{#if requiresAuth}}
4
+ import { requireAuth } from '../../auth/middleware.{{ext}}'
5
+ {{/if}}
6
+ import { {{namedExport}} } from '{{importPath fnSource ext}}'
7
+
8
+ export const {{camelCase name}}Route = new Elysia()
9
+ {{#if requiresAuth}}
10
+ .use(requireAuth)
11
+ .get('/api/queries/{{camelCase name}}', async ({ query, user }) => {
12
+ const result = await {{namedExport}}({ db, user, args: query })
13
+ return result
14
+ })
15
+ {{else}}
16
+ .get('/api/queries/{{camelCase name}}', async ({ query }) => {
17
+ const result = await {{namedExport}}({ db, args: query })
18
+ return result
19
+ })
20
+ {{/if}}
@@ -0,0 +1,30 @@
1
+ import { Elysia } from 'elysia'
2
+
3
+ // In-process subscriber set for '{{name}}' channel
4
+ // Swap this for a Redis adapter in production multi-instance deployments
5
+ const subscribers = new Set()
6
+
7
+ /**
8
+ * Publish a realtime event to all connected subscribers of '{{name}}'.
9
+ * Called automatically by CRUD mutation handlers.
10
+ */
11
+ export function publish{{pascalCase name}}(event, data) {
12
+ const message = JSON.stringify({ channel: '{{camelCase name}}', event, data })
13
+ for (const ws of subscribers) {
14
+ try { ws.send(message) } catch { subscribers.delete(ws) }
15
+ }
16
+ }
17
+
18
+ export const {{camelCase name}}Channel = new Elysia()
19
+ .ws('/ws/{{camelCase name}}', {
20
+ open(ws) {
21
+ subscribers.add(ws)
22
+ },
23
+ message(ws, msg) {
24
+ // Echo back for debugging; extend for room-based subscriptions
25
+ ws.send(JSON.stringify({ ack: msg }))
26
+ },
27
+ close(ws) {
28
+ subscribers.delete(ws)
29
+ },
30
+ })
@@ -0,0 +1,9 @@
1
+ import { Elysia } from 'elysia'
2
+ {{#each realtimes}}
3
+ import { {{camelCase name}}Channel } from './{{camelCase name}}.{{../ext}}'
4
+ {{/each}}
5
+
6
+ export const realtimeRoutes = new Elysia()
7
+ {{#each realtimes}}
8
+ .use({{camelCase name}}Channel)
9
+ {{/each}}
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
7
+ "strict": true,
8
+ "noUnusedLocals": true,
9
+ "noUnusedParameters": true,
10
+ "noFallthroughCasesInSwitch": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "jsx": "preserve",
14
+ "paths": {
15
+ "@src/*": ["./src/*"],
16
+ "@vasp-framework/client": ["./src/vasp/client/index.ts"]
17
+ }
18
+ },
19
+ "include": ["src/**/*.ts", "src/**/*.vue", "server/**/*.ts", "drizzle/**/*.ts"],
20
+ "exclude": ["node_modules", "dist", ".vasp-gen"]
21
+ }
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{appTitle}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.js"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <RouterView />
3
+ </template>
@@ -0,0 +1,9 @@
1
+ import { createApp } from 'vue'
2
+ import App from './App.vue'
3
+ import router from './router/index.js'
4
+ import { vaspPlugin } from './vasp/plugin.js'
5
+
6
+ const app = createApp(App)
7
+ app.use(router)
8
+ app.use(vaspPlugin)
9
+ app.mount('#app')
@@ -0,0 +1,41 @@
1
+ import { createRouter, createWebHistory } from 'vue-router'
2
+ {{#if hasAuth}}
3
+ import { useAuth } from '../vasp/auth.js'
4
+ {{/if}}
5
+
6
+ const routes = [
7
+ {{#each routes}}
8
+ {
9
+ path: '{{path}}',
10
+ component: () => import('{{lookup ../pagesMap to}}'),
11
+ },
12
+ {{/each}}
13
+ {{#if hasAuth}}
14
+ {
15
+ path: '/login',
16
+ component: () => import('../pages/Login.vue'),
17
+ },
18
+ {
19
+ path: '/register',
20
+ component: () => import('../pages/Register.vue'),
21
+ },
22
+ {{/if}}
23
+ ]
24
+
25
+ const router = createRouter({
26
+ history: createWebHistory(),
27
+ routes,
28
+ })
29
+
30
+ {{#if hasAuth}}
31
+ router.beforeEach(async (to) => {
32
+ const { user, checkAuth } = useAuth()
33
+ await checkAuth()
34
+ const publicPaths = ['/login', '/register']
35
+ if (!user.value && !publicPaths.includes(to.path)) {
36
+ return '/login'
37
+ }
38
+ })
39
+ {{/if}}
40
+
41
+ export default router
@@ -0,0 +1,45 @@
1
+ import { ref } from 'vue'
2
+ import { $fetch } from 'ofetch'
3
+
4
+ const user = ref(null)
5
+ let checked = false
6
+
7
+ const API = import.meta.env.VITE_API_URL || '/api'
8
+
9
+ export function useAuth() {
10
+ async function checkAuth() {
11
+ if (checked) return
12
+ checked = true
13
+ try {
14
+ user.value = await $fetch(`${API}/auth/me`, { credentials: 'include' })
15
+ } catch {
16
+ user.value = null
17
+ }
18
+ }
19
+
20
+ 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
27
+ }
28
+
29
+ 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
+ }
37
+
38
+ async function logout() {
39
+ await $fetch(`${API}/auth/logout`, { method: 'POST', credentials: 'include' })
40
+ user.value = null
41
+ checked = false
42
+ }
43
+
44
+ return { user, checkAuth, login, register, logout }
45
+ }
@@ -0,0 +1,15 @@
1
+ // Auto-generated by Vasp — do not edit
2
+ import { useVasp } from '@vasp-framework/runtime'
3
+
4
+ {{#each actions}}
5
+ /**
6
+ * Call the '{{name}}' action on the Vasp backend.
7
+ * @param {unknown} args - Arguments to pass to the action function
8
+ * @returns {Promise<unknown>}
9
+ */
10
+ export async function {{camelCase name}}(args) {
11
+ const { $vasp } = useVasp()
12
+ return $vasp.action('{{camelCase name}}', args)
13
+ }
14
+
15
+ {{/each}}
@@ -0,0 +1,30 @@
1
+ // Auto-generated by Vasp — do not edit
2
+ import { $fetch } from 'ofetch'
3
+
4
+ const API = import.meta.env.VITE_API_URL || '/api'
5
+
6
+ {{#each cruds}}
7
+ /**
8
+ * CRUD helpers for the '{{entity}}' entity.
9
+ */
10
+ export function use{{pascalCase entity}}Crud() {
11
+ const base = `${API}/crud/{{camelCase entity}}`
12
+
13
+ return {
14
+ {{#if (includes operations "list")}}
15
+ list: () => $fetch(base, { credentials: 'include' }),
16
+ {{/if}}
17
+ {{#if (includes operations "create")}}
18
+ create: (data) => $fetch(base, { method: 'POST', body: data, credentials: 'include' }),
19
+ {{/if}}
20
+ get: (id) => $fetch(`${base}/${id}`, { credentials: 'include' }),
21
+ {{#if (includes operations "update")}}
22
+ update: (id, data) => $fetch(`${base}/${id}`, { method: 'PUT', body: data, credentials: 'include' }),
23
+ {{/if}}
24
+ {{#if (includes operations "delete")}}
25
+ remove: (id) => $fetch(`${base}/${id}`, { method: 'DELETE', credentials: 'include' }),
26
+ {{/if}}
27
+ }
28
+ }
29
+
30
+ {{/each}}
@@ -0,0 +1,16 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ // Re-run `vasp build` or restart `vasp start` to regenerate
3
+
4
+ export { useVasp } from '@vasp-framework/runtime'
5
+ {{#each queries}}
6
+ export { {{camelCase name}} } from './queries.js'
7
+ {{/each}}
8
+ {{#each actions}}
9
+ export { {{camelCase name}} } from './actions.js'
10
+ {{/each}}
11
+ {{#each cruds}}
12
+ export { use{{pascalCase entity}}Crud } from './crud.js'
13
+ {{/each}}
14
+ {{#if hasRealtime}}
15
+ export { useRealtime } from './realtime.js'
16
+ {{/if}}
@@ -0,0 +1,15 @@
1
+ // Auto-generated by Vasp — do not edit
2
+ import { useVasp } from '@vasp-framework/runtime'
3
+
4
+ {{#each queries}}
5
+ /**
6
+ * Call the '{{name}}' query on the Vasp backend.
7
+ * @param {unknown} [args] - Arguments to pass to the query function
8
+ * @returns {Promise<unknown>}
9
+ */
10
+ export async function {{camelCase name}}(args) {
11
+ const { $vasp } = useVasp()
12
+ return $vasp.query('{{camelCase name}}', args)
13
+ }
14
+
15
+ {{/each}}
@@ -0,0 +1,51 @@
1
+ // Auto-generated by Vasp — do not edit
2
+ import { ref, onUnmounted } from 'vue'
3
+
4
+ const WS_BASE = import.meta.env.VITE_WS_URL || `ws://${location.host}`
5
+
6
+ /**
7
+ * useRealtime — subscribe to a Vasp realtime channel.
8
+ *
9
+ * @example
10
+ * const { on } = useRealtime('todoChannel')
11
+ * on('created', (todo) => todos.value.push(todo))
12
+ */
13
+ export function useRealtime(channelName) {
14
+ const connected = ref(false)
15
+ const handlers = new Map()
16
+ let ws = null
17
+ let reconnectTimer = null
18
+
19
+ function connect() {
20
+ ws = new WebSocket(`${WS_BASE}/ws/${channelName}`)
21
+
22
+ ws.onopen = () => { connected.value = true }
23
+ ws.onclose = () => {
24
+ connected.value = false
25
+ reconnectTimer = setTimeout(connect, 3000) // auto-reconnect
26
+ }
27
+ ws.onerror = () => ws.close()
28
+ ws.onmessage = ({ data }) => {
29
+ try {
30
+ const msg = JSON.parse(data)
31
+ if (msg.channel === channelName && handlers.has(msg.event)) {
32
+ handlers.get(msg.event)(msg.data)
33
+ }
34
+ } catch {}
35
+ }
36
+ }
37
+
38
+ function on(event, handler) {
39
+ handlers.set(event, handler)
40
+ }
41
+
42
+ function disconnect() {
43
+ clearTimeout(reconnectTimer)
44
+ ws?.close()
45
+ }
46
+
47
+ connect()
48
+ onUnmounted(disconnect)
49
+
50
+ return { connected, on, disconnect }
51
+ }
@@ -0,0 +1,11 @@
1
+ import { createVaspClient } from '@vasp-framework/runtime'
2
+
3
+ export const vaspPlugin = {
4
+ install(app) {
5
+ const client = createVaspClient({
6
+ baseURL: import.meta.env.VITE_API_URL || '/api',
7
+ })
8
+ app.provide('$vasp', client)
9
+ app.config.globalProperties.$vasp = client
10
+ },
11
+ }
@@ -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,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{appTitle}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <RouterView />
3
+ </template>
@@ -0,0 +1,9 @@
1
+ import { createApp } from 'vue'
2
+ import App from './App.vue'
3
+ import router from './router/index.js'
4
+ import { vaspPlugin } from './vasp/plugin.js'
5
+
6
+ const app = createApp(App)
7
+ app.use(router)
8
+ app.use(vaspPlugin)
9
+ app.mount('#app')
@@ -0,0 +1,41 @@
1
+ import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
2
+ {{#if hasAuth}}
3
+ import { useAuth } from '../vasp/auth.js'
4
+ {{/if}}
5
+
6
+ const routes: RouteRecordRaw[] = [
7
+ {{#each routes}}
8
+ {
9
+ path: '{{path}}',
10
+ component: () => import('{{lookup ../pagesMap to}}'),
11
+ },
12
+ {{/each}}
13
+ {{#if hasAuth}}
14
+ {
15
+ path: '/login',
16
+ component: () => import('../pages/Login.vue'),
17
+ },
18
+ {
19
+ path: '/register',
20
+ component: () => import('../pages/Register.vue'),
21
+ },
22
+ {{/if}}
23
+ ]
24
+
25
+ const router = createRouter({
26
+ history: createWebHistory(),
27
+ routes,
28
+ })
29
+
30
+ {{#if hasAuth}}
31
+ router.beforeEach(async (to) => {
32
+ const { user, checkAuth } = useAuth()
33
+ await checkAuth()
34
+ const publicPaths = ['/login', '/register']
35
+ if (!user.value && !publicPaths.includes(to.path)) {
36
+ return '/login'
37
+ }
38
+ })
39
+ {{/if}}
40
+
41
+ export default router
@@ -0,0 +1,53 @@
1
+ import { ref, type Ref } from 'vue'
2
+ import { $fetch } from 'ofetch'
3
+
4
+ export interface AuthUser {
5
+ id: number
6
+ username: string
7
+ email: string | null
8
+ createdAt: Date
9
+ updatedAt: Date
10
+ }
11
+
12
+ const user: Ref<AuthUser | null> = ref(null)
13
+ let checked = false
14
+
15
+ const API = import.meta.env.VITE_API_URL || '/api'
16
+
17
+ export function useAuth() {
18
+ async function checkAuth(): Promise<void> {
19
+ if (checked) return
20
+ checked = true
21
+ try {
22
+ user.value = await $fetch<AuthUser>(`${API}/auth/me`, { credentials: 'include' })
23
+ } catch {
24
+ user.value = null
25
+ }
26
+ }
27
+
28
+ 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!
35
+ }
36
+
37
+ 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
+ }
45
+
46
+ async function logout(): Promise<void> {
47
+ await $fetch(`${API}/auth/logout`, { method: 'POST', credentials: 'include' })
48
+ user.value = null
49
+ checked = false
50
+ }
51
+
52
+ return { user, checkAuth, login, register, logout }
53
+ }
@@ -0,0 +1,19 @@
1
+ // Auto-generated by Vasp — do not edit
2
+ import { useVasp } from '@vasp-framework/runtime'
3
+ import type {
4
+ {{#each actions}}
5
+ {{pascalCase name}}Args,
6
+ {{pascalCase name}}Return,
7
+ {{/each}}
8
+ } from './types.js'
9
+
10
+ {{#each actions}}
11
+ /**
12
+ * Call the '{{name}}' action on the Vasp backend.
13
+ */
14
+ export async function {{camelCase name}}(args: {{pascalCase name}}Args): Promise<{{pascalCase name}}Return> {
15
+ const { $vasp } = useVasp()
16
+ return $vasp.action('{{camelCase name}}', args) as Promise<{{pascalCase name}}Return>
17
+ }
18
+
19
+ {{/each}}
@@ -0,0 +1,37 @@
1
+ // Auto-generated by Vasp — do not edit
2
+ import { $fetch } from 'ofetch'
3
+ import type {
4
+ {{#each cruds}}
5
+ {{pascalCase entity}},
6
+ New{{pascalCase entity}},
7
+ {{/each}}
8
+ } from './types.js'
9
+
10
+ const API = import.meta.env.VITE_API_URL || '/api'
11
+
12
+ {{#each cruds}}
13
+ export function use{{pascalCase entity}}Crud() {
14
+ const base = `${API}/crud/{{camelCase entity}}`
15
+
16
+ return {
17
+ {{#if (includes operations "list")}}
18
+ list: (): Promise<{{pascalCase entity}}[]> => $fetch(base, { credentials: 'include' }),
19
+ {{/if}}
20
+ {{#if (includes operations "create")}}
21
+ create: (data: New{{pascalCase entity}}): Promise<{{pascalCase entity}}> =>
22
+ $fetch(base, { method: 'POST', body: data, credentials: 'include' }),
23
+ {{/if}}
24
+ get: (id: number): Promise<{{pascalCase entity}}> =>
25
+ $fetch(`${base}/${id}`, { credentials: 'include' }),
26
+ {{#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' }),
29
+ {{/if}}
30
+ {{#if (includes operations "delete")}}
31
+ remove: (id: number): Promise<{ ok: boolean }> =>
32
+ $fetch(`${base}/${id}`, { method: 'DELETE', credentials: 'include' }),
33
+ {{/if}}
34
+ }
35
+ }
36
+
37
+ {{/each}}
@@ -0,0 +1,17 @@
1
+ // Auto-generated by Vasp — do not edit directly
2
+ // Re-run `vasp build` or restart `vasp start` to regenerate
3
+
4
+ export { useVasp } from '@vasp-framework/runtime'
5
+ {{#each queries}}
6
+ export { {{camelCase name}} } from './queries.js'
7
+ {{/each}}
8
+ {{#each actions}}
9
+ export { {{camelCase name}} } from './actions.js'
10
+ {{/each}}
11
+ {{#each cruds}}
12
+ export { use{{pascalCase entity}}Crud } from './crud.js'
13
+ {{/each}}
14
+ {{#if hasRealtime}}
15
+ export { useRealtime } from './realtime.js'
16
+ {{/if}}
17
+ export type * from './types.js'
@@ -0,0 +1,19 @@
1
+ // Auto-generated by Vasp — do not edit
2
+ import { useVasp } from '@vasp-framework/runtime'
3
+ import type {
4
+ {{#each queries}}
5
+ {{pascalCase name}}Args,
6
+ {{pascalCase name}}Return,
7
+ {{/each}}
8
+ } from './types.js'
9
+
10
+ {{#each queries}}
11
+ /**
12
+ * Call the '{{name}}' query on the Vasp backend.
13
+ */
14
+ export async function {{camelCase name}}(args?: {{pascalCase name}}Args): Promise<{{pascalCase name}}Return> {
15
+ const { $vasp } = useVasp()
16
+ return $vasp.query('{{camelCase name}}', args) as Promise<{{pascalCase name}}Return>
17
+ }
18
+
19
+ {{/each}}
@@ -0,0 +1,56 @@
1
+ // Auto-generated by Vasp — do not edit
2
+ import { ref, onUnmounted, type Ref } from 'vue'
3
+
4
+ const WS_BASE = import.meta.env.VITE_WS_URL || `ws://${location.host}`
5
+
6
+ export interface RealtimeMessage {
7
+ channel: string
8
+ event: string
9
+ data: unknown
10
+ }
11
+
12
+ export interface UseRealtimeResult {
13
+ connected: Ref<boolean>
14
+ on: (event: string, handler: (data: unknown) => void) => void
15
+ disconnect: () => void
16
+ }
17
+
18
+ export function useRealtime(channelName: string): UseRealtimeResult {
19
+ const connected = ref(false)
20
+ const handlers = new Map<string, (data: unknown) => void>()
21
+ let ws: WebSocket | null = null
22
+ let reconnectTimer: ReturnType<typeof setTimeout> | null = null
23
+
24
+ function connect() {
25
+ ws = new WebSocket(`${WS_BASE}/ws/${channelName}`)
26
+
27
+ ws.onopen = () => { connected.value = true }
28
+ ws.onclose = () => {
29
+ connected.value = false
30
+ reconnectTimer = setTimeout(connect, 3000)
31
+ }
32
+ ws.onerror = () => ws?.close()
33
+ ws.onmessage = ({ data }: MessageEvent<string>) => {
34
+ try {
35
+ const msg = JSON.parse(data) as RealtimeMessage
36
+ if (msg.channel === channelName) {
37
+ handlers.get(msg.event)?.(msg.data)
38
+ }
39
+ } catch {}
40
+ }
41
+ }
42
+
43
+ function on(event: string, handler: (data: unknown) => void) {
44
+ handlers.set(event, handler)
45
+ }
46
+
47
+ function disconnect() {
48
+ if (reconnectTimer) clearTimeout(reconnectTimer)
49
+ ws?.close()
50
+ }
51
+
52
+ connect()
53
+ onUnmounted(disconnect)
54
+
55
+ return { connected, on, disconnect }
56
+ }