dzql 0.6.1 → 0.6.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.
- package/dist/client/stores/useMyProfileStore.ts +1 -1
- package/dist/client/stores/useOrgDashboardStore.ts +1 -1
- package/dist/client/stores/useVenueDetailStore.ts +1 -1
- package/dist/client/ws.ts +5 -5
- package/dist/db/migrations/{20251229T212912022Z_subscribables.sql → 20260101T235039268Z_subscribables.sql} +3 -3
- package/docs/README.md +1 -1
- package/docs/feature-requests/applyPatch-bug-report.md +2 -2
- package/docs/feature-requests/connection-ready-profile.md +1 -1
- package/docs/feature-requests/hidden-bug-report.md +4 -4
- package/docs/feature-requests/todo.md +1 -1
- package/docs/for_ai.md +17 -17
- package/docs/project-setup.md +37 -37
- package/package.json +5 -1
- package/src/cli/codegen/client.ts +5 -5
- package/src/cli/codegen/pinia.ts +1 -1
- package/src/cli/codegen/subscribable_sql.ts +64 -3
- package/src/cli/codegen/subscribable_store.ts +1 -1
- package/src/client/ws.ts +14 -14
- package/src/runtime/index.ts +1 -1
- package/src/runtime/namespace.ts +47 -47
- package/src/runtime/ws.ts +2 -0
- package/tests/client.test.ts +3 -3
- package/tests/namespace.test.ts +22 -22
- /package/dist/db/migrations/{20251229T212912022Z_schema.sql → 20260101T235039268Z_schema.sql} +0 -0
package/dist/client/ws.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// Generated by
|
|
1
|
+
// Generated by DZQL Compiler v2.0.0
|
|
2
2
|
// Do not edit this file directly.
|
|
3
3
|
|
|
4
|
-
import { WebSocketManager } from '
|
|
4
|
+
import { WebSocketManager } from 'dzql/client';
|
|
5
5
|
|
|
6
6
|
// Filter operators for search queries
|
|
7
7
|
export interface FilterOperators<T> {
|
|
@@ -556,7 +556,7 @@ export interface MyProfileParams {
|
|
|
556
556
|
|
|
557
557
|
|
|
558
558
|
/** API interface with typed methods */
|
|
559
|
-
export interface
|
|
559
|
+
export interface DzqlAPI {
|
|
560
560
|
login_user: (params: Record<string, unknown>) => Promise<unknown>;
|
|
561
561
|
register_user: (params: Record<string, unknown>) => Promise<unknown>;
|
|
562
562
|
get_users: (params: UsersPK) => Promise<Users | null>;
|
|
@@ -632,7 +632,7 @@ export interface TzqlAPI {
|
|
|
632
632
|
|
|
633
633
|
/** Extended WebSocket manager with typed API */
|
|
634
634
|
export class GeneratedWebSocketManager extends WebSocketManager {
|
|
635
|
-
api:
|
|
635
|
+
api: DzqlAPI;
|
|
636
636
|
|
|
637
637
|
constructor(options: { url?: string; reconnect?: boolean } = {}) {
|
|
638
638
|
super(options);
|
|
@@ -708,7 +708,7 @@ export class GeneratedWebSocketManager extends WebSocketManager {
|
|
|
708
708
|
subscribe_venue_detail: (params: Record<string, unknown>, callback: (data: unknown) => void) => this.subscribe('subscribe_venue_detail', params, callback),
|
|
709
709
|
subscribe_org_dashboard: (params: Record<string, unknown>, callback: (data: unknown) => void) => this.subscribe('subscribe_org_dashboard', params, callback),
|
|
710
710
|
subscribe_my_profile: (params: Record<string, unknown>, callback: (data: unknown) => void) => this.subscribe('subscribe_my_profile', params, callback),
|
|
711
|
-
} as
|
|
711
|
+
} as DzqlAPI;
|
|
712
712
|
}
|
|
713
713
|
}
|
|
714
714
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
-- Subscribable: venue_detail
|
|
3
3
|
-- Root Entity: venues
|
|
4
4
|
-- Scope Tables: venues, organisations, sites, allocations
|
|
5
|
-
-- Generated:
|
|
5
|
+
-- Generated: 2026-01-01T23:50:39.267Z
|
|
6
6
|
-- ============================================================================
|
|
7
7
|
|
|
8
8
|
CREATE OR REPLACE FUNCTION dzql_v2.venue_detail_can_subscribe(
|
|
@@ -125,7 +125,7 @@ $$;
|
|
|
125
125
|
-- Subscribable: org_dashboard
|
|
126
126
|
-- Root Entity: organisations
|
|
127
127
|
-- Scope Tables: organisations, venues, sites, products, packages, brands, artwork
|
|
128
|
-
-- Generated:
|
|
128
|
+
-- Generated: 2026-01-01T23:50:39.267Z
|
|
129
129
|
-- ============================================================================
|
|
130
130
|
|
|
131
131
|
CREATE OR REPLACE FUNCTION dzql_v2.org_dashboard_can_subscribe(
|
|
@@ -276,7 +276,7 @@ $$;
|
|
|
276
276
|
-- Subscribable: my_profile
|
|
277
277
|
-- Root Entity: users
|
|
278
278
|
-- Scope Tables: users, acts_for, organisations
|
|
279
|
-
-- Generated:
|
|
279
|
+
-- Generated: 2026-01-01T23:50:39.267Z
|
|
280
280
|
-- ============================================================================
|
|
281
281
|
|
|
282
282
|
CREATE OR REPLACE FUNCTION dzql_v2.my_profile_can_subscribe(
|
package/docs/README.md
CHANGED
|
@@ -147,7 +147,7 @@ await ws.logout(); // Clears token and user state
|
|
|
147
147
|
|
|
148
148
|
## Generated Pinia Subscribable Stores
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
DZQL generates Pinia stores for each subscribable that handle:
|
|
151
151
|
- Initial data fetch via WebSocket subscription
|
|
152
152
|
- Automatic realtime patching when related data changes
|
|
153
153
|
- Deduplication of subscriptions by parameter key
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DZQL Bug Report
|
|
2
2
|
|
|
3
3
|
## Generated Store applyPatch Doesn't Match Data Structure
|
|
4
4
|
|
|
@@ -79,7 +79,7 @@ to_jsonb(rel.*) || jsonb_build_object(...)
|
|
|
79
79
|
|
|
80
80
|
## Environment
|
|
81
81
|
|
|
82
|
-
-
|
|
82
|
+
- dzql version: local development (linked)
|
|
83
83
|
- Database: PostgreSQL 17 (Docker)
|
|
84
84
|
- Client: Vue 3 + Pinia + TypeScript
|
|
85
85
|
- Runtime: Bun
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## Summary
|
|
6
6
|
|
|
7
|
-
When a client connects to the
|
|
7
|
+
When a client connects to the DZQL WebSocket server, the server should immediately send a connection:ready message containing the authenticated user profile (or null if not authenticated).
|
|
8
8
|
|
|
9
9
|
## Current Behavior
|
|
10
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DZQL Bug Report
|
|
2
2
|
|
|
3
|
-
Bugs discovered while building the Venues application with
|
|
3
|
+
Bugs discovered while building the Venues application with DZQL.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -97,7 +97,7 @@ Updated `compileSubscribePermission` in `subscribable_sql.ts` to map param names
|
|
|
97
97
|
|
|
98
98
|
## Environment
|
|
99
99
|
|
|
100
|
-
-
|
|
100
|
+
- dzql version: (linked local development version)
|
|
101
101
|
- Database: PostgreSQL 17 (via Docker)
|
|
102
102
|
- Client: Vue 3 + Pinia + TypeScript
|
|
103
103
|
- Runtime: Bun
|
|
@@ -106,6 +106,6 @@ Updated `compileSubscribePermission` in `subscribable_sql.ts` to map param names
|
|
|
106
106
|
|
|
107
107
|
## Related Files
|
|
108
108
|
|
|
109
|
-
Detailed bug documents created in
|
|
109
|
+
Detailed bug documents created in dzql docs:
|
|
110
110
|
- `/packages/tzql/docs/feature-requests/hidden-fields-in-subscribables.md`
|
|
111
111
|
- `/packages/tzql/docs/feature-requests/subscribable-param-key-bug.md`
|
package/docs/for_ai.md
CHANGED
|
@@ -288,7 +288,7 @@ Use `type: 'reactor'` for anything that requires Node.js (Email, Stripe, AI proc
|
|
|
288
288
|
|
|
289
289
|
## Custom Functions
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
DZQL supports two types of custom functions that can be called via RPC:
|
|
292
292
|
|
|
293
293
|
### 1. SQL Custom Functions
|
|
294
294
|
|
|
@@ -367,7 +367,7 @@ JavaScript custom functions run in the Bun/Node runtime. They are ideal for:
|
|
|
367
367
|
|
|
368
368
|
```typescript
|
|
369
369
|
// server.ts or wherever you start your runtime
|
|
370
|
-
import { registerJsFunction } from '
|
|
370
|
+
import { registerJsFunction } from 'dzql/runtime';
|
|
371
371
|
|
|
372
372
|
// Simple function
|
|
373
373
|
registerJsFunction('hello_world', async (ctx) => {
|
|
@@ -471,7 +471,7 @@ interface JsFunctionContext {
|
|
|
471
471
|
|
|
472
472
|
## Unmanaged Entities (Junction Tables)
|
|
473
473
|
|
|
474
|
-
For junction tables used in many-to-many relationships, you typically don't want
|
|
474
|
+
For junction tables used in many-to-many relationships, you typically don't want DZQL to generate CRUD functions. These tables are managed via the M2M relationship on the parent entity.
|
|
475
475
|
|
|
476
476
|
Use `managed: false` to skip CRUD generation:
|
|
477
477
|
|
|
@@ -580,7 +580,7 @@ await ws.logout(); // Clears token, user state, and reconnects
|
|
|
580
580
|
|
|
581
581
|
## CLI Integration with Namespace
|
|
582
582
|
|
|
583
|
-
|
|
583
|
+
DZQL provides a namespace export for CLI tools like `invokej` to interact with the database directly without going through the WebSocket runtime.
|
|
584
584
|
|
|
585
585
|
### Setup
|
|
586
586
|
|
|
@@ -599,19 +599,19 @@ const dzql = new DzqlNamespace(sql, manifest);
|
|
|
599
599
|
|
|
600
600
|
```typescript
|
|
601
601
|
// Get a record
|
|
602
|
-
const venue = await
|
|
602
|
+
const venue = await dzql.get('venues', { id: 1 }, userId);
|
|
603
603
|
|
|
604
604
|
// Save (create or update)
|
|
605
|
-
const newVenue = await
|
|
605
|
+
const newVenue = await dzql.save('venues', { name: 'New Venue', org_id: 1 }, userId);
|
|
606
606
|
|
|
607
607
|
// Delete
|
|
608
|
-
const deleted = await
|
|
608
|
+
const deleted = await dzql.delete('venues', { id: 1 }, userId);
|
|
609
609
|
|
|
610
610
|
// Search with filters
|
|
611
|
-
const venues = await
|
|
611
|
+
const venues = await dzql.search('venues', { org_id: 1, limit: 10 }, userId);
|
|
612
612
|
|
|
613
613
|
// Lookup for autocomplete
|
|
614
|
-
const options = await
|
|
614
|
+
const options = await dzql.lookup('venues', { q: 'test' }, userId);
|
|
615
615
|
```
|
|
616
616
|
|
|
617
617
|
### Ad-hoc Function Calls
|
|
@@ -620,17 +620,17 @@ Call any function in the manifest directly:
|
|
|
620
620
|
|
|
621
621
|
```typescript
|
|
622
622
|
// Call a custom function
|
|
623
|
-
const result = await
|
|
623
|
+
const result = await dzql.call('calculate_org_stats', { org_id: 1 }, userId);
|
|
624
624
|
|
|
625
625
|
// Call a subscribable getter
|
|
626
|
-
const detail = await
|
|
626
|
+
const detail = await dzql.call('get_venue_detail', { venue_id: 1 }, userId);
|
|
627
627
|
```
|
|
628
628
|
|
|
629
629
|
### List Available Functions
|
|
630
630
|
|
|
631
631
|
```typescript
|
|
632
632
|
// Get all functions from manifest
|
|
633
|
-
const functions =
|
|
633
|
+
const functions = dzql.functions();
|
|
634
634
|
// Returns: ['login_user', 'register_user', 'get_venues', 'save_venues', ...]
|
|
635
635
|
```
|
|
636
636
|
|
|
@@ -639,11 +639,11 @@ const functions = tzql.functions();
|
|
|
639
639
|
The namespace is designed for use with `invokej`, a CLI tool for invoking functions:
|
|
640
640
|
|
|
641
641
|
```bash
|
|
642
|
-
# In your invokej configuration, register the
|
|
643
|
-
invokej
|
|
644
|
-
invokej
|
|
645
|
-
invokej
|
|
646
|
-
invokej
|
|
642
|
+
# In your invokej configuration, register the DZQL namespace
|
|
643
|
+
invokej dzql:get venues '{"id": 1}'
|
|
644
|
+
invokej dzql:save venues '{"name": "Updated Venue", "id": 1}'
|
|
645
|
+
invokej dzql:call calculate_org_stats '{"org_id": 1}'
|
|
646
|
+
invokej dzql:functions
|
|
647
647
|
```
|
|
648
648
|
|
|
649
649
|
**Key Points:**
|
package/docs/project-setup.md
CHANGED
|
@@ -26,7 +26,7 @@ If you prefer to set up manually:
|
|
|
26
26
|
my-app/
|
|
27
27
|
├── package.json # Workspaces root
|
|
28
28
|
├── bun.lock # Single lockfile
|
|
29
|
-
├── domain.js #
|
|
29
|
+
├── domain.js # DZQL domain definition
|
|
30
30
|
├── .env # Environment variables
|
|
31
31
|
├── compose.yml # PostgreSQL for development
|
|
32
32
|
├── generated/ # DO NOT EDIT - compiled output
|
|
@@ -43,7 +43,7 @@ my-app/
|
|
|
43
43
|
│ ├── composables/
|
|
44
44
|
│ ├── components/
|
|
45
45
|
│ └── views/
|
|
46
|
-
└── server/ #
|
|
46
|
+
└── server/ # DZQL server workspace
|
|
47
47
|
├── package.json
|
|
48
48
|
└── index.ts
|
|
49
49
|
```
|
|
@@ -164,7 +164,7 @@ MANIFEST_PATH=./generated/runtime/manifest.json
|
|
|
164
164
|
JWT_SECRET=dev-secret-change-in-production
|
|
165
165
|
|
|
166
166
|
# Client (Vite)
|
|
167
|
-
|
|
167
|
+
VITE_DZQL_TOKEN_NAME=myapp_token
|
|
168
168
|
```
|
|
169
169
|
|
|
170
170
|
## 4. Vite Configuration
|
|
@@ -189,7 +189,7 @@ export default defineConfig({
|
|
|
189
189
|
host: '0.0.0.0',
|
|
190
190
|
allowedHosts: ['host.docker.internal'],
|
|
191
191
|
|
|
192
|
-
// Proxy WebSocket to
|
|
192
|
+
// Proxy WebSocket to DZQL server
|
|
193
193
|
proxy: {
|
|
194
194
|
'/ws': {
|
|
195
195
|
target: 'ws://localhost:3000',
|
|
@@ -204,7 +204,7 @@ export default defineConfig({
|
|
|
204
204
|
|
|
205
205
|
- `host: '0.0.0.0'` - Binds to all interfaces, required for Docker access
|
|
206
206
|
- `allowedHosts: ['host.docker.internal']` - Allows Playwright MCP (running in Docker) to connect
|
|
207
|
-
- `proxy: { '/ws': ... }` - Client connects to `/ws` on Vite's port, proxied to
|
|
207
|
+
- `proxy: { '/ws': ... }` - Client connects to `/ws` on Vite's port, proxied to DZQL server on port 3000
|
|
208
208
|
|
|
209
209
|
**For Playwright MCP testing:** Navigate to `http://host.docker.internal:5173`
|
|
210
210
|
|
|
@@ -227,7 +227,7 @@ export default defineConfig({
|
|
|
227
227
|
|
|
228
228
|
## 6. Authentication Composable
|
|
229
229
|
|
|
230
|
-
`src/src/composables/
|
|
230
|
+
`src/src/composables/useDzql.ts`:
|
|
231
231
|
|
|
232
232
|
```typescript
|
|
233
233
|
import { ref } from 'vue'
|
|
@@ -237,7 +237,7 @@ const ready = ref(false)
|
|
|
237
237
|
const user = ref<any>(null)
|
|
238
238
|
const connectionError = ref<string | null>(null)
|
|
239
239
|
|
|
240
|
-
export function
|
|
240
|
+
export function useDzql() {
|
|
241
241
|
async function connect(url?: string) {
|
|
242
242
|
try {
|
|
243
243
|
connectionError.value = null
|
|
@@ -293,15 +293,15 @@ import { createApp } from 'vue'
|
|
|
293
293
|
import { createPinia } from 'pinia'
|
|
294
294
|
import App from './App.vue'
|
|
295
295
|
import router from './router'
|
|
296
|
-
import {
|
|
296
|
+
import { useDzql } from './composables/useDzql'
|
|
297
297
|
|
|
298
298
|
const app = createApp(App)
|
|
299
299
|
app.use(createPinia())
|
|
300
300
|
app.use(router)
|
|
301
301
|
app.mount('#app')
|
|
302
302
|
|
|
303
|
-
// Connect to
|
|
304
|
-
const { connect } =
|
|
303
|
+
// Connect to DZQL server
|
|
304
|
+
const { connect } = useDzql()
|
|
305
305
|
connect()
|
|
306
306
|
```
|
|
307
307
|
|
|
@@ -311,10 +311,10 @@ connect()
|
|
|
311
311
|
|
|
312
312
|
```vue
|
|
313
313
|
<script setup lang="ts">
|
|
314
|
-
import {
|
|
314
|
+
import { useDzql } from '@/composables/useDzql'
|
|
315
315
|
import LoginModal from '@/components/LoginModal.vue'
|
|
316
316
|
|
|
317
|
-
const { ready, user, logout } =
|
|
317
|
+
const { ready, user, logout } = useDzql()
|
|
318
318
|
</script>
|
|
319
319
|
|
|
320
320
|
<template>
|
|
@@ -347,11 +347,11 @@ const { ready, user, logout } = useTzql()
|
|
|
347
347
|
<script setup lang="ts">
|
|
348
348
|
import { computed, watchEffect } from 'vue'
|
|
349
349
|
import { useRoute } from 'vue-router'
|
|
350
|
-
import {
|
|
350
|
+
import { useDzql } from '@/composables/useDzql'
|
|
351
351
|
import { useVenueDetailStore } from '@generated/client/stores/useVenueDetailStore.js'
|
|
352
352
|
|
|
353
353
|
const route = useRoute()
|
|
354
|
-
const { ws } =
|
|
354
|
+
const { ws } = useDzql()
|
|
355
355
|
const store = useVenueDetailStore()
|
|
356
356
|
|
|
357
357
|
const venueId = computed(() => Number(route.params.id))
|
|
@@ -391,11 +391,11 @@ async function deleteSite(id: number) {
|
|
|
391
391
|
Create `tasks.js` in the project root to enable CLI database operations:
|
|
392
392
|
|
|
393
393
|
```javascript
|
|
394
|
-
import {
|
|
394
|
+
import { DzqlNamespace } from "dzql/namespace";
|
|
395
395
|
|
|
396
396
|
export class Tasks {
|
|
397
397
|
constructor() {
|
|
398
|
-
this.
|
|
398
|
+
this.dzql = new DzqlNamespace();
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
```
|
|
@@ -406,25 +406,25 @@ This integrates with `invj` to provide direct database access:
|
|
|
406
406
|
# List available commands
|
|
407
407
|
invj -l
|
|
408
408
|
|
|
409
|
-
# Available
|
|
410
|
-
#
|
|
411
|
-
#
|
|
412
|
-
#
|
|
413
|
-
#
|
|
414
|
-
#
|
|
415
|
-
#
|
|
416
|
-
#
|
|
417
|
-
#
|
|
418
|
-
#
|
|
419
|
-
#
|
|
409
|
+
# Available dzql commands:
|
|
410
|
+
# dzql:entities - List all entities
|
|
411
|
+
# dzql:subscribables - List all subscribables
|
|
412
|
+
# dzql:functions - List all functions
|
|
413
|
+
# dzql:search <entity> [json] - Search records
|
|
414
|
+
# dzql:get <entity> [json] - Get single record
|
|
415
|
+
# dzql:save <entity> [json] - Create/update record
|
|
416
|
+
# dzql:delete <entity> [json] - Delete record
|
|
417
|
+
# dzql:lookup <entity> [json] - Autocomplete lookup
|
|
418
|
+
# dzql:call <func> [json] - Call custom function
|
|
419
|
+
# dzql:subscribe <name> [json] - Get subscribable data
|
|
420
420
|
|
|
421
421
|
# Examples
|
|
422
|
-
invj
|
|
423
|
-
invj
|
|
424
|
-
invj
|
|
425
|
-
invj
|
|
426
|
-
invj
|
|
427
|
-
invj
|
|
422
|
+
invj dzql:entities
|
|
423
|
+
invj dzql:search venues
|
|
424
|
+
invj dzql:search venues '{"org_id": 1}'
|
|
425
|
+
invj dzql:get venues '{"id": 1}'
|
|
426
|
+
invj dzql:save venues '{"org_id": 1, "name": "New Venue", "address": "123 Main St"}'
|
|
427
|
+
invj dzql:delete venues '{"id": 1}'
|
|
428
428
|
```
|
|
429
429
|
|
|
430
430
|
## Development Workflow
|
|
@@ -442,15 +442,15 @@ bun run dev
|
|
|
442
442
|
|
|
443
443
|
After `bun run db`, the database initializes with migrations automatically. After domain changes, run `bun run compile` then restart the server.
|
|
444
444
|
|
|
445
|
-
## Linking
|
|
445
|
+
## Linking DZQL for Local Development
|
|
446
446
|
|
|
447
|
-
If developing
|
|
447
|
+
If developing DZQL locally:
|
|
448
448
|
|
|
449
449
|
```bash
|
|
450
|
-
cd /path/to/
|
|
450
|
+
cd /path/to/dzql
|
|
451
451
|
bun link
|
|
452
452
|
|
|
453
453
|
cd /path/to/my-app
|
|
454
|
-
#
|
|
454
|
+
# dzql is already in package.json as "link:dzql"
|
|
455
455
|
bun install
|
|
456
456
|
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dzql",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Database-first real-time framework with TypeScript support",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/blueshed/dzql"
|
|
8
|
+
},
|
|
5
9
|
"type": "module",
|
|
6
10
|
"bin": {
|
|
7
11
|
"dzql": "./src/cli/index.ts"
|
|
@@ -64,21 +64,21 @@ export function generateClientSDK(manifest: Manifest): string {
|
|
|
64
64
|
return ` ${funcName}: (params: ${paramType}) => Promise<${returnType}>;`;
|
|
65
65
|
}).join('\n');
|
|
66
66
|
|
|
67
|
-
return `// Generated by
|
|
67
|
+
return `// Generated by DZQL Compiler v${manifest.version}
|
|
68
68
|
// Do not edit this file directly.
|
|
69
69
|
|
|
70
|
-
import { WebSocketManager } from '
|
|
70
|
+
import { WebSocketManager } from 'dzql/client';
|
|
71
71
|
|
|
72
72
|
${typeDefs}
|
|
73
73
|
|
|
74
74
|
/** API interface with typed methods */
|
|
75
|
-
export interface
|
|
75
|
+
export interface DzqlAPI {
|
|
76
76
|
${apiMethods}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/** Extended WebSocket manager with typed API */
|
|
80
80
|
export class GeneratedWebSocketManager extends WebSocketManager {
|
|
81
|
-
api:
|
|
81
|
+
api: DzqlAPI;
|
|
82
82
|
|
|
83
83
|
constructor(options: { url?: string; reconnect?: boolean } = {}) {
|
|
84
84
|
super(options);
|
|
@@ -89,7 +89,7 @@ ${regularFunctions.map(([funcName]) =>
|
|
|
89
89
|
${subscriptionFunctions.map(([funcName]) =>
|
|
90
90
|
` ${funcName}: (params: Record<string, unknown>, callback: (data: unknown) => void) => this.subscribe('${funcName}', params, callback),`
|
|
91
91
|
).join('\n')}
|
|
92
|
-
} as
|
|
92
|
+
} as DzqlAPI;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
package/src/cli/codegen/pinia.ts
CHANGED
|
@@ -48,7 +48,7 @@ export function generatePiniaStore(manifest: Manifest, entityName: string): stri
|
|
|
48
48
|
? 'number'
|
|
49
49
|
: 'string';
|
|
50
50
|
|
|
51
|
-
return `// Generated by
|
|
51
|
+
return `// Generated by DZQL Compiler v${manifest.version}
|
|
52
52
|
// Do not edit this file directly.
|
|
53
53
|
|
|
54
54
|
import { defineStore } from 'pinia';
|
|
@@ -223,7 +223,7 @@ function generateGetFunction(name: string, sub: SubscribableIR, entities: Record
|
|
|
223
223
|
// Handle special @user_id root key
|
|
224
224
|
const rootKey = sub.root.key;
|
|
225
225
|
const isUserIdRoot = rootKey === '@user_id';
|
|
226
|
-
const
|
|
226
|
+
const isList = !rootKey; // No key means it's a list subscribable
|
|
227
227
|
|
|
228
228
|
// Build root select expression excluding hidden fields
|
|
229
229
|
const rootSelectExpr = buildVisibleRowJson('root', sub.root.entity, entities);
|
|
@@ -239,7 +239,67 @@ function generateGetFunction(name: string, sub: SubscribableIR, entities: Record
|
|
|
239
239
|
scopeTables: sub.scopeTables
|
|
240
240
|
}).replace(/'/g, "''"); // Escape single quotes for SQL
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
// Build WHERE clause based on root filter and key
|
|
243
|
+
const whereConditions: string[] = [];
|
|
244
|
+
if (rootKey) {
|
|
245
|
+
const rootWhereValue = isUserIdRoot ? 'p_user_id' : `v_${rootKey}`;
|
|
246
|
+
whereConditions.push(`root.id = ${rootWhereValue}`);
|
|
247
|
+
}
|
|
248
|
+
if (sub.root.filter) {
|
|
249
|
+
for (const [field, value] of Object.entries(sub.root.filter)) {
|
|
250
|
+
if (typeof value === 'boolean') {
|
|
251
|
+
whereConditions.push(`root.${field} = ${value}`);
|
|
252
|
+
} else if (typeof value === 'string') {
|
|
253
|
+
whereConditions.push(`root.${field} = '${value}'`);
|
|
254
|
+
} else {
|
|
255
|
+
whereConditions.push(`root.${field} = ${value}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const whereClause = whereConditions.length > 0
|
|
260
|
+
? `WHERE ${whereConditions.join(' AND ')}`
|
|
261
|
+
: '';
|
|
262
|
+
|
|
263
|
+
if (isList) {
|
|
264
|
+
// List subscribable - return array of records with nested includes
|
|
265
|
+
return `CREATE OR REPLACE FUNCTION dzql_v2.get_${name}(
|
|
266
|
+
p_params JSONB,
|
|
267
|
+
p_user_id INT
|
|
268
|
+
) RETURNS JSONB
|
|
269
|
+
LANGUAGE plpgsql
|
|
270
|
+
SECURITY DEFINER
|
|
271
|
+
SET search_path = dzql_v2, public
|
|
272
|
+
AS $$
|
|
273
|
+
DECLARE
|
|
274
|
+
${paramDecls}
|
|
275
|
+
v_data JSONB;
|
|
276
|
+
BEGIN
|
|
277
|
+
-- Extract parameters
|
|
278
|
+
${paramExtracts}
|
|
279
|
+
|
|
280
|
+
-- Check access control
|
|
281
|
+
IF NOT dzql_v2.${name}_can_subscribe(p_user_id, p_params) THEN
|
|
282
|
+
RAISE EXCEPTION 'permission_denied';
|
|
283
|
+
END IF;
|
|
284
|
+
|
|
285
|
+
-- Build list of documents with nested relations
|
|
286
|
+
SELECT COALESCE(jsonb_agg(
|
|
287
|
+
to_jsonb(root.*) || jsonb_build_object(${relationSelects ? relationSelects.slice(1) : ''})
|
|
288
|
+
), '[]'::jsonb)
|
|
289
|
+
INTO v_data
|
|
290
|
+
FROM ${sub.root.entity} root
|
|
291
|
+
${whereClause};
|
|
292
|
+
|
|
293
|
+
-- Return data with embedded schema for atomic updates
|
|
294
|
+
RETURN jsonb_build_object(
|
|
295
|
+
'${sub.root.entity}', v_data,
|
|
296
|
+
'schema', '${schemaJson}'::jsonb
|
|
297
|
+
);
|
|
298
|
+
END;
|
|
299
|
+
$$;`;
|
|
300
|
+
} else {
|
|
301
|
+
// Single record subscribable
|
|
302
|
+
return `CREATE OR REPLACE FUNCTION dzql_v2.get_${name}(
|
|
243
303
|
p_params JSONB,
|
|
244
304
|
p_user_id INT
|
|
245
305
|
) RETURNS JSONB
|
|
@@ -265,7 +325,7 @@ ${paramExtracts}
|
|
|
265
325
|
)
|
|
266
326
|
INTO v_data
|
|
267
327
|
FROM ${sub.root.entity} root
|
|
268
|
-
|
|
328
|
+
${whereClause};
|
|
269
329
|
|
|
270
330
|
-- Return data with embedded schema for atomic updates
|
|
271
331
|
RETURN jsonb_build_object(
|
|
@@ -274,6 +334,7 @@ ${paramExtracts}
|
|
|
274
334
|
);
|
|
275
335
|
END;
|
|
276
336
|
$$;`;
|
|
337
|
+
}
|
|
277
338
|
}
|
|
278
339
|
|
|
279
340
|
function singularize(name: string): string {
|
|
@@ -81,7 +81,7 @@ export function generateSubscribableStore(manifest: Manifest, subName: string):
|
|
|
81
81
|
|
|
82
82
|
const patchCases = generatePatchCases();
|
|
83
83
|
|
|
84
|
-
return `// Generated by
|
|
84
|
+
return `// Generated by DZQL Compiler v${manifest.version}
|
|
85
85
|
// Do not edit this file directly.
|
|
86
86
|
|
|
87
87
|
import { defineStore } from 'pinia';
|
package/src/client/ws.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Core WebSocket Manager for
|
|
1
|
+
// Core WebSocket Manager for DZQL Client
|
|
2
2
|
// Handles connection, auth, reconnects, and message dispatching.
|
|
3
3
|
// This is a pure transport layer - it does not manage or cache data.
|
|
4
4
|
|
|
@@ -10,17 +10,17 @@ export interface WebSocketOptions {
|
|
|
10
10
|
|
|
11
11
|
// Get default token name from environment (build-time injection)
|
|
12
12
|
function getDefaultTokenName(): string {
|
|
13
|
-
// Vite: import.meta.env.
|
|
13
|
+
// Vite: import.meta.env.VITE_DZQL_TOKEN_NAME
|
|
14
14
|
// @ts-ignore
|
|
15
|
-
if (typeof import.meta !== 'undefined' && import.meta.env?.
|
|
15
|
+
if (typeof import.meta !== 'undefined' && import.meta.env?.VITE_DZQL_TOKEN_NAME) {
|
|
16
16
|
// @ts-ignore
|
|
17
|
-
return import.meta.env.
|
|
17
|
+
return import.meta.env.VITE_DZQL_TOKEN_NAME;
|
|
18
18
|
}
|
|
19
|
-
// Node/bundlers: process.env.
|
|
20
|
-
if (typeof process !== 'undefined' && process.env?.
|
|
21
|
-
return process.env.
|
|
19
|
+
// Node/bundlers: process.env.DZQL_TOKEN_NAME
|
|
20
|
+
if (typeof process !== 'undefined' && process.env?.DZQL_TOKEN_NAME) {
|
|
21
|
+
return process.env.DZQL_TOKEN_NAME;
|
|
22
22
|
}
|
|
23
|
-
return '
|
|
23
|
+
return 'dzql_token';
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export class WebSocketManager {
|
|
@@ -32,7 +32,7 @@ export class WebSocketManager {
|
|
|
32
32
|
protected readyCallbacks = new Set<(user: any) => void>();
|
|
33
33
|
protected reconnectAttempts = 0;
|
|
34
34
|
protected maxReconnectAttempts = 5;
|
|
35
|
-
protected tokenName = '
|
|
35
|
+
protected tokenName = 'dzql_token';
|
|
36
36
|
protected isShuttingDown = false;
|
|
37
37
|
|
|
38
38
|
// Connection state
|
|
@@ -123,7 +123,7 @@ export class WebSocketManager {
|
|
|
123
123
|
|
|
124
124
|
this.ws.onopen = () => {
|
|
125
125
|
clearTimeout(connectionTimeout);
|
|
126
|
-
console.log('[
|
|
126
|
+
console.log('[DZQL] Connected to ' + wsUrl);
|
|
127
127
|
this.reconnectAttempts = 0;
|
|
128
128
|
resolve();
|
|
129
129
|
};
|
|
@@ -133,12 +133,12 @@ export class WebSocketManager {
|
|
|
133
133
|
const message = JSON.parse(event.data);
|
|
134
134
|
this.handleMessage(message);
|
|
135
135
|
} catch (error) {
|
|
136
|
-
console.error("[
|
|
136
|
+
console.error("[DZQL] Failed to parse message:", error);
|
|
137
137
|
}
|
|
138
138
|
};
|
|
139
139
|
|
|
140
140
|
this.ws.onclose = () => {
|
|
141
|
-
console.log("[
|
|
141
|
+
console.log("[DZQL] Disconnected");
|
|
142
142
|
if (!this.isShuttingDown) {
|
|
143
143
|
this.attemptReconnect();
|
|
144
144
|
}
|
|
@@ -146,7 +146,7 @@ export class WebSocketManager {
|
|
|
146
146
|
|
|
147
147
|
this.ws.onerror = (error) => {
|
|
148
148
|
clearTimeout(connectionTimeout);
|
|
149
|
-
console.error("[
|
|
149
|
+
console.error("[DZQL] Connection error:", error);
|
|
150
150
|
reject(error);
|
|
151
151
|
};
|
|
152
152
|
});
|
|
@@ -157,7 +157,7 @@ export class WebSocketManager {
|
|
|
157
157
|
this.reconnectAttempts++;
|
|
158
158
|
const delay = 1000 * this.reconnectAttempts;
|
|
159
159
|
setTimeout(() => {
|
|
160
|
-
console.log('[
|
|
160
|
+
console.log('[DZQL] Reconnecting (' + this.reconnectAttempts + ')...');
|
|
161
161
|
this.connect();
|
|
162
162
|
}, delay);
|
|
163
163
|
}
|
package/src/runtime/index.ts
CHANGED
|
@@ -90,7 +90,7 @@ const server = serve({
|
|
|
90
90
|
if (server.upgrade(req, { data: { token } })) {
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
|
-
return new Response("
|
|
93
|
+
return new Response("DZQL Runtime Active", { status: 200 });
|
|
94
94
|
},
|
|
95
95
|
websocket: wsServer.handlers
|
|
96
96
|
});
|
package/src/runtime/namespace.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* DZQL Namespace for invokej integration
|
|
3
3
|
*
|
|
4
|
-
* Provides CLI-style access to
|
|
4
|
+
* Provides CLI-style access to DZQL operations via the compiled manifest.
|
|
5
5
|
* Each method outputs JSON to console and closes the connection before returning.
|
|
6
6
|
*
|
|
7
7
|
* Setup - add to your tasks.js:
|
|
8
8
|
* ```js
|
|
9
|
-
* import {
|
|
9
|
+
* import { DzqlNamespace } from 'dzql/namespace';
|
|
10
10
|
*
|
|
11
11
|
* export class Tasks {
|
|
12
12
|
* constructor() {
|
|
13
|
-
* this.
|
|
13
|
+
* this.dzql = new DzqlNamespace();
|
|
14
14
|
* }
|
|
15
15
|
* }
|
|
16
16
|
* ```
|
|
@@ -18,32 +18,32 @@
|
|
|
18
18
|
* Available Commands:
|
|
19
19
|
*
|
|
20
20
|
* Discovery:
|
|
21
|
-
* invj
|
|
22
|
-
* invj
|
|
23
|
-
* invj
|
|
21
|
+
* invj dzql:entities # List all entities
|
|
22
|
+
* invj dzql:subscribables # List all subscribables
|
|
23
|
+
* invj dzql:functions # List all manifest functions
|
|
24
24
|
*
|
|
25
25
|
* Entity CRUD:
|
|
26
|
-
* invj
|
|
27
|
-
* invj
|
|
28
|
-
* invj
|
|
29
|
-
* invj
|
|
30
|
-
* invj
|
|
31
|
-
* invj
|
|
26
|
+
* invj dzql:search venues '{"query": "test"}' # Search with filters
|
|
27
|
+
* invj dzql:get venues '{"id": 1}' # Get by primary key
|
|
28
|
+
* invj dzql:save venues '{"name": "New", "org_id": 1}' # Create (no id)
|
|
29
|
+
* invj dzql:save venues '{"id": 1, "name": "Updated"}' # Update (with id)
|
|
30
|
+
* invj dzql:delete venues '{"id": 1}' # Delete by primary key
|
|
31
|
+
* invj dzql:lookup venues '{"query": "test"}' # Lookup for dropdowns
|
|
32
32
|
*
|
|
33
33
|
* Subscribables:
|
|
34
|
-
* invj
|
|
34
|
+
* invj dzql:subscribe venue_detail '{"venue_id": 1}' # Get snapshot
|
|
35
35
|
*
|
|
36
36
|
* Ad-hoc Function Calls:
|
|
37
|
-
* invj
|
|
38
|
-
* invj
|
|
39
|
-
* invj
|
|
40
|
-
* invj
|
|
37
|
+
* invj dzql:call login_user '{"email": "x", "password": "y"}'
|
|
38
|
+
* invj dzql:call register_user '{"email": "x", "password": "y"}'
|
|
39
|
+
* invj dzql:call get_venue_detail '{"venue_id": 1}'
|
|
40
|
+
* invj dzql:call save_venues '{"name": "Test", "org_id": 1}'
|
|
41
41
|
*
|
|
42
42
|
* Environment:
|
|
43
43
|
* DATABASE_URL - PostgreSQL connection string (default: postgres://localhost:5432/dzql)
|
|
44
44
|
*
|
|
45
45
|
* Requirements:
|
|
46
|
-
* - Run '
|
|
46
|
+
* - Run 'dzql compile' first to generate dist/runtime/manifest.json
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import postgres from "postgres";
|
|
@@ -74,7 +74,7 @@ function loadManifestFromDisk(): Manifest {
|
|
|
74
74
|
// Fall back to default paths
|
|
75
75
|
const paths = [
|
|
76
76
|
join(process.cwd(), "dist/runtime/manifest.json"),
|
|
77
|
-
join(process.cwd(), "
|
|
77
|
+
join(process.cwd(), "generated/runtime/manifest.json"),
|
|
78
78
|
];
|
|
79
79
|
|
|
80
80
|
for (const path of paths) {
|
|
@@ -85,7 +85,7 @@ function loadManifestFromDisk(): Manifest {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
throw new Error(
|
|
88
|
-
"Manifest not found. Set MANIFEST_PATH env var or run '
|
|
88
|
+
"Manifest not found. Set MANIFEST_PATH env var or run 'dzql compile' to generate dist/runtime/manifest.json"
|
|
89
89
|
);
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -122,9 +122,9 @@ function discoverSubscribables(manifest: Manifest): Record<string, { params: Rec
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
*
|
|
125
|
+
* DZQL operations namespace for invokej
|
|
126
126
|
*/
|
|
127
|
-
export class
|
|
127
|
+
export class DzqlNamespace {
|
|
128
128
|
private userId: number;
|
|
129
129
|
private sql: postgres.Sql | null = null;
|
|
130
130
|
private manifest: Manifest | null = null;
|
|
@@ -225,13 +225,13 @@ export class TzqlNamespace {
|
|
|
225
225
|
|
|
226
226
|
/**
|
|
227
227
|
* Search an entity
|
|
228
|
-
* @example invj
|
|
228
|
+
* @example invj dzql:search venues '{"query": "test"}'
|
|
229
229
|
*/
|
|
230
230
|
async search(_context: any, entity?: string, argsJson: string = "{}"): Promise<void> {
|
|
231
231
|
if (!entity) {
|
|
232
232
|
console.error("Error: entity name required");
|
|
233
|
-
console.error("Usage: invj
|
|
234
|
-
console.error('Example: invj
|
|
233
|
+
console.error("Usage: invj dzql:search <entity> '<json_args>'");
|
|
234
|
+
console.error('Example: invj dzql:search venues \'{"query": "test"}\'');
|
|
235
235
|
await this.cleanup();
|
|
236
236
|
process.exit(1);
|
|
237
237
|
}
|
|
@@ -258,13 +258,13 @@ export class TzqlNamespace {
|
|
|
258
258
|
|
|
259
259
|
/**
|
|
260
260
|
* Get entity by ID
|
|
261
|
-
* @example invj
|
|
261
|
+
* @example invj dzql:get venues '{"id": 1}'
|
|
262
262
|
*/
|
|
263
263
|
async get(_context: any, entity?: string, argsJson: string = "{}"): Promise<void> {
|
|
264
264
|
if (!entity) {
|
|
265
265
|
console.error("Error: entity name required");
|
|
266
|
-
console.error("Usage: invj
|
|
267
|
-
console.error('Example: invj
|
|
266
|
+
console.error("Usage: invj dzql:get <entity> '<json_args>'");
|
|
267
|
+
console.error('Example: invj dzql:get venues \'{"id": 1}\'');
|
|
268
268
|
await this.cleanup();
|
|
269
269
|
process.exit(1);
|
|
270
270
|
}
|
|
@@ -291,13 +291,13 @@ export class TzqlNamespace {
|
|
|
291
291
|
|
|
292
292
|
/**
|
|
293
293
|
* Save (create or update) entity
|
|
294
|
-
* @example invj
|
|
294
|
+
* @example invj dzql:save venues '{"name": "New Venue", "org_id": 1}'
|
|
295
295
|
*/
|
|
296
296
|
async save(_context: any, entity?: string, argsJson: string = "{}"): Promise<void> {
|
|
297
297
|
if (!entity) {
|
|
298
298
|
console.error("Error: entity name required");
|
|
299
|
-
console.error("Usage: invj
|
|
300
|
-
console.error('Example: invj
|
|
299
|
+
console.error("Usage: invj dzql:save <entity> '<json_args>'");
|
|
300
|
+
console.error('Example: invj dzql:save venues \'{"name": "Test Venue", "org_id": 1}\'');
|
|
301
301
|
await this.cleanup();
|
|
302
302
|
process.exit(1);
|
|
303
303
|
}
|
|
@@ -324,13 +324,13 @@ export class TzqlNamespace {
|
|
|
324
324
|
|
|
325
325
|
/**
|
|
326
326
|
* Delete entity by ID
|
|
327
|
-
* @example invj
|
|
327
|
+
* @example invj dzql:delete venues '{"id": 1}'
|
|
328
328
|
*/
|
|
329
329
|
async delete(_context: any, entity?: string, argsJson: string = "{}"): Promise<void> {
|
|
330
330
|
if (!entity) {
|
|
331
331
|
console.error("Error: entity name required");
|
|
332
|
-
console.error("Usage: invj
|
|
333
|
-
console.error('Example: invj
|
|
332
|
+
console.error("Usage: invj dzql:delete <entity> '<json_args>'");
|
|
333
|
+
console.error('Example: invj dzql:delete venues \'{"id": 1}\'');
|
|
334
334
|
await this.cleanup();
|
|
335
335
|
process.exit(1);
|
|
336
336
|
}
|
|
@@ -357,13 +357,13 @@ export class TzqlNamespace {
|
|
|
357
357
|
|
|
358
358
|
/**
|
|
359
359
|
* Lookup entity (for dropdowns/autocomplete)
|
|
360
|
-
* @example invj
|
|
360
|
+
* @example invj dzql:lookup organisations '{"query": "acme"}'
|
|
361
361
|
*/
|
|
362
362
|
async lookup(_context: any, entity?: string, argsJson: string = "{}"): Promise<void> {
|
|
363
363
|
if (!entity) {
|
|
364
364
|
console.error("Error: entity name required");
|
|
365
|
-
console.error("Usage: invj
|
|
366
|
-
console.error('Example: invj
|
|
365
|
+
console.error("Usage: invj dzql:lookup <entity> '<json_args>'");
|
|
366
|
+
console.error('Example: invj dzql:lookup organisations \'{"query": "acme"}\'');
|
|
367
367
|
await this.cleanup();
|
|
368
368
|
process.exit(1);
|
|
369
369
|
}
|
|
@@ -390,13 +390,13 @@ export class TzqlNamespace {
|
|
|
390
390
|
|
|
391
391
|
/**
|
|
392
392
|
* Get subscribable snapshot
|
|
393
|
-
* @example invj
|
|
393
|
+
* @example invj dzql:subscribe venue_detail '{"venue_id": 1}'
|
|
394
394
|
*/
|
|
395
395
|
async subscribe(_context: any, name?: string, argsJson: string = "{}"): Promise<void> {
|
|
396
396
|
if (!name) {
|
|
397
397
|
console.error("Error: subscribable name required");
|
|
398
|
-
console.error("Usage: invj
|
|
399
|
-
console.error('Example: invj
|
|
398
|
+
console.error("Usage: invj dzql:subscribe <name> '<json_args>'");
|
|
399
|
+
console.error('Example: invj dzql:subscribe venue_detail \'{"venue_id": 1}\'');
|
|
400
400
|
await this.cleanup();
|
|
401
401
|
process.exit(1);
|
|
402
402
|
}
|
|
@@ -423,15 +423,15 @@ export class TzqlNamespace {
|
|
|
423
423
|
|
|
424
424
|
/**
|
|
425
425
|
* Call any function in the manifest by name
|
|
426
|
-
* @example invj
|
|
427
|
-
* @example invj
|
|
426
|
+
* @example invj dzql:call login_user '{"email": "test@example.com", "password": "secret"}'
|
|
427
|
+
* @example invj dzql:call get_venue_detail '{"venue_id": 1}'
|
|
428
428
|
*/
|
|
429
429
|
async call(_context: any, funcName?: string, argsJson: string = "{}"): Promise<void> {
|
|
430
430
|
if (!funcName) {
|
|
431
431
|
console.error("Error: function name required");
|
|
432
|
-
console.error("Usage: invj
|
|
433
|
-
console.error('Example: invj
|
|
434
|
-
console.error('Example: invj
|
|
432
|
+
console.error("Usage: invj dzql:call <function_name> '<json_args>'");
|
|
433
|
+
console.error('Example: invj dzql:call login_user \'{"email": "test@example.com", "password": "secret"}\'');
|
|
434
|
+
console.error('Example: invj dzql:call get_venue_detail \'{"venue_id": 1}\'');
|
|
435
435
|
await this.cleanup();
|
|
436
436
|
process.exit(1);
|
|
437
437
|
}
|
|
@@ -458,7 +458,7 @@ export class TzqlNamespace {
|
|
|
458
458
|
|
|
459
459
|
/**
|
|
460
460
|
* List all available functions in the manifest
|
|
461
|
-
* @example invj
|
|
461
|
+
* @example invj dzql:functions
|
|
462
462
|
*/
|
|
463
463
|
async functions(_context?: any): Promise<void> {
|
|
464
464
|
try {
|
package/src/runtime/ws.ts
CHANGED
|
@@ -148,6 +148,8 @@ export class WebSocketServer {
|
|
|
148
148
|
// Auto-generate token for auth methods
|
|
149
149
|
if (req.method === 'login_user' || req.method === 'register_user') {
|
|
150
150
|
const token = await signToken({ user_id: result.user_id, role: 'user' });
|
|
151
|
+
// Update connection's userId for subsequent calls
|
|
152
|
+
ws.data.userId = result.user_id;
|
|
151
153
|
// Return profile + token
|
|
152
154
|
ws.send(JSON.stringify({ id: req.id, result: { ...result, token } }));
|
|
153
155
|
} else {
|
package/tests/client.test.ts
CHANGED
|
@@ -18,16 +18,16 @@ describe("Client SDK Generation", () => {
|
|
|
18
18
|
const tsCode = generateClientSDK(mockManifest);
|
|
19
19
|
|
|
20
20
|
// Check imports
|
|
21
|
-
expect(tsCode).toContain("import { WebSocketManager } from '
|
|
21
|
+
expect(tsCode).toContain("import { WebSocketManager } from 'dzql/client'");
|
|
22
22
|
|
|
23
23
|
// Check interface definition
|
|
24
|
-
expect(tsCode).toContain("export interface
|
|
24
|
+
expect(tsCode).toContain("export interface DzqlAPI {");
|
|
25
25
|
expect(tsCode).toContain("save_posts: (params: SavePostsParams) => Promise<Posts>");
|
|
26
26
|
expect(tsCode).toContain("get_posts: (params: PostsPK) => Promise<Posts | null>");
|
|
27
27
|
|
|
28
28
|
// Check class definition
|
|
29
29
|
expect(tsCode).toContain("export class GeneratedWebSocketManager extends WebSocketManager");
|
|
30
|
-
expect(tsCode).toContain("api:
|
|
30
|
+
expect(tsCode).toContain("api: DzqlAPI");
|
|
31
31
|
|
|
32
32
|
// Check API implementation
|
|
33
33
|
expect(tsCode).toContain("this.call('save_posts', params)");
|
package/tests/namespace.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for
|
|
2
|
+
* Tests for DzqlNamespace - invokej integration
|
|
3
3
|
*
|
|
4
4
|
* These tests verify the namespace works correctly with the manifest
|
|
5
5
|
* and can execute CRUD operations against a real database.
|
|
@@ -16,7 +16,7 @@ import { join } from "path";
|
|
|
16
16
|
|
|
17
17
|
const blogDomain = { entities, subscribables: {} };
|
|
18
18
|
|
|
19
|
-
describe("
|
|
19
|
+
describe("DzqlNamespace", () => {
|
|
20
20
|
let db: V2TestDatabase;
|
|
21
21
|
let sql: any;
|
|
22
22
|
let testManifestPath: string;
|
|
@@ -140,8 +140,8 @@ describe("TzqlNamespace", () => {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
test("entities() lists available entities", async () => {
|
|
143
|
-
const {
|
|
144
|
-
const ns = new
|
|
143
|
+
const { DzqlNamespace } = await import("../src/runtime/namespace.js");
|
|
144
|
+
const ns = new DzqlNamespace();
|
|
145
145
|
|
|
146
146
|
const restore = setupMocks();
|
|
147
147
|
|
|
@@ -162,8 +162,8 @@ describe("TzqlNamespace", () => {
|
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
test("functions() lists available functions", async () => {
|
|
165
|
-
const {
|
|
166
|
-
const ns = new
|
|
165
|
+
const { DzqlNamespace } = await import("../src/runtime/namespace.js");
|
|
166
|
+
const ns = new DzqlNamespace();
|
|
167
167
|
|
|
168
168
|
const restore = setupMocks();
|
|
169
169
|
|
|
@@ -188,8 +188,8 @@ describe("TzqlNamespace", () => {
|
|
|
188
188
|
});
|
|
189
189
|
|
|
190
190
|
test("save() creates a new entity", async () => {
|
|
191
|
-
const {
|
|
192
|
-
const ns = new
|
|
191
|
+
const { DzqlNamespace } = await import("../src/runtime/namespace.js");
|
|
192
|
+
const ns = new DzqlNamespace(1); // userId = 1
|
|
193
193
|
|
|
194
194
|
const restore = setupMocks();
|
|
195
195
|
|
|
@@ -222,8 +222,8 @@ describe("TzqlNamespace", () => {
|
|
|
222
222
|
`;
|
|
223
223
|
const postId = created[0].id;
|
|
224
224
|
|
|
225
|
-
const {
|
|
226
|
-
const ns = new
|
|
225
|
+
const { DzqlNamespace } = await import("../src/runtime/namespace.js");
|
|
226
|
+
const ns = new DzqlNamespace(1);
|
|
227
227
|
|
|
228
228
|
const restore = setupMocks();
|
|
229
229
|
|
|
@@ -250,8 +250,8 @@ describe("TzqlNamespace", () => {
|
|
|
250
250
|
VALUES ('Search Post 1', 'Content', 1), ('Search Post 2', 'More', 1)
|
|
251
251
|
`;
|
|
252
252
|
|
|
253
|
-
const {
|
|
254
|
-
const ns = new
|
|
253
|
+
const { DzqlNamespace } = await import("../src/runtime/namespace.js");
|
|
254
|
+
const ns = new DzqlNamespace(1);
|
|
255
255
|
|
|
256
256
|
const restore = setupMocks();
|
|
257
257
|
|
|
@@ -279,8 +279,8 @@ describe("TzqlNamespace", () => {
|
|
|
279
279
|
`;
|
|
280
280
|
const postId = created[0].id;
|
|
281
281
|
|
|
282
|
-
const {
|
|
283
|
-
const ns = new
|
|
282
|
+
const { DzqlNamespace } = await import("../src/runtime/namespace.js");
|
|
283
|
+
const ns = new DzqlNamespace(1); // userId must match author_id
|
|
284
284
|
|
|
285
285
|
const restore = setupMocks();
|
|
286
286
|
|
|
@@ -308,8 +308,8 @@ describe("TzqlNamespace", () => {
|
|
|
308
308
|
});
|
|
309
309
|
|
|
310
310
|
test("call() executes arbitrary manifest function", async () => {
|
|
311
|
-
const {
|
|
312
|
-
const ns = new
|
|
311
|
+
const { DzqlNamespace } = await import("../src/runtime/namespace.js");
|
|
312
|
+
const ns = new DzqlNamespace(1);
|
|
313
313
|
|
|
314
314
|
const restore = setupMocks();
|
|
315
315
|
|
|
@@ -338,8 +338,8 @@ describe("TzqlNamespace", () => {
|
|
|
338
338
|
});
|
|
339
339
|
|
|
340
340
|
test("call() with unknown function returns error", async () => {
|
|
341
|
-
const {
|
|
342
|
-
const ns = new
|
|
341
|
+
const { DzqlNamespace } = await import("../src/runtime/namespace.js");
|
|
342
|
+
const ns = new DzqlNamespace(1);
|
|
343
343
|
|
|
344
344
|
const restore = setupMocks();
|
|
345
345
|
|
|
@@ -360,8 +360,8 @@ describe("TzqlNamespace", () => {
|
|
|
360
360
|
});
|
|
361
361
|
|
|
362
362
|
test("search() without entity shows usage", async () => {
|
|
363
|
-
const {
|
|
364
|
-
const ns = new
|
|
363
|
+
const { DzqlNamespace } = await import("../src/runtime/namespace.js");
|
|
364
|
+
const ns = new DzqlNamespace(1);
|
|
365
365
|
|
|
366
366
|
const restore = setupMocks();
|
|
367
367
|
|
|
@@ -377,8 +377,8 @@ describe("TzqlNamespace", () => {
|
|
|
377
377
|
});
|
|
378
378
|
|
|
379
379
|
test("save() with invalid JSON shows error", async () => {
|
|
380
|
-
const {
|
|
381
|
-
const ns = new
|
|
380
|
+
const { DzqlNamespace } = await import("../src/runtime/namespace.js");
|
|
381
|
+
const ns = new DzqlNamespace(1);
|
|
382
382
|
|
|
383
383
|
const restore = setupMocks();
|
|
384
384
|
|
/package/dist/db/migrations/{20251229T212912022Z_schema.sql → 20260101T235039268Z_schema.sql}
RENAMED
|
File without changes
|