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
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# TZQL Project Setup Guide
|
|
2
|
+
|
|
3
|
+
Complete guide for setting up a Vue/Pinia client project with TZQL.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
my-app/
|
|
9
|
+
├── package.json # Workspaces root
|
|
10
|
+
├── bun.lock # Single lockfile
|
|
11
|
+
├── domain.js # TZQL domain definition
|
|
12
|
+
├── .env # Environment variables
|
|
13
|
+
├── compose.yml # PostgreSQL for development
|
|
14
|
+
├── generated/ # DO NOT EDIT - compiled output
|
|
15
|
+
│ ├── client/ # WebSocket client, Pinia stores, types
|
|
16
|
+
│ ├── db/migrations/ # PostgreSQL schema
|
|
17
|
+
│ └── runtime/ # Server manifest
|
|
18
|
+
├── src/ # Vue client workspace
|
|
19
|
+
│ ├── package.json
|
|
20
|
+
│ ├── vite.config.ts
|
|
21
|
+
│ ├── tsconfig.app.json
|
|
22
|
+
│ └── src/
|
|
23
|
+
│ ├── main.ts
|
|
24
|
+
│ ├── App.vue
|
|
25
|
+
│ ├── composables/
|
|
26
|
+
│ ├── components/
|
|
27
|
+
│ └── views/
|
|
28
|
+
└── server/ # TZQL server workspace
|
|
29
|
+
├── package.json
|
|
30
|
+
└── index.ts
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 1. Bun Workspaces
|
|
34
|
+
|
|
35
|
+
Root `package.json`:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"name": "my-app",
|
|
40
|
+
"private": true,
|
|
41
|
+
"workspaces": ["src", "server"],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"compile": "tzql compile domain.js -o generated",
|
|
44
|
+
"db": "docker compose down -v && docker compose up -d",
|
|
45
|
+
"logs": "docker compose logs -f",
|
|
46
|
+
"dev": "concurrently -n server,client -c blue,green \"bun run --filter @my-app/server dev\" \"bun run --filter @my-app/client dev\""
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"concurrently": "^9.2.1",
|
|
50
|
+
"tzql": "link:tzql"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Client `src/package.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"name": "@my-app/client",
|
|
60
|
+
"private": true,
|
|
61
|
+
"type": "module",
|
|
62
|
+
"scripts": {
|
|
63
|
+
"dev": "vite",
|
|
64
|
+
"build": "vite build"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"pinia": "^3.0.4",
|
|
68
|
+
"vue": "^3.5.25",
|
|
69
|
+
"vue-router": "^4.6.3"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@vitejs/plugin-vue": "^6.0.2",
|
|
73
|
+
"vite": "^7.2.4",
|
|
74
|
+
"typescript": "~5.9.0"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Server `server/package.json`:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"name": "@my-app/server",
|
|
84
|
+
"private": true,
|
|
85
|
+
"type": "module",
|
|
86
|
+
"scripts": {
|
|
87
|
+
"dev": "cd .. && bun run server/index.ts"
|
|
88
|
+
},
|
|
89
|
+
"dependencies": {
|
|
90
|
+
"tzql": "link:tzql"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Important:** The server script uses `cd ..` to run from the project root so that:
|
|
96
|
+
- `.env` is found (dotenv loads from `process.cwd()`)
|
|
97
|
+
- `MANIFEST_PATH=./generated/runtime/manifest.json` resolves correctly
|
|
98
|
+
|
|
99
|
+
Server `server/index.ts`:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import "tzql";
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 2. Docker Compose for PostgreSQL
|
|
106
|
+
|
|
107
|
+
`compose.yml`:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
services:
|
|
111
|
+
db:
|
|
112
|
+
image: postgres:16-alpine
|
|
113
|
+
environment:
|
|
114
|
+
POSTGRES_USER: myapp
|
|
115
|
+
POSTGRES_PASSWORD: myapp
|
|
116
|
+
POSTGRES_DB: myapp
|
|
117
|
+
ports:
|
|
118
|
+
- "5432:5432"
|
|
119
|
+
volumes:
|
|
120
|
+
- pgdata:/var/lib/postgresql/data
|
|
121
|
+
- ./generated/db/migrations:/docker-entrypoint-initdb.d:ro
|
|
122
|
+
|
|
123
|
+
volumes:
|
|
124
|
+
pgdata:
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Key:** Mount `generated/db/migrations` to `/docker-entrypoint-initdb.d` for automatic schema initialization on first run.
|
|
128
|
+
|
|
129
|
+
## 3. Environment Variables
|
|
130
|
+
|
|
131
|
+
`.env`:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
# Database
|
|
135
|
+
DATABASE_URL=postgres://myapp:myapp@localhost:5432/myapp
|
|
136
|
+
|
|
137
|
+
# Server
|
|
138
|
+
PORT=3000
|
|
139
|
+
MANIFEST_PATH=./generated/runtime/manifest.json
|
|
140
|
+
JWT_SECRET=dev-secret-change-in-production
|
|
141
|
+
|
|
142
|
+
# Client (Vite)
|
|
143
|
+
VITE_TZQL_TOKEN_NAME=myapp_token
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 4. Vite Configuration
|
|
147
|
+
|
|
148
|
+
`src/vite.config.ts`:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { fileURLToPath, URL } from 'node:url'
|
|
152
|
+
import { defineConfig } from 'vite'
|
|
153
|
+
import vue from '@vitejs/plugin-vue'
|
|
154
|
+
|
|
155
|
+
export default defineConfig({
|
|
156
|
+
plugins: [vue()],
|
|
157
|
+
resolve: {
|
|
158
|
+
alias: {
|
|
159
|
+
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
160
|
+
'@generated': fileURLToPath(new URL('../generated', import.meta.url)),
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
server: {
|
|
164
|
+
// Allow external access (for Docker-based tools like Playwright MCP)
|
|
165
|
+
host: '0.0.0.0',
|
|
166
|
+
allowedHosts: ['host.docker.internal'],
|
|
167
|
+
|
|
168
|
+
// Proxy WebSocket to TZQL server
|
|
169
|
+
proxy: {
|
|
170
|
+
'/ws': {
|
|
171
|
+
target: 'ws://localhost:3000',
|
|
172
|
+
ws: true,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Configuration explained:**
|
|
180
|
+
|
|
181
|
+
- `host: '0.0.0.0'` - Binds to all interfaces, required for Docker access
|
|
182
|
+
- `allowedHosts: ['host.docker.internal']` - Allows Playwright MCP (running in Docker) to connect
|
|
183
|
+
- `proxy: { '/ws': ... }` - Client connects to `/ws` on Vite's port, proxied to TZQL server on port 3000
|
|
184
|
+
|
|
185
|
+
**For Playwright MCP testing:** Navigate to `http://host.docker.internal:5173`
|
|
186
|
+
|
|
187
|
+
## 5. TypeScript Path Aliases
|
|
188
|
+
|
|
189
|
+
`src/tsconfig.app.json`:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
|
194
|
+
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
|
195
|
+
"compilerOptions": {
|
|
196
|
+
"paths": {
|
|
197
|
+
"@/*": ["./src/*"],
|
|
198
|
+
"@generated/*": ["../generated/*"]
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## 6. Authentication Composable
|
|
205
|
+
|
|
206
|
+
`src/src/composables/useTzql.ts`:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { ref } from 'vue'
|
|
210
|
+
import { ws } from '@generated/client'
|
|
211
|
+
|
|
212
|
+
const ready = ref(false)
|
|
213
|
+
const user = ref<any>(null)
|
|
214
|
+
const connectionError = ref<string | null>(null)
|
|
215
|
+
|
|
216
|
+
export function useTzql() {
|
|
217
|
+
async function connect(url?: string) {
|
|
218
|
+
try {
|
|
219
|
+
connectionError.value = null
|
|
220
|
+
ready.value = false
|
|
221
|
+
|
|
222
|
+
ws.onReady((profile: any) => {
|
|
223
|
+
if (!profile && localStorage.getItem('token')) {
|
|
224
|
+
localStorage.removeItem('token')
|
|
225
|
+
}
|
|
226
|
+
user.value = profile
|
|
227
|
+
ready.value = true
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
await ws.connect(url)
|
|
231
|
+
} catch (e: any) {
|
|
232
|
+
connectionError.value = e.message
|
|
233
|
+
throw e
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function login(email: string, password: string) {
|
|
238
|
+
const result = await ws.login({ email, password })
|
|
239
|
+
if (result?.user_id) {
|
|
240
|
+
user.value = result
|
|
241
|
+
}
|
|
242
|
+
return result
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function register(name: string, email: string, password: string) {
|
|
246
|
+
const result = await ws.register({ name, email, password })
|
|
247
|
+
if (result?.user_id) {
|
|
248
|
+
user.value = result
|
|
249
|
+
}
|
|
250
|
+
return result
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function logout() {
|
|
254
|
+
await ws.logout()
|
|
255
|
+
user.value = null
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { ws, ready, user, connectionError, connect, login, register, logout }
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## 7. App Entry Point
|
|
263
|
+
|
|
264
|
+
`src/src/main.ts`:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import './assets/main.css'
|
|
268
|
+
import { createApp } from 'vue'
|
|
269
|
+
import { createPinia } from 'pinia'
|
|
270
|
+
import App from './App.vue'
|
|
271
|
+
import router from './router'
|
|
272
|
+
import { useTzql } from './composables/useTzql'
|
|
273
|
+
|
|
274
|
+
const app = createApp(App)
|
|
275
|
+
app.use(createPinia())
|
|
276
|
+
app.use(router)
|
|
277
|
+
app.mount('#app')
|
|
278
|
+
|
|
279
|
+
// Connect to TZQL server
|
|
280
|
+
const { connect } = useTzql()
|
|
281
|
+
connect()
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## 8. App Component Pattern
|
|
285
|
+
|
|
286
|
+
`src/src/App.vue`:
|
|
287
|
+
|
|
288
|
+
```vue
|
|
289
|
+
<script setup lang="ts">
|
|
290
|
+
import { useTzql } from '@/composables/useTzql'
|
|
291
|
+
import LoginModal from '@/components/LoginModal.vue'
|
|
292
|
+
|
|
293
|
+
const { ready, user, logout } = useTzql()
|
|
294
|
+
</script>
|
|
295
|
+
|
|
296
|
+
<template>
|
|
297
|
+
<div v-if="!ready" class="loading">Connecting...</div>
|
|
298
|
+
<LoginModal v-else-if="!user" />
|
|
299
|
+
<template v-else>
|
|
300
|
+
<header>
|
|
301
|
+
<nav>
|
|
302
|
+
<RouterLink to="/">Home</RouterLink>
|
|
303
|
+
<span>{{ user.name }}</span>
|
|
304
|
+
<button @click="logout">Logout</button>
|
|
305
|
+
</nav>
|
|
306
|
+
</header>
|
|
307
|
+
<main>
|
|
308
|
+
<RouterView />
|
|
309
|
+
</main>
|
|
310
|
+
</template>
|
|
311
|
+
</template>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Key points:**
|
|
315
|
+
- No router guards needed - WebSocket `connection:ready` is the source of truth
|
|
316
|
+
- Shows loading until connection established
|
|
317
|
+
- Shows login if no user, app content if authenticated
|
|
318
|
+
- Login/logout reactively update UI - no page reload needed
|
|
319
|
+
|
|
320
|
+
## 9. Using Generated Stores
|
|
321
|
+
|
|
322
|
+
```vue
|
|
323
|
+
<script setup lang="ts">
|
|
324
|
+
import { computed, watchEffect } from 'vue'
|
|
325
|
+
import { useRoute } from 'vue-router'
|
|
326
|
+
import { useTzql } from '@/composables/useTzql'
|
|
327
|
+
import { useVenueDetailStore } from '@generated/client/stores/useVenueDetailStore.js'
|
|
328
|
+
|
|
329
|
+
const route = useRoute()
|
|
330
|
+
const { ws } = useTzql()
|
|
331
|
+
const store = useVenueDetailStore()
|
|
332
|
+
|
|
333
|
+
const venueId = computed(() => Number(route.params.id))
|
|
334
|
+
const docKey = computed(() => JSON.stringify({ venue_id: venueId.value }))
|
|
335
|
+
|
|
336
|
+
// Bind to subscription when venueId changes
|
|
337
|
+
watchEffect(() => {
|
|
338
|
+
if (venueId.value) {
|
|
339
|
+
store.bind({ venue_id: venueId.value })
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
// Reactive data from store
|
|
344
|
+
const doc = computed(() => store.documents[docKey.value])
|
|
345
|
+
const loading = computed(() => !doc.value || doc.value.loading.value)
|
|
346
|
+
const venue = computed(() => doc.value?.data.value)
|
|
347
|
+
const sites = computed(() => doc.value?.data.value?.sites ?? [])
|
|
348
|
+
|
|
349
|
+
// CRUD operations
|
|
350
|
+
async function createSite(name: string) {
|
|
351
|
+
await ws.api.save_sites({ venue_id: venueId.value, name })
|
|
352
|
+
// No refetch needed - patch arrives via WebSocket
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function updateSite(id: number, name: string) {
|
|
356
|
+
await ws.api.save_sites({ id, venue_id: venueId.value, name })
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function deleteSite(id: number) {
|
|
360
|
+
await ws.api.delete_sites({ id })
|
|
361
|
+
}
|
|
362
|
+
</script>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## 10. CLI Database Access with invj
|
|
366
|
+
|
|
367
|
+
Create `tasks.js` in the project root to enable CLI database operations:
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
import { TzqlNamespace } from "tzql/namespace";
|
|
371
|
+
|
|
372
|
+
export class Tasks {
|
|
373
|
+
constructor() {
|
|
374
|
+
this.tzql = new TzqlNamespace();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
This integrates with `invj` to provide direct database access:
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
# List available commands
|
|
383
|
+
invj -l
|
|
384
|
+
|
|
385
|
+
# Available tzql commands:
|
|
386
|
+
# tzql:entities - List all entities
|
|
387
|
+
# tzql:subscribables - List all subscribables
|
|
388
|
+
# tzql:functions - List all functions
|
|
389
|
+
# tzql:search <entity> [json] - Search records
|
|
390
|
+
# tzql:get <entity> [json] - Get single record
|
|
391
|
+
# tzql:save <entity> [json] - Create/update record
|
|
392
|
+
# tzql:delete <entity> [json] - Delete record
|
|
393
|
+
# tzql:lookup <entity> [json] - Autocomplete lookup
|
|
394
|
+
# tzql:call <func> [json] - Call custom function
|
|
395
|
+
# tzql:subscribe <name> [json] - Get subscribable data
|
|
396
|
+
|
|
397
|
+
# Examples
|
|
398
|
+
invj tzql:entities
|
|
399
|
+
invj tzql:search venues
|
|
400
|
+
invj tzql:search venues '{"org_id": 1}'
|
|
401
|
+
invj tzql:get venues '{"id": 1}'
|
|
402
|
+
invj tzql:save venues '{"org_id": 1, "name": "New Venue", "address": "123 Main St"}'
|
|
403
|
+
invj tzql:delete venues '{"id": 1}'
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Development Workflow
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
# 1. Start PostgreSQL
|
|
410
|
+
bun run db
|
|
411
|
+
|
|
412
|
+
# 2. Compile domain (after any domain.js changes)
|
|
413
|
+
bun run compile
|
|
414
|
+
|
|
415
|
+
# 3. Start dev servers
|
|
416
|
+
bun run dev
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
After `bun run db`, the database initializes with migrations automatically. After domain changes, run `bun run compile` then restart the server.
|
|
420
|
+
|
|
421
|
+
## Linking TZQL for Local Development
|
|
422
|
+
|
|
423
|
+
If developing TZQL locally:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
cd /path/to/tzql
|
|
427
|
+
bun link
|
|
428
|
+
|
|
429
|
+
cd /path/to/my-app
|
|
430
|
+
# tzql is already in package.json as "link:tzql"
|
|
431
|
+
bun install
|
|
432
|
+
```
|
package/examples/blog.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// TZQL Entity Definition Example
|
|
2
|
+
|
|
3
|
+
export const entities = {
|
|
4
|
+
posts: {
|
|
5
|
+
schema: {
|
|
6
|
+
id: 'serial PRIMARY KEY',
|
|
7
|
+
title: 'text NOT NULL',
|
|
8
|
+
content: 'text',
|
|
9
|
+
author_id: 'int NOT NULL', // In a real app, this would reference users(id)
|
|
10
|
+
created_at: 'timestamptz DEFAULT now()'
|
|
11
|
+
},
|
|
12
|
+
permissions: {
|
|
13
|
+
view: [], // Public
|
|
14
|
+
create: ['@author_id == @user_id'], // Only create for self
|
|
15
|
+
update: ['@author_id == @user_id'], // Only owner
|
|
16
|
+
delete: ['@author_id == @user_id'] // Only owner
|
|
17
|
+
},
|
|
18
|
+
graphRules: {
|
|
19
|
+
on_create: {
|
|
20
|
+
actions: [
|
|
21
|
+
{ type: 'reactor', name: 'notify_subscribers', params: { post_id: '@id' } }
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
comments: {
|
|
27
|
+
schema: {
|
|
28
|
+
id: 'serial PRIMARY KEY',
|
|
29
|
+
post_id: 'int NOT NULL REFERENCES posts(id) ON DELETE CASCADE',
|
|
30
|
+
content: 'text NOT NULL',
|
|
31
|
+
author_id: 'int NOT NULL'
|
|
32
|
+
},
|
|
33
|
+
permissions: {
|
|
34
|
+
view: [],
|
|
35
|
+
create: [],
|
|
36
|
+
delete: ['@author_id == @user_id']
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const subscribables = {
|
|
42
|
+
post_detail: {
|
|
43
|
+
params: { post_id: 'int' },
|
|
44
|
+
root: { entity: 'posts', key: 'post_id' },
|
|
45
|
+
includes: {
|
|
46
|
+
comments: { entity: 'comments', filter: { post_id: '@id' } }
|
|
47
|
+
},
|
|
48
|
+
scopeTables: ['posts', 'comments']
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// TZQL Entity Definition Example (INVALID)
|
|
2
|
+
|
|
3
|
+
export const entities = {
|
|
4
|
+
posts: {
|
|
5
|
+
schema: { id: 'serial PRIMARY KEY' },
|
|
6
|
+
permissions: {}
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const subscribables = {
|
|
11
|
+
broken_feed: {
|
|
12
|
+
params: {},
|
|
13
|
+
root: { entity: 'posts' },
|
|
14
|
+
includes: {
|
|
15
|
+
comments: { entity: 'missing_table' } // <--- Error: 'missing_table' does not exist
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|