dzql 0.5.33 → 0.6.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/.env.sample +28 -0
- package/compose.yml +28 -0
- package/dist/client/index.ts +1 -0
- package/dist/client/stores/useMyProfileStore.ts +114 -0
- package/dist/client/stores/useOrgDashboardStore.ts +131 -0
- package/dist/client/stores/useVenueDetailStore.ts +117 -0
- package/dist/client/ws.ts +716 -0
- package/dist/db/migrations/000_core.sql +92 -0
- package/dist/db/migrations/20251229T212912022Z_schema.sql +3020 -0
- package/dist/db/migrations/20251229T212912022Z_subscribables.sql +371 -0
- package/dist/runtime/manifest.json +1562 -0
- package/docs/README.md +293 -36
- package/docs/feature-requests/applyPatch-bug-report.md +85 -0
- package/docs/feature-requests/connection-ready-profile.md +57 -0
- package/docs/feature-requests/hidden-bug-report.md +111 -0
- package/docs/feature-requests/hidden-fields-subscribables.md +34 -0
- package/docs/feature-requests/subscribable-param-key-bug.md +38 -0
- package/docs/feature-requests/todo.md +146 -0
- package/docs/for_ai.md +641 -0
- package/docs/project-setup.md +432 -0
- package/examples/blog.ts +50 -0
- package/examples/invalid.ts +18 -0
- package/examples/venues.js +485 -0
- package/package.json +23 -60
- package/src/cli/codegen/client.ts +99 -0
- package/src/cli/codegen/manifest.ts +95 -0
- package/src/cli/codegen/pinia.ts +174 -0
- package/src/cli/codegen/realtime.ts +58 -0
- package/src/cli/codegen/sql.ts +698 -0
- package/src/cli/codegen/subscribable_sql.ts +547 -0
- package/src/cli/codegen/subscribable_store.ts +184 -0
- package/src/cli/codegen/types.ts +142 -0
- package/src/cli/compiler/analyzer.ts +52 -0
- package/src/cli/compiler/graph_rules.ts +251 -0
- package/src/cli/compiler/ir.ts +233 -0
- package/src/cli/compiler/loader.ts +132 -0
- package/src/cli/compiler/permissions.ts +227 -0
- package/src/cli/index.ts +164 -0
- package/src/client/index.ts +1 -0
- package/src/client/ws.ts +286 -0
- package/src/create/.env.example +8 -0
- package/src/create/README.md +101 -0
- package/src/create/compose.yml +14 -0
- package/src/create/domain.ts +153 -0
- package/src/create/package.json +24 -0
- package/src/create/server.ts +18 -0
- package/src/create/setup.sh +11 -0
- package/src/create/tsconfig.json +15 -0
- package/src/runtime/auth.ts +39 -0
- package/src/runtime/db.ts +33 -0
- package/src/runtime/errors.ts +51 -0
- package/src/runtime/index.ts +98 -0
- package/src/runtime/js_functions.ts +63 -0
- package/src/runtime/manifest_loader.ts +29 -0
- package/src/runtime/namespace.ts +483 -0
- package/src/runtime/server.ts +87 -0
- package/src/runtime/ws.ts +197 -0
- package/src/shared/ir.ts +197 -0
- package/tests/client.test.ts +38 -0
- package/tests/codegen.test.ts +71 -0
- package/tests/compiler.test.ts +45 -0
- package/tests/graph_rules.test.ts +173 -0
- package/tests/integration/db.test.ts +174 -0
- package/tests/integration/e2e.test.ts +65 -0
- package/tests/integration/features.test.ts +922 -0
- package/tests/integration/full_stack.test.ts +262 -0
- package/tests/integration/setup.ts +45 -0
- package/tests/ir.test.ts +32 -0
- package/tests/namespace.test.ts +395 -0
- package/tests/permissions.test.ts +55 -0
- package/tests/pinia.test.ts +48 -0
- package/tests/realtime.test.ts +22 -0
- package/tests/runtime.test.ts +80 -0
- package/tests/subscribable_gen.test.ts +72 -0
- package/tests/subscribable_reactivity.test.ts +258 -0
- package/tests/venues_gen.test.ts +25 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/README.md +0 -90
- package/bin/cli.js +0 -727
- package/docs/compiler/ADVANCED_FILTERS.md +0 -183
- package/docs/compiler/CODING_STANDARDS.md +0 -415
- package/docs/compiler/COMPARISON.md +0 -673
- package/docs/compiler/QUICKSTART.md +0 -326
- package/docs/compiler/README.md +0 -134
- package/docs/examples/README.md +0 -38
- package/docs/examples/blog.sql +0 -160
- package/docs/examples/venue-detail-simple.sql +0 -8
- package/docs/examples/venue-detail-subscribable.sql +0 -45
- package/docs/for-ai/claude-guide.md +0 -1210
- package/docs/getting-started/quickstart.md +0 -125
- package/docs/getting-started/subscriptions-quick-start.md +0 -203
- package/docs/getting-started/tutorial.md +0 -1104
- package/docs/guides/atomic-updates.md +0 -299
- package/docs/guides/client-stores.md +0 -730
- package/docs/guides/composite-primary-keys.md +0 -158
- package/docs/guides/custom-functions.md +0 -362
- package/docs/guides/drop-semantics.md +0 -554
- package/docs/guides/field-defaults.md +0 -240
- package/docs/guides/interpreter-vs-compiler.md +0 -237
- package/docs/guides/many-to-many.md +0 -929
- package/docs/guides/subscriptions.md +0 -537
- package/docs/reference/api.md +0 -1373
- package/docs/reference/client.md +0 -224
- package/src/client/stores/index.js +0 -8
- package/src/client/stores/useAppStore.js +0 -285
- package/src/client/stores/useWsStore.js +0 -289
- package/src/client/ws.js +0 -762
- package/src/compiler/cli/compile-example.js +0 -33
- package/src/compiler/cli/compile-subscribable.js +0 -43
- package/src/compiler/cli/debug-compile.js +0 -44
- package/src/compiler/cli/debug-parse.js +0 -26
- package/src/compiler/cli/debug-path-parser.js +0 -18
- package/src/compiler/cli/debug-subscribable-parser.js +0 -21
- package/src/compiler/cli/index.js +0 -174
- package/src/compiler/codegen/auth-codegen.js +0 -153
- package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
- package/src/compiler/codegen/graph-rules-codegen.js +0 -450
- package/src/compiler/codegen/notification-codegen.js +0 -232
- package/src/compiler/codegen/operation-codegen.js +0 -1382
- package/src/compiler/codegen/permission-codegen.js +0 -318
- package/src/compiler/codegen/subscribable-codegen.js +0 -827
- package/src/compiler/compiler.js +0 -371
- package/src/compiler/index.js +0 -11
- package/src/compiler/parser/entity-parser.js +0 -440
- package/src/compiler/parser/path-parser.js +0 -290
- package/src/compiler/parser/subscribable-parser.js +0 -244
- package/src/database/dzql-core.sql +0 -161
- package/src/database/migrations/001_schema.sql +0 -60
- package/src/database/migrations/002_functions.sql +0 -890
- package/src/database/migrations/003_operations.sql +0 -1135
- package/src/database/migrations/004_search.sql +0 -581
- package/src/database/migrations/005_entities.sql +0 -730
- package/src/database/migrations/006_auth.sql +0 -94
- package/src/database/migrations/007_events.sql +0 -133
- package/src/database/migrations/008_hello.sql +0 -18
- package/src/database/migrations/008a_meta.sql +0 -172
- package/src/database/migrations/009_subscriptions.sql +0 -240
- package/src/database/migrations/010_atomic_updates.sql +0 -157
- package/src/database/migrations/010_fix_m2m_events.sql +0 -94
- package/src/index.js +0 -40
- package/src/server/api.js +0 -9
- package/src/server/db.js +0 -442
- package/src/server/index.js +0 -317
- package/src/server/logger.js +0 -259
- package/src/server/mcp.js +0 -594
- package/src/server/meta-route.js +0 -251
- package/src/server/namespace.js +0 -292
- package/src/server/subscriptions.js +0 -351
- package/src/server/ws.js +0 -573
|
@@ -1,730 +0,0 @@
|
|
|
1
|
-
# DZQL Client Stores Guide
|
|
2
|
-
|
|
3
|
-
Complete guide to using the canonical DZQL Pinia stores for Vue.js applications.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
DZQL provides two canonical Pinia stores that handle the complete application lifecycle:
|
|
8
|
-
|
|
9
|
-
1. **`useWsStore`** - WebSocket connection and authentication management
|
|
10
|
-
2. **`useAppStore`** - Application state and navigation
|
|
11
|
-
|
|
12
|
-
These stores implement the **three-phase lifecycle** that DZQL applications follow:
|
|
13
|
-
|
|
14
|
-
### Three-Phase Lifecycle
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
┌─────────────┐
|
|
18
|
-
│ CONNECTING │ Initial WebSocket connection
|
|
19
|
-
└──────┬──────┘
|
|
20
|
-
│
|
|
21
|
-
├─ Connected without profile
|
|
22
|
-
│
|
|
23
|
-
v
|
|
24
|
-
┌─────────────┐
|
|
25
|
-
│ LOGIN │ Show login form, wait for authentication
|
|
26
|
-
└──────┬──────┘
|
|
27
|
-
│
|
|
28
|
-
├─ User authenticates
|
|
29
|
-
│
|
|
30
|
-
v
|
|
31
|
-
┌─────────────┐
|
|
32
|
-
│ READY │ App is ready to use
|
|
33
|
-
└─────────────┘
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Quick Start
|
|
37
|
-
|
|
38
|
-
### 1. Install Dependencies
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npm install pinia vue-router
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### 2. Basic Setup
|
|
45
|
-
|
|
46
|
-
**main.js:**
|
|
47
|
-
```javascript
|
|
48
|
-
import { createApp } from 'vue'
|
|
49
|
-
import { createPinia } from 'pinia'
|
|
50
|
-
import { useAppStore } from 'dzql/client/stores'
|
|
51
|
-
import App from './App.vue'
|
|
52
|
-
|
|
53
|
-
const pinia = createPinia()
|
|
54
|
-
const app = createApp(App)
|
|
55
|
-
|
|
56
|
-
app.use(pinia)
|
|
57
|
-
|
|
58
|
-
// Initialize app store
|
|
59
|
-
const appStore = useAppStore()
|
|
60
|
-
await appStore.initialize({
|
|
61
|
-
title: 'My DZQL App'
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
app.mount('#app')
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
**App.vue:**
|
|
68
|
-
```vue
|
|
69
|
-
<script setup>
|
|
70
|
-
import { computed } from 'vue'
|
|
71
|
-
import { useWsStore } from 'dzql/client/stores'
|
|
72
|
-
import LoginView from './components/LoginView.vue'
|
|
73
|
-
import MainLayout from './components/MainLayout.vue'
|
|
74
|
-
|
|
75
|
-
const wsStore = useWsStore()
|
|
76
|
-
const state = computed(() => wsStore.appState)
|
|
77
|
-
</script>
|
|
78
|
-
|
|
79
|
-
<template>
|
|
80
|
-
<!-- Phase 1: CONNECTING -->
|
|
81
|
-
<div v-if="state === 'connecting'">
|
|
82
|
-
<span class="loading loading-spinner"></span>
|
|
83
|
-
Connecting...
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
<!-- Phase 2: LOGIN -->
|
|
87
|
-
<LoginView v-else-if="state === 'login'" />
|
|
88
|
-
|
|
89
|
-
<!-- Phase 3: READY -->
|
|
90
|
-
<MainLayout v-else-if="state === 'ready'" />
|
|
91
|
-
</template>
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Store API Reference
|
|
95
|
-
|
|
96
|
-
### useWsStore
|
|
97
|
-
|
|
98
|
-
Manages WebSocket connection and user authentication.
|
|
99
|
-
|
|
100
|
-
#### State
|
|
101
|
-
|
|
102
|
-
```javascript
|
|
103
|
-
const wsStore = useWsStore()
|
|
104
|
-
|
|
105
|
-
wsStore.connectionState // 'disconnected' | 'connecting' | 'connected' | 'error'
|
|
106
|
-
wsStore.appState // 'connecting' | 'login' | 'ready'
|
|
107
|
-
wsStore.profile // User profile object or null
|
|
108
|
-
wsStore.error // Last error message or null
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
#### Computed
|
|
112
|
-
|
|
113
|
-
```javascript
|
|
114
|
-
wsStore.isConnected // boolean - WebSocket is connected
|
|
115
|
-
wsStore.isAuthenticated // boolean - User is logged in
|
|
116
|
-
wsStore.isReady // boolean - App is ready (connected + authenticated)
|
|
117
|
-
wsStore.needsLogin // boolean - Needs to show login form
|
|
118
|
-
wsStore.isConnecting // boolean - Currently connecting
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
#### Actions
|
|
122
|
-
|
|
123
|
-
**connect(url, timeout)**
|
|
124
|
-
|
|
125
|
-
Connect to WebSocket server. URL is auto-detected if not provided.
|
|
126
|
-
|
|
127
|
-
```javascript
|
|
128
|
-
// Auto-detect URL
|
|
129
|
-
await wsStore.connect()
|
|
130
|
-
|
|
131
|
-
// Custom URL (development)
|
|
132
|
-
await wsStore.connect('ws://localhost:3000/ws')
|
|
133
|
-
|
|
134
|
-
// Custom timeout
|
|
135
|
-
await wsStore.connect(null, 10000) // 10 seconds
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
**login({ email, password })**
|
|
139
|
-
|
|
140
|
-
Login with email and password.
|
|
141
|
-
|
|
142
|
-
```javascript
|
|
143
|
-
try {
|
|
144
|
-
const result = await wsStore.login({
|
|
145
|
-
email: 'user@example.com',
|
|
146
|
-
password: 'password123'
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
console.log('Logged in:', result.profile)
|
|
150
|
-
} catch (err) {
|
|
151
|
-
console.error('Login failed:', err.message)
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
**register({ email, password })**
|
|
156
|
-
|
|
157
|
-
Register a new user.
|
|
158
|
-
|
|
159
|
-
```javascript
|
|
160
|
-
try {
|
|
161
|
-
const result = await wsStore.register({
|
|
162
|
-
email: 'newuser@example.com',
|
|
163
|
-
password: 'securepass123'
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
console.log('Registered:', result.profile)
|
|
167
|
-
} catch (err) {
|
|
168
|
-
console.error('Registration failed:', err.message)
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
**logout()**
|
|
173
|
-
|
|
174
|
-
Logout current user and clear session.
|
|
175
|
-
|
|
176
|
-
```javascript
|
|
177
|
-
await wsStore.logout()
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
**disconnect()**
|
|
181
|
-
|
|
182
|
-
Disconnect from WebSocket.
|
|
183
|
-
|
|
184
|
-
```javascript
|
|
185
|
-
wsStore.disconnect()
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
**getWs()**
|
|
189
|
-
|
|
190
|
-
Get the WebSocket manager instance for direct API calls.
|
|
191
|
-
|
|
192
|
-
```javascript
|
|
193
|
-
const ws = wsStore.getWs()
|
|
194
|
-
|
|
195
|
-
// Use DZQL API
|
|
196
|
-
const venue = await ws.api.get.venues({ id: 1 })
|
|
197
|
-
const created = await ws.api.save.venues({ name: 'New Venue' })
|
|
198
|
-
|
|
199
|
-
// Call custom functions
|
|
200
|
-
const result = await ws.api.myCustomFunction({ param: 'value' })
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
### useAppStore
|
|
206
|
-
|
|
207
|
-
Manages application-level state and navigation.
|
|
208
|
-
|
|
209
|
-
#### State
|
|
210
|
-
|
|
211
|
-
```javascript
|
|
212
|
-
const appStore = useAppStore()
|
|
213
|
-
|
|
214
|
-
appStore.title // App title
|
|
215
|
-
appStore.currentEntity // Current entity name or null
|
|
216
|
-
appStore.currentId // Current record ID or null
|
|
217
|
-
appStore.entityMetadata // Entity metadata cache object
|
|
218
|
-
appStore.isLoadingMetadata // boolean - Fetching metadata
|
|
219
|
-
appStore.sidebarOpen // boolean - Sidebar visibility
|
|
220
|
-
appStore.propertiesPanelOpen // boolean - Properties panel visibility
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
#### Computed
|
|
224
|
-
|
|
225
|
-
```javascript
|
|
226
|
-
appStore.hasMetadata // boolean - Metadata loaded
|
|
227
|
-
appStore.entityList // string[] - Sorted list of entity names
|
|
228
|
-
appStore.currentEntityMeta // object - Metadata for current entity
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
#### Actions
|
|
232
|
-
|
|
233
|
-
**initialize(options)**
|
|
234
|
-
|
|
235
|
-
Initialize the app (connect to WebSocket and set up lifecycle).
|
|
236
|
-
|
|
237
|
-
```javascript
|
|
238
|
-
await appStore.initialize({
|
|
239
|
-
wsUrl: 'ws://localhost:3000/ws', // Optional
|
|
240
|
-
title: 'My DZQL App' // Optional
|
|
241
|
-
})
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
**fetchMetadata()**
|
|
245
|
-
|
|
246
|
-
Fetch entity metadata from server. Called automatically after authentication.
|
|
247
|
-
|
|
248
|
-
```javascript
|
|
249
|
-
await appStore.fetchMetadata()
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
**setRouter(router)**
|
|
253
|
-
|
|
254
|
-
Set Vue Router instance to enable programmatic navigation.
|
|
255
|
-
|
|
256
|
-
```javascript
|
|
257
|
-
import { createRouter } from 'vue-router'
|
|
258
|
-
|
|
259
|
-
const router = createRouter({ ... })
|
|
260
|
-
appStore.setRouter(router)
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
**Navigation Methods:**
|
|
264
|
-
|
|
265
|
-
```javascript
|
|
266
|
-
// Navigate to entity list
|
|
267
|
-
appStore.navigateToEntity('venues')
|
|
268
|
-
|
|
269
|
-
// Navigate to entity detail
|
|
270
|
-
appStore.navigateToEntityDetail('venues', 123)
|
|
271
|
-
appStore.navigateToEntityDetail('venues', 'new')
|
|
272
|
-
|
|
273
|
-
// Navigate to home
|
|
274
|
-
appStore.navigateToHome()
|
|
275
|
-
|
|
276
|
-
// Set context manually (without navigation)
|
|
277
|
-
appStore.setContext('venues', 123)
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
**UI Toggles:**
|
|
281
|
-
|
|
282
|
-
```javascript
|
|
283
|
-
appStore.toggleSidebar()
|
|
284
|
-
appStore.togglePropertiesPanel()
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
---
|
|
288
|
-
|
|
289
|
-
## Complete Examples
|
|
290
|
-
|
|
291
|
-
### Example 1: Basic App with Router
|
|
292
|
-
|
|
293
|
-
**main.js:**
|
|
294
|
-
```javascript
|
|
295
|
-
import { createApp } from 'vue'
|
|
296
|
-
import { createRouter, createWebHashHistory } from 'vue-router'
|
|
297
|
-
import { createPinia } from 'pinia'
|
|
298
|
-
import { useAppStore } from 'dzql/client/stores'
|
|
299
|
-
import App from './App.vue'
|
|
300
|
-
|
|
301
|
-
// Create Pinia
|
|
302
|
-
const pinia = createPinia()
|
|
303
|
-
|
|
304
|
-
// Create Router
|
|
305
|
-
const router = createRouter({
|
|
306
|
-
history: createWebHashHistory(),
|
|
307
|
-
routes: [
|
|
308
|
-
{ path: '/', name: 'home', component: () => import('./views/Home.vue') },
|
|
309
|
-
{ path: '/:entity', name: 'entity-list', component: () => import('./views/EntityList.vue') },
|
|
310
|
-
{ path: '/:entity/:id', name: 'entity-detail', component: () => import('./views/EntityDetail.vue') }
|
|
311
|
-
]
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
// Create App
|
|
315
|
-
const app = createApp(App)
|
|
316
|
-
app.use(pinia)
|
|
317
|
-
app.use(router)
|
|
318
|
-
|
|
319
|
-
// Initialize
|
|
320
|
-
const appStore = useAppStore()
|
|
321
|
-
appStore.setRouter(router)
|
|
322
|
-
await appStore.initialize()
|
|
323
|
-
|
|
324
|
-
app.mount('#app')
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
**App.vue:**
|
|
328
|
-
```vue
|
|
329
|
-
<script setup>
|
|
330
|
-
import { computed } from 'vue'
|
|
331
|
-
import { useWsStore, useAppStore } from 'dzql/client/stores'
|
|
332
|
-
import LoginView from './components/LoginView.vue'
|
|
333
|
-
|
|
334
|
-
const wsStore = useWsStore()
|
|
335
|
-
const appStore = useAppStore()
|
|
336
|
-
|
|
337
|
-
const state = computed(() => wsStore.appState)
|
|
338
|
-
const profile = computed(() => wsStore.profile)
|
|
339
|
-
|
|
340
|
-
async function handleLogout() {
|
|
341
|
-
await wsStore.logout()
|
|
342
|
-
}
|
|
343
|
-
</script>
|
|
344
|
-
|
|
345
|
-
<template>
|
|
346
|
-
<div class="app">
|
|
347
|
-
<!-- CONNECTING -->
|
|
348
|
-
<div v-if="state === 'connecting'" class="loading-screen">
|
|
349
|
-
<div class="spinner"></div>
|
|
350
|
-
<p>Connecting...</p>
|
|
351
|
-
</div>
|
|
352
|
-
|
|
353
|
-
<!-- LOGIN -->
|
|
354
|
-
<LoginView v-else-if="state === 'login'" />
|
|
355
|
-
|
|
356
|
-
<!-- READY -->
|
|
357
|
-
<div v-else-if="state === 'ready'">
|
|
358
|
-
<nav>
|
|
359
|
-
<h1>{{ appStore.title }}</h1>
|
|
360
|
-
<div class="user-menu">
|
|
361
|
-
<span>{{ profile.email }}</span>
|
|
362
|
-
<button @click="handleLogout">Logout</button>
|
|
363
|
-
</div>
|
|
364
|
-
</nav>
|
|
365
|
-
|
|
366
|
-
<main>
|
|
367
|
-
<router-view />
|
|
368
|
-
</main>
|
|
369
|
-
</div>
|
|
370
|
-
</div>
|
|
371
|
-
</template>
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
**LoginView.vue:**
|
|
375
|
-
```vue
|
|
376
|
-
<script setup>
|
|
377
|
-
import { ref } from 'vue'
|
|
378
|
-
import { useWsStore } from 'dzql/client/stores'
|
|
379
|
-
|
|
380
|
-
const wsStore = useWsStore()
|
|
381
|
-
const email = ref('')
|
|
382
|
-
const password = ref('')
|
|
383
|
-
const error = ref(null)
|
|
384
|
-
|
|
385
|
-
async function handleLogin() {
|
|
386
|
-
try {
|
|
387
|
-
error.value = null
|
|
388
|
-
await wsStore.login({
|
|
389
|
-
email: email.value,
|
|
390
|
-
password: password.value
|
|
391
|
-
})
|
|
392
|
-
} catch (err) {
|
|
393
|
-
error.value = err.message
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
</script>
|
|
397
|
-
|
|
398
|
-
<template>
|
|
399
|
-
<div class="login-form">
|
|
400
|
-
<h2>Login</h2>
|
|
401
|
-
|
|
402
|
-
<form @submit.prevent="handleLogin">
|
|
403
|
-
<input v-model="email" type="email" placeholder="Email" required />
|
|
404
|
-
<input v-model="password" type="password" placeholder="Password" required />
|
|
405
|
-
<button type="submit">Login</button>
|
|
406
|
-
</form>
|
|
407
|
-
|
|
408
|
-
<p v-if="error" class="error">{{ error }}</p>
|
|
409
|
-
</div>
|
|
410
|
-
</template>
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
---
|
|
414
|
-
|
|
415
|
-
### Example 2: Using DZQL API
|
|
416
|
-
|
|
417
|
-
**EntityList.vue:**
|
|
418
|
-
```vue
|
|
419
|
-
<script setup>
|
|
420
|
-
import { ref, onMounted, computed } from 'vue'
|
|
421
|
-
import { useRoute } from 'vue-router'
|
|
422
|
-
import { useWsStore, useAppStore } from 'dzql/client/stores'
|
|
423
|
-
|
|
424
|
-
const route = useRoute()
|
|
425
|
-
const wsStore = useWsStore()
|
|
426
|
-
const appStore = useAppStore()
|
|
427
|
-
|
|
428
|
-
const entity = computed(() => route.params.entity)
|
|
429
|
-
const records = ref([])
|
|
430
|
-
const loading = ref(false)
|
|
431
|
-
|
|
432
|
-
async function loadRecords() {
|
|
433
|
-
const ws = wsStore.getWs()
|
|
434
|
-
loading.value = true
|
|
435
|
-
|
|
436
|
-
try {
|
|
437
|
-
const result = await ws.api.search[entity.value]({
|
|
438
|
-
filters: {},
|
|
439
|
-
page: 1,
|
|
440
|
-
limit: 50
|
|
441
|
-
})
|
|
442
|
-
|
|
443
|
-
records.value = result.data
|
|
444
|
-
} catch (err) {
|
|
445
|
-
console.error('Failed to load records:', err)
|
|
446
|
-
} finally {
|
|
447
|
-
loading.value = false
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
onMounted(() => {
|
|
452
|
-
loadRecords()
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
function createNew() {
|
|
456
|
-
appStore.navigateToEntityDetail(entity.value, 'new')
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function editRecord(id) {
|
|
460
|
-
appStore.navigateToEntityDetail(entity.value, id)
|
|
461
|
-
}
|
|
462
|
-
</script>
|
|
463
|
-
|
|
464
|
-
<template>
|
|
465
|
-
<div>
|
|
466
|
-
<div class="header">
|
|
467
|
-
<h2>{{ entity }}</h2>
|
|
468
|
-
<button @click="createNew">New</button>
|
|
469
|
-
</div>
|
|
470
|
-
|
|
471
|
-
<div v-if="loading">Loading...</div>
|
|
472
|
-
|
|
473
|
-
<table v-else>
|
|
474
|
-
<tbody>
|
|
475
|
-
<tr v-for="record in records" :key="record.id" @click="editRecord(record.id)">
|
|
476
|
-
<td>{{ record.id }}</td>
|
|
477
|
-
<td>{{ record.name || record.title || record.email }}</td>
|
|
478
|
-
</tr>
|
|
479
|
-
</tbody>
|
|
480
|
-
</table>
|
|
481
|
-
</div>
|
|
482
|
-
</template>
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
---
|
|
486
|
-
|
|
487
|
-
## Patterns and Best Practices
|
|
488
|
-
|
|
489
|
-
### Pattern 1: Reactive State in Components
|
|
490
|
-
|
|
491
|
-
```vue
|
|
492
|
-
<script setup>
|
|
493
|
-
import { computed } from 'vue'
|
|
494
|
-
import { useWsStore } from 'dzql/client/stores'
|
|
495
|
-
|
|
496
|
-
const wsStore = useWsStore()
|
|
497
|
-
|
|
498
|
-
// Use computed for reactive access
|
|
499
|
-
const profile = computed(() => wsStore.profile)
|
|
500
|
-
const isAuthenticated = computed(() => wsStore.isAuthenticated)
|
|
501
|
-
|
|
502
|
-
// Direct access to actions
|
|
503
|
-
async function login() {
|
|
504
|
-
await wsStore.login({ email: '...', password: '...' })
|
|
505
|
-
}
|
|
506
|
-
</script>
|
|
507
|
-
|
|
508
|
-
<template>
|
|
509
|
-
<div v-if="isAuthenticated">
|
|
510
|
-
Welcome, {{ profile.email }}!
|
|
511
|
-
</div>
|
|
512
|
-
</template>
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
### Pattern 2: Listening to WebSocket Broadcasts
|
|
516
|
-
|
|
517
|
-
```javascript
|
|
518
|
-
import { onMounted, onUnmounted } from 'vue'
|
|
519
|
-
import { useWsStore } from 'dzql/client/stores'
|
|
520
|
-
|
|
521
|
-
const wsStore = useWsStore()
|
|
522
|
-
const ws = wsStore.getWs()
|
|
523
|
-
|
|
524
|
-
onMounted(() => {
|
|
525
|
-
// Listen for real-time updates
|
|
526
|
-
const cleanup = ws.onBroadcast((method, params) => {
|
|
527
|
-
if (method === 'venues:update') {
|
|
528
|
-
console.log('Venue updated:', params.after)
|
|
529
|
-
// Refresh your data
|
|
530
|
-
}
|
|
531
|
-
})
|
|
532
|
-
|
|
533
|
-
onUnmounted(() => {
|
|
534
|
-
cleanup()
|
|
535
|
-
})
|
|
536
|
-
})
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
### Pattern 3: Global Navigation
|
|
540
|
-
|
|
541
|
-
```javascript
|
|
542
|
-
import { useAppStore } from 'dzql/client/stores'
|
|
543
|
-
|
|
544
|
-
const appStore = useAppStore()
|
|
545
|
-
|
|
546
|
-
// Navigate from anywhere in your app
|
|
547
|
-
function goToVenues() {
|
|
548
|
-
appStore.navigateToEntity('venues')
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function createNewVenue() {
|
|
552
|
-
appStore.navigateToEntityDetail('venues', 'new')
|
|
553
|
-
}
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
### Pattern 4: Error Handling
|
|
557
|
-
|
|
558
|
-
```javascript
|
|
559
|
-
import { useWsStore } from 'dzql/client/stores'
|
|
560
|
-
|
|
561
|
-
const wsStore = useWsStore()
|
|
562
|
-
|
|
563
|
-
async function saveRecord(data) {
|
|
564
|
-
const ws = wsStore.getWs()
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
const result = await ws.api.save.venues(data)
|
|
568
|
-
return result
|
|
569
|
-
} catch (err) {
|
|
570
|
-
if (err.message.includes('Permission denied')) {
|
|
571
|
-
// Handle permission error
|
|
572
|
-
alert('You do not have permission to save this record')
|
|
573
|
-
} else if (err.message.includes('not found')) {
|
|
574
|
-
// Handle not found
|
|
575
|
-
alert('Record not found')
|
|
576
|
-
} else {
|
|
577
|
-
// Generic error
|
|
578
|
-
console.error('Save failed:', err)
|
|
579
|
-
alert('Failed to save: ' + err.message)
|
|
580
|
-
}
|
|
581
|
-
throw err
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
---
|
|
587
|
-
|
|
588
|
-
## Common Issues and Solutions
|
|
589
|
-
|
|
590
|
-
### Issue: AI Gets the Pattern Wrong
|
|
591
|
-
|
|
592
|
-
**Problem:** When asking AI to help, it often mixes patterns or forgets the three-phase lifecycle.
|
|
593
|
-
|
|
594
|
-
**Solution:** Always reference this document and the template files:
|
|
595
|
-
```
|
|
596
|
-
packages/dzql/src/client/stores/useWsStore.js
|
|
597
|
-
packages/dzql/src/client/stores/useAppStore.js
|
|
598
|
-
packages/dzql/src/client/templates/App.vue
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
Point the AI to these files explicitly:
|
|
602
|
-
> "Use the canonical stores from packages/dzql/src/client/stores/ - follow the pattern in useWsStore.js for authentication"
|
|
603
|
-
|
|
604
|
-
### Issue: Router Integration Confusion
|
|
605
|
-
|
|
606
|
-
**Problem:** Router state doesn't sync with app state.
|
|
607
|
-
|
|
608
|
-
**Solution:** Always call `setRouter()` in main.js:
|
|
609
|
-
```javascript
|
|
610
|
-
const appStore = useAppStore()
|
|
611
|
-
appStore.setRouter(router)
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
### Issue: WebSocket Not Connecting
|
|
615
|
-
|
|
616
|
-
**Problem:** Connection fails or times out.
|
|
617
|
-
|
|
618
|
-
**Solution:** Check:
|
|
619
|
-
1. Server is running
|
|
620
|
-
2. URL is correct (auto-detection works in browser, use explicit URL in dev)
|
|
621
|
-
3. CORS settings allow WebSocket connections
|
|
622
|
-
4. Check browser console for errors
|
|
623
|
-
|
|
624
|
-
```javascript
|
|
625
|
-
// Use explicit URL during development
|
|
626
|
-
await appStore.initialize({
|
|
627
|
-
wsUrl: import.meta.env.DEV ? 'ws://localhost:3000/ws' : null
|
|
628
|
-
})
|
|
629
|
-
```
|
|
630
|
-
|
|
631
|
-
### Issue: Metadata Not Loading
|
|
632
|
-
|
|
633
|
-
**Problem:** `appStore.entityMetadata` is empty.
|
|
634
|
-
|
|
635
|
-
**Solution:** Metadata loads after authentication. Make sure:
|
|
636
|
-
```javascript
|
|
637
|
-
// Option 1: Happens automatically after login
|
|
638
|
-
await wsStore.login({ email, password })
|
|
639
|
-
// fetchMetadata() called automatically
|
|
640
|
-
|
|
641
|
-
// Option 2: Call manually
|
|
642
|
-
await appStore.fetchMetadata()
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
---
|
|
646
|
-
|
|
647
|
-
## Migration Guide
|
|
648
|
-
|
|
649
|
-
### From Old Pattern to New Stores
|
|
650
|
-
|
|
651
|
-
**Before (old pattern in packages/client/src/stores/main.js):**
|
|
652
|
-
```javascript
|
|
653
|
-
import { useWs } from "dzql/client"
|
|
654
|
-
|
|
655
|
-
export const useProfileStore = defineStore('profile', () => {
|
|
656
|
-
const ws = useWs()
|
|
657
|
-
const profile = ref(null)
|
|
658
|
-
|
|
659
|
-
ws.onBroadcast(async (method, params) => {
|
|
660
|
-
if (method === "connected") {
|
|
661
|
-
profile.value = params.profile || null
|
|
662
|
-
}
|
|
663
|
-
})
|
|
664
|
-
|
|
665
|
-
const connect = async () => {
|
|
666
|
-
await ws.connect('ws://localhost:3000/ws')
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
return { profile, connect }
|
|
670
|
-
})
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
**After (new canonical pattern):**
|
|
674
|
-
```javascript
|
|
675
|
-
import { useWsStore, useAppStore } from 'dzql/client/stores'
|
|
676
|
-
|
|
677
|
-
// In main.js
|
|
678
|
-
const appStore = useAppStore()
|
|
679
|
-
await appStore.initialize()
|
|
680
|
-
|
|
681
|
-
// In components
|
|
682
|
-
const wsStore = useWsStore()
|
|
683
|
-
const profile = computed(() => wsStore.profile)
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
---
|
|
687
|
-
|
|
688
|
-
## TypeScript Support
|
|
689
|
-
|
|
690
|
-
The stores are written in JavaScript but work perfectly with TypeScript projects. Type definitions coming soon!
|
|
691
|
-
|
|
692
|
-
---
|
|
693
|
-
|
|
694
|
-
## Testing
|
|
695
|
-
|
|
696
|
-
### Unit Testing Stores
|
|
697
|
-
|
|
698
|
-
```javascript
|
|
699
|
-
import { setActivePinia, createPinia } from 'pinia'
|
|
700
|
-
import { useWsStore } from 'dzql/client/stores'
|
|
701
|
-
|
|
702
|
-
describe('useWsStore', () => {
|
|
703
|
-
beforeEach(() => {
|
|
704
|
-
setActivePinia(createPinia())
|
|
705
|
-
})
|
|
706
|
-
|
|
707
|
-
it('initializes with correct state', () => {
|
|
708
|
-
const wsStore = useWsStore()
|
|
709
|
-
|
|
710
|
-
expect(wsStore.connectionState).toBe('disconnected')
|
|
711
|
-
expect(wsStore.appState).toBe('connecting')
|
|
712
|
-
expect(wsStore.profile).toBeNull()
|
|
713
|
-
})
|
|
714
|
-
})
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
---
|
|
718
|
-
|
|
719
|
-
## Summary
|
|
720
|
-
|
|
721
|
-
The DZQL canonical stores provide:
|
|
722
|
-
|
|
723
|
-
✅ **Three-phase lifecycle** - connecting → login → ready
|
|
724
|
-
✅ **Automatic auth handling** - Token storage, profile management
|
|
725
|
-
✅ **Router integration** - Programmatic navigation
|
|
726
|
-
✅ **Metadata caching** - Entity information
|
|
727
|
-
✅ **Direct WebSocket access** - For DZQL API calls
|
|
728
|
-
✅ **Clear patterns** - Easier for AI to understand
|
|
729
|
-
|
|
730
|
-
Use these stores as the foundation for all DZQL Vue.js applications!
|