dzql 0.1.0-alpha.1 → 0.1.0-alpha.2
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/GETTING_STARTED.md +559 -0
- package/README.md +500 -0
- package/package.json +2 -1
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
# Getting Started with DZQL
|
|
2
|
+
|
|
3
|
+
DZQL is a PostgreSQL-powered framework that provides automatic CRUD operations via WebSocket RPC with zero boilerplate. This guide walks you through setting up your first DZQL project.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- **Bun** 1.0+ (Node.js not required!)
|
|
8
|
+
- **Docker** and **Docker Compose** (for PostgreSQL)
|
|
9
|
+
- A code editor
|
|
10
|
+
|
|
11
|
+
## Quick Start (5 minutes)
|
|
12
|
+
|
|
13
|
+
### 1. Create a New Project
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
mkdir my-dzql-app
|
|
17
|
+
cd my-dzql-app
|
|
18
|
+
bun init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. Install DZQL
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun add dzql
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 3. Set Up PostgreSQL with Docker
|
|
28
|
+
|
|
29
|
+
Create a `docker-compose.yml`:
|
|
30
|
+
|
|
31
|
+
**For standalone projects (using npm/bun):**
|
|
32
|
+
```yaml
|
|
33
|
+
services:
|
|
34
|
+
postgres:
|
|
35
|
+
image: postgres:latest
|
|
36
|
+
environment:
|
|
37
|
+
POSTGRES_USER: dzql
|
|
38
|
+
POSTGRES_PASSWORD: dzql
|
|
39
|
+
POSTGRES_DB: dzql
|
|
40
|
+
volumes:
|
|
41
|
+
# DZQL Core System migrations
|
|
42
|
+
- node_modules/dzql/src/database/migrations/001_schema.sql:/docker-entrypoint-initdb.d/001_schema.sql:ro
|
|
43
|
+
- node_modules/dzql/src/database/migrations/002_functions.sql:/docker-entrypoint-initdb.d/002_functions.sql:ro
|
|
44
|
+
- node_modules/dzql/src/database/migrations/003_operations.sql:/docker-entrypoint-initdb.d/003_operations.sql:ro
|
|
45
|
+
- node_modules/dzql/src/database/migrations/004_search.sql:/docker-entrypoint-initdb.d/004_search.sql:ro
|
|
46
|
+
- node_modules/dzql/src/database/migrations/005_entities.sql:/docker-entrypoint-initdb.d/005_entities.sql:ro
|
|
47
|
+
- node_modules/dzql/src/database/migrations/006_auth.sql:/docker-entrypoint-initdb.d/006_auth.sql:ro
|
|
48
|
+
- node_modules/dzql/src/database/migrations/007_events.sql:/docker-entrypoint-initdb.d/007_events.sql:ro
|
|
49
|
+
- node_modules/dzql/src/database/migrations/008_hello.sql:/docker-entrypoint-initdb.d/008_hello.sql:ro
|
|
50
|
+
- node_modules/dzql/src/database/migrations/008a_meta.sql:/docker-entrypoint-initdb.d/008a_meta.sql:ro
|
|
51
|
+
# Your domain-specific migrations
|
|
52
|
+
- ./init_db:/docker-entrypoint-initdb.d/init_db:ro
|
|
53
|
+
ports:
|
|
54
|
+
- "5432:5432"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**For monorepo projects (like the venues example):**
|
|
58
|
+
```yaml
|
|
59
|
+
services:
|
|
60
|
+
postgres:
|
|
61
|
+
image: postgres:latest
|
|
62
|
+
environment:
|
|
63
|
+
POSTGRES_USER: dzql
|
|
64
|
+
POSTGRES_PASSWORD: dzql
|
|
65
|
+
POSTGRES_DB: dzql
|
|
66
|
+
volumes:
|
|
67
|
+
# DZQL Core System migrations (relative path from monorepo root)
|
|
68
|
+
- ../../dzql/src/database/migrations/001_schema.sql:/docker-entrypoint-initdb.d/001_schema.sql:ro
|
|
69
|
+
- ../../dzql/src/database/migrations/002_functions.sql:/docker-entrypoint-initdb.d/002_functions.sql:ro
|
|
70
|
+
- ../../dzql/src/database/migrations/003_operations.sql:/docker-entrypoint-initdb.d/003_operations.sql:ro
|
|
71
|
+
- ../../dzql/src/database/migrations/004_search.sql:/docker-entrypoint-initdb.d/004_search.sql:ro
|
|
72
|
+
- ../../dzql/src/database/migrations/005_entities.sql:/docker-entrypoint-initdb.d/005_entities.sql:ro
|
|
73
|
+
- ../../dzql/src/database/migrations/006_auth.sql:/docker-entrypoint-initdb.d/006_auth.sql:ro
|
|
74
|
+
- ../../dzql/src/database/migrations/007_events.sql:/docker-entrypoint-initdb.d/007_events.sql:ro
|
|
75
|
+
- ../../dzql/src/database/migrations/008_hello.sql:/docker-entrypoint-initdb.d/008_hello.sql:ro
|
|
76
|
+
- ../../dzql/src/database/migrations/008a_meta.sql:/docker-entrypoint-initdb.d/008a_meta.sql:ro
|
|
77
|
+
# Your domain-specific migrations
|
|
78
|
+
- ./init_db:/docker-entrypoint-initdb.d/init_db:ro
|
|
79
|
+
ports:
|
|
80
|
+
- "5432:5432"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Start PostgreSQL:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
docker compose up -d
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 4. Initialize Database
|
|
90
|
+
|
|
91
|
+
The docker-compose.yml automatically runs DZQL core migrations from `node_modules/dzql/src/database/migrations/`.
|
|
92
|
+
|
|
93
|
+
Create your domain-specific migrations in `database/init_db/001_domain.sql`:
|
|
94
|
+
|
|
95
|
+
```sql
|
|
96
|
+
-- Create your tables (after DZQL core is set up)
|
|
97
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
98
|
+
id SERIAL PRIMARY KEY,
|
|
99
|
+
name TEXT NOT NULL,
|
|
100
|
+
email TEXT NOT NULL UNIQUE,
|
|
101
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
-- Register your entities with DZQL
|
|
105
|
+
SELECT dzql.register_entity(
|
|
106
|
+
p_table_name := 'users',
|
|
107
|
+
p_label_field := 'name',
|
|
108
|
+
p_searchable_fields := array['name', 'email'],
|
|
109
|
+
p_fk_includes := '{}'::jsonb
|
|
110
|
+
);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Important:** Your migrations run AFTER the DZQL core migrations, so entity registration functions are available.
|
|
114
|
+
|
|
115
|
+
Start PostgreSQL:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
docker compose up -d
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The Docker Compose file will automatically run all DZQL core migrations first, then your domain migrations.
|
|
122
|
+
|
|
123
|
+
### 5. Create Your Server
|
|
124
|
+
|
|
125
|
+
Create `server/index.js`:
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
import { createServer } from 'dzql';
|
|
129
|
+
|
|
130
|
+
const server = createServer({
|
|
131
|
+
port: process.env.PORT || 3000,
|
|
132
|
+
// Optional: custom API functions
|
|
133
|
+
customApi: {},
|
|
134
|
+
// Optional: static file serving
|
|
135
|
+
staticPath: './public'
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
console.log(`🚀 Server running on port ${server.port}`);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 6. Start Your Server
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
bun server/index.js
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Your DZQL server is now running at `ws://localhost:3000/ws`!
|
|
148
|
+
|
|
149
|
+
## Project Setup: Standalone vs Monorepo
|
|
150
|
+
|
|
151
|
+
### Standalone Project (Recommended for Most Users)
|
|
152
|
+
Use `node_modules/dzql/src/database/migrations/` paths in your docker-compose.yml
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
my-app/
|
|
156
|
+
├── database/
|
|
157
|
+
│ ├── docker-compose.yml # Uses node_modules paths
|
|
158
|
+
│ └── init_db/
|
|
159
|
+
│ └── 001_domain.sql
|
|
160
|
+
├── server/
|
|
161
|
+
├── client/
|
|
162
|
+
└── package.json
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Monorepo Project (Like the Venues Example)
|
|
166
|
+
Use relative paths `../../dzql/src/database/migrations/` in docker-compose.yml
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
monorepo/
|
|
170
|
+
├── packages/
|
|
171
|
+
│ ├── dzql/ # Framework package
|
|
172
|
+
│ │ └── src/database/migrations/ # Core migrations
|
|
173
|
+
│ └── my-app/ # Your app
|
|
174
|
+
│ ├── database/
|
|
175
|
+
│ │ └── docker-compose.yml # References ../../dzql/...
|
|
176
|
+
│ ├── server/
|
|
177
|
+
│ └── package.json
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Using DZQL in Your Client
|
|
181
|
+
|
|
182
|
+
### Browser/Frontend
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
import { useWs, WebSocketManager } from 'dzql/client';
|
|
186
|
+
|
|
187
|
+
// Create a fresh WebSocket connection
|
|
188
|
+
const ws = new WebSocketManager();
|
|
189
|
+
|
|
190
|
+
// Connect to server
|
|
191
|
+
await ws.connect();
|
|
192
|
+
|
|
193
|
+
// Login
|
|
194
|
+
const result = await ws.api.login_user({
|
|
195
|
+
email: 'user@example.com',
|
|
196
|
+
password: 'password123'
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Use DZQL operations - all 5 operations work the same way:
|
|
200
|
+
// GET, SAVE, DELETE, LOOKUP, SEARCH
|
|
201
|
+
|
|
202
|
+
// GET - Retrieve a single record
|
|
203
|
+
const user = await ws.api.get.users({ id: 1 });
|
|
204
|
+
|
|
205
|
+
// SAVE - Create or update (upsert)
|
|
206
|
+
const newUser = await ws.api.save.users({
|
|
207
|
+
name: 'John Doe',
|
|
208
|
+
email: 'john@example.com'
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// LOOKUP - Autocomplete/suggestions
|
|
212
|
+
const suggestions = await ws.api.lookup.users({
|
|
213
|
+
p_filter: 'john'
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// SEARCH - Full search with filters and pagination
|
|
217
|
+
const results = await ws.api.search.users({
|
|
218
|
+
filters: {
|
|
219
|
+
name: { ilike: '%john%' },
|
|
220
|
+
email: 'john@example.com'
|
|
221
|
+
},
|
|
222
|
+
page: 1,
|
|
223
|
+
limit: 10
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// DELETE - Remove a record
|
|
227
|
+
const deleted = await ws.api.delete.users({ id: 1 });
|
|
228
|
+
|
|
229
|
+
// When done
|
|
230
|
+
ws.cleanDisconnect();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Bun/Backend
|
|
234
|
+
|
|
235
|
+
```javascript
|
|
236
|
+
import { db, sql } from 'dzql';
|
|
237
|
+
|
|
238
|
+
// Direct database queries
|
|
239
|
+
const users = await sql`SELECT * FROM users`;
|
|
240
|
+
|
|
241
|
+
// DZQL operations (require userId)
|
|
242
|
+
const user = await db.api.get.users({ id: 1 }, userId);
|
|
243
|
+
const saved = await db.api.save.users({ name: 'John' }, userId);
|
|
244
|
+
const searched = await db.api.search.users({ filters: {} }, userId);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Project Structure
|
|
248
|
+
|
|
249
|
+
A typical DZQL project looks like:
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
my-dzql-app/
|
|
253
|
+
├── server/
|
|
254
|
+
│ ├── index.js # Server entry point
|
|
255
|
+
│ └── api.js # Custom API functions (optional)
|
|
256
|
+
├── database/
|
|
257
|
+
│ └── init_db/
|
|
258
|
+
│ ├── 001_domain.sql # Your schema & entity registration
|
|
259
|
+
│ └── 002_seed.sql # Sample data (optional)
|
|
260
|
+
├── public/ # Static files (optional)
|
|
261
|
+
│ └── index.html
|
|
262
|
+
├── docker-compose.yml
|
|
263
|
+
├── bunfig.toml # Bun config (optional)
|
|
264
|
+
└── package.json
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## The 5 DZQL Operations
|
|
268
|
+
|
|
269
|
+
Every registered entity automatically gets these 5 operations:
|
|
270
|
+
|
|
271
|
+
### 1. GET - Single Record
|
|
272
|
+
```javascript
|
|
273
|
+
const record = await ws.api.get.tableName({ id: 1 });
|
|
274
|
+
// Throws "record not found" error if not exists
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### 2. SAVE - Upsert
|
|
278
|
+
```javascript
|
|
279
|
+
const record = await ws.api.save.tableName({
|
|
280
|
+
id: 1, // Optional - omit for insert
|
|
281
|
+
name: 'Updated'
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### 3. DELETE - Remove Record
|
|
286
|
+
```javascript
|
|
287
|
+
const deleted = await ws.api.delete.tableName({ id: 1 });
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### 4. LOOKUP - Autocomplete
|
|
291
|
+
```javascript
|
|
292
|
+
const options = await ws.api.lookup.tableName({
|
|
293
|
+
p_filter: 'search term'
|
|
294
|
+
});
|
|
295
|
+
// Returns: [{ label: 'Display Name', value: 1 }, ...]
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 5. SEARCH - Advanced Search
|
|
299
|
+
```javascript
|
|
300
|
+
const results = await ws.api.search.tableName({
|
|
301
|
+
filters: {
|
|
302
|
+
name: { ilike: '%john%' },
|
|
303
|
+
age: { gte: 18 },
|
|
304
|
+
city: ['NYC', 'LA'],
|
|
305
|
+
active: true
|
|
306
|
+
},
|
|
307
|
+
sort: { field: 'name', order: 'asc' },
|
|
308
|
+
page: 1,
|
|
309
|
+
limit: 25
|
|
310
|
+
});
|
|
311
|
+
// Returns: { data: [...], total: 100, page: 1, limit: 25 }
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Entity Registration
|
|
315
|
+
|
|
316
|
+
Before DZQL can work with a table, you must register it with the `dzql.register_entity()` function.
|
|
317
|
+
|
|
318
|
+
**Important:** This function is provided by DZQL's core migrations, which run automatically when PostgreSQL starts via docker-compose.
|
|
319
|
+
|
|
320
|
+
```sql
|
|
321
|
+
SELECT dzql.register_entity(
|
|
322
|
+
p_table_name := 'venues', -- Your table name
|
|
323
|
+
p_label_field := 'name', -- Used by LOOKUP
|
|
324
|
+
p_searchable_fields := array['name', 'address'], -- Used by SEARCH
|
|
325
|
+
p_fk_includes := '{"org": "organisations"}'::jsonb, -- Dereference FKs
|
|
326
|
+
p_graph_rules := '{}'::jsonb -- Optional: automation
|
|
327
|
+
);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Parameters:**
|
|
331
|
+
- `p_table_name`: Your PostgreSQL table name
|
|
332
|
+
- `p_label_field`: Which field to use for display (LOOKUP)
|
|
333
|
+
- `p_searchable_fields`: Fields searchable by SEARCH
|
|
334
|
+
- `p_fk_includes`: Foreign keys to auto-dereference (optional)
|
|
335
|
+
- `p_graph_rules`: Graph rules for automation (optional)
|
|
336
|
+
|
|
337
|
+
See the [venues example](https://github.com/blueshed/dzql/blob/main/packages/venues/database/init_db/009_venues_domain.sql) for a complete schema with multiple entity registrations.
|
|
338
|
+
|
|
339
|
+
## Custom API Functions
|
|
340
|
+
|
|
341
|
+
Add custom functions that work alongside DZQL operations:
|
|
342
|
+
|
|
343
|
+
**PostgreSQL Function:**
|
|
344
|
+
```sql
|
|
345
|
+
CREATE OR REPLACE FUNCTION my_function(
|
|
346
|
+
p_user_id INT,
|
|
347
|
+
p_name TEXT
|
|
348
|
+
) RETURNS TABLE (message TEXT) AS $$
|
|
349
|
+
BEGIN
|
|
350
|
+
RETURN QUERY SELECT 'Hello, ' || p_name;
|
|
351
|
+
END;
|
|
352
|
+
$$ LANGUAGE plpgsql;
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Call from Client:**
|
|
356
|
+
```javascript
|
|
357
|
+
const result = await ws.api.my_function({ name: 'World' });
|
|
358
|
+
// Returns: { message: 'Hello, World' }
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Bun Function:**
|
|
362
|
+
```javascript
|
|
363
|
+
// server/api.js
|
|
364
|
+
export async function my_function(userId, params = {}) {
|
|
365
|
+
return {
|
|
366
|
+
message: `Hello, ${params.name}!`,
|
|
367
|
+
user_id: userId
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Then pass it to createServer:
|
|
373
|
+
```javascript
|
|
374
|
+
const customApi = await import('./api.js');
|
|
375
|
+
const server = createServer({
|
|
376
|
+
customApi
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Real-time Events
|
|
381
|
+
|
|
382
|
+
DZQL broadcasts changes in real-time via WebSocket:
|
|
383
|
+
|
|
384
|
+
```javascript
|
|
385
|
+
// Listen for all events
|
|
386
|
+
const unsubscribe = ws.onBroadcast((method, params) => {
|
|
387
|
+
console.log(`Event: ${method}`, params);
|
|
388
|
+
// method: "users:insert", "users:update", "users:delete"
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Stop listening
|
|
392
|
+
unsubscribe();
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Authentication
|
|
396
|
+
|
|
397
|
+
DZQL provides built-in user authentication:
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
// Register
|
|
401
|
+
const result = await ws.api.register_user({
|
|
402
|
+
email: 'user@example.com',
|
|
403
|
+
password: 'secure-password'
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Login
|
|
407
|
+
const result = await ws.api.login_user({
|
|
408
|
+
email: 'user@example.com',
|
|
409
|
+
password: 'secure-password'
|
|
410
|
+
});
|
|
411
|
+
// Returns: { token, profile, user_id }
|
|
412
|
+
|
|
413
|
+
// Logout
|
|
414
|
+
await ws.api.logout();
|
|
415
|
+
|
|
416
|
+
// Save token for auto-login
|
|
417
|
+
localStorage.setItem('dzql_token', result.token);
|
|
418
|
+
|
|
419
|
+
// Auto-connect with token
|
|
420
|
+
const ws = new WebSocketManager();
|
|
421
|
+
await ws.connect(); // Automatically uses token from localStorage
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Error Handling
|
|
425
|
+
|
|
426
|
+
```javascript
|
|
427
|
+
try {
|
|
428
|
+
const user = await ws.api.get.users({ id: 999 });
|
|
429
|
+
} catch (error) {
|
|
430
|
+
console.error(error.message);
|
|
431
|
+
// "record not found" - record doesn't exist
|
|
432
|
+
// "Permission denied: view on users" - access denied
|
|
433
|
+
// "Function not found" - custom function doesn't exist
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Environment Variables
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
# Server
|
|
441
|
+
PORT=3000
|
|
442
|
+
DATABASE_URL=postgresql://dzql:dzql@localhost:5432/dzql
|
|
443
|
+
NODE_ENV=development
|
|
444
|
+
LOG_LEVEL=INFO
|
|
445
|
+
|
|
446
|
+
# JWT
|
|
447
|
+
JWT_SECRET=your-secret-key
|
|
448
|
+
JWT_EXPIRES_IN=7d
|
|
449
|
+
|
|
450
|
+
# WebSocket
|
|
451
|
+
WS_PING_INTERVAL=30000
|
|
452
|
+
WS_PING_TIMEOUT=5000
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Running Tests
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
bun test tests/
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Troubleshooting
|
|
462
|
+
|
|
463
|
+
### Database won't connect
|
|
464
|
+
```bash
|
|
465
|
+
# Check if PostgreSQL is running
|
|
466
|
+
docker ps
|
|
467
|
+
# Check logs
|
|
468
|
+
docker compose logs postgres
|
|
469
|
+
# Restart
|
|
470
|
+
docker compose down -v && docker compose up -d
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### WebSocket connection fails
|
|
474
|
+
- Ensure server is running: `http://localhost:3000`
|
|
475
|
+
- Check firewall for port 3000
|
|
476
|
+
- Check browser console for errors
|
|
477
|
+
|
|
478
|
+
### Entity not found errors
|
|
479
|
+
- Verify table is created in database
|
|
480
|
+
- Verify entity is registered with `dzql.register_entity()`
|
|
481
|
+
- Check `p_table_name` matches exactly
|
|
482
|
+
|
|
483
|
+
### Permission denied errors
|
|
484
|
+
- Implement permission rules in your entity registration
|
|
485
|
+
- Check user authentication status
|
|
486
|
+
- Verify user_id is being passed correctly
|
|
487
|
+
|
|
488
|
+
## Next Steps
|
|
489
|
+
|
|
490
|
+
1. **Read the full documentation**: Check the framework's README
|
|
491
|
+
2. **Explore graph rules**: Add automation with `p_graph_rules`
|
|
492
|
+
3. **Implement permissions**: Use path-based access control
|
|
493
|
+
4. **Add notifications**: Set up `p_notification_path` for real-time updates
|
|
494
|
+
5. **Build your UI**: Connect to any frontend framework (React, Vue, Svelte, etc.)
|
|
495
|
+
|
|
496
|
+
## Example: Complete Todo App
|
|
497
|
+
|
|
498
|
+
**Database Setup:**
|
|
499
|
+
```sql
|
|
500
|
+
CREATE TABLE todos (
|
|
501
|
+
id SERIAL PRIMARY KEY,
|
|
502
|
+
user_id INT NOT NULL REFERENCES users(id),
|
|
503
|
+
title TEXT NOT NULL,
|
|
504
|
+
completed BOOLEAN DEFAULT FALSE,
|
|
505
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
SELECT dzql.register_entity(
|
|
509
|
+
'todos',
|
|
510
|
+
'title',
|
|
511
|
+
array['title'],
|
|
512
|
+
'{"user": "users"}'::jsonb
|
|
513
|
+
);
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Server:**
|
|
517
|
+
```javascript
|
|
518
|
+
import { createServer } from 'dzql';
|
|
519
|
+
|
|
520
|
+
const server = createServer({
|
|
521
|
+
port: 3000
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**Client:**
|
|
526
|
+
```javascript
|
|
527
|
+
const ws = new WebSocketManager();
|
|
528
|
+
await ws.connect();
|
|
529
|
+
await ws.api.login_user({ email: 'user@example.com', password: 'pass' });
|
|
530
|
+
|
|
531
|
+
// Create todo
|
|
532
|
+
const todo = await ws.api.save.todos({
|
|
533
|
+
title: 'Learn DZQL',
|
|
534
|
+
completed: false
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Get all todos
|
|
538
|
+
const results = await ws.api.search.todos({
|
|
539
|
+
filters: { completed: false },
|
|
540
|
+
limit: 100
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// Update todo
|
|
544
|
+
await ws.api.save.todos({
|
|
545
|
+
id: todo.id,
|
|
546
|
+
completed: true
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Delete todo
|
|
550
|
+
await ws.api.delete.todos({ id: todo.id });
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
## Support
|
|
554
|
+
|
|
555
|
+
- **GitHub**: https://github.com/blueshed/dzql
|
|
556
|
+
- **Issues**: https://github.com/blueshed/dzql/issues
|
|
557
|
+
- **Documentation**: See README.md in the package
|
|
558
|
+
|
|
559
|
+
Happy building! 🚀
|
package/README.md
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
# DZQL - Zero-Boilerplate Database Framework
|
|
2
|
+
|
|
3
|
+
PostgreSQL-powered framework with automatic CRUD operations, real-time WebSocket synchronization, and graph-based permissions. **No migrations. No schema files. No API boilerplate.**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install dzql
|
|
7
|
+
# or with Bun (no Node.js required)
|
|
8
|
+
bun add dzql
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Why DZQL?
|
|
12
|
+
|
|
13
|
+
### Before DZQL
|
|
14
|
+
```javascript
|
|
15
|
+
// Traditional approach: Write everything
|
|
16
|
+
app.post('/api/users', authenticate, validate, async (req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const user = await db.query('INSERT INTO users (...) VALUES (...)', [...]);
|
|
19
|
+
res.json(user);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
res.status(500).json({ error: error.message });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
// Repeat for GET, PUT, DELETE, SEARCH, LOOKUP... = 50+ lines of boilerplate
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### With DZQL
|
|
28
|
+
```javascript
|
|
29
|
+
// That's it. All 5 operations work automatically.
|
|
30
|
+
// GET, SAVE, DELETE, LOOKUP, SEARCH
|
|
31
|
+
const user = await ws.api.save.users({ name: 'John' });
|
|
32
|
+
const results = await ws.api.search.users({ filters: {name: 'john'} });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
✅ **Zero Boilerplate** - Register entity, get 5 CRUD operations automatically
|
|
38
|
+
✅ **Real-time WebSocket** - Automatic change notifications to all clients
|
|
39
|
+
✅ **PostgreSQL-native** - Leverage full SQL power when needed
|
|
40
|
+
✅ **Graph Rules** - Cascading operations without joins
|
|
41
|
+
✅ **Permissions & RLS** - Row-level security built-in
|
|
42
|
+
✅ **Full-text Search** - Built-in search with filters & pagination
|
|
43
|
+
✅ **Type-safe** - Uses PostgreSQL as source of truth
|
|
44
|
+
✅ **Framework-agnostic** - Works with any frontend (React, Vue, Svelte, plain JS)
|
|
45
|
+
✅ **Bun Native** - No Node.js required
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
### 1. Install
|
|
50
|
+
```bash
|
|
51
|
+
bun add dzql
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Start PostgreSQL
|
|
55
|
+
```bash
|
|
56
|
+
docker run -d \
|
|
57
|
+
-e POSTGRES_PASSWORD=dzql \
|
|
58
|
+
-e POSTGRES_DB=dzql \
|
|
59
|
+
-p 5432:5432 \
|
|
60
|
+
postgres:latest
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Create Server
|
|
64
|
+
```javascript
|
|
65
|
+
import { createServer } from 'dzql';
|
|
66
|
+
|
|
67
|
+
const server = createServer({ port: 3000 });
|
|
68
|
+
console.log('🚀 Server on ws://localhost:3000/ws');
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 4. Initialize Database
|
|
72
|
+
```bash
|
|
73
|
+
# Apply DZQL core migrations (included in package)
|
|
74
|
+
psql -h localhost -U postgres -d dzql < node_modules/dzql/src/database/migrations/*.sql
|
|
75
|
+
|
|
76
|
+
# Register your entities
|
|
77
|
+
psql -h localhost -U postgres -d dzql << EOF
|
|
78
|
+
CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT, email TEXT);
|
|
79
|
+
|
|
80
|
+
SELECT dzql.register_entity(
|
|
81
|
+
'users',
|
|
82
|
+
'name',
|
|
83
|
+
array['name', 'email']
|
|
84
|
+
);
|
|
85
|
+
EOF
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 5. Use from Client
|
|
89
|
+
```javascript
|
|
90
|
+
import { WebSocketManager } from 'dzql/client';
|
|
91
|
+
|
|
92
|
+
const ws = new WebSocketManager();
|
|
93
|
+
await ws.connect();
|
|
94
|
+
|
|
95
|
+
// All 5 operations work automatically
|
|
96
|
+
const user = await ws.api.save.users({ name: 'Alice', email: 'alice@example.com' });
|
|
97
|
+
const results = await ws.api.search.users({ filters: { name: { ilike: '%alice%' } } });
|
|
98
|
+
const deleted = await ws.api.delete.users({ id: user.id });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## The 5 Operations
|
|
102
|
+
|
|
103
|
+
Every registered entity automatically gets these 5 operations:
|
|
104
|
+
|
|
105
|
+
### GET - Retrieve Single Record
|
|
106
|
+
```javascript
|
|
107
|
+
const user = await ws.api.get.users({ id: 1 });
|
|
108
|
+
// Server: await db.api.get.users({ id: 1 }, userId);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### SAVE - Create or Update
|
|
112
|
+
```javascript
|
|
113
|
+
const user = await ws.api.save.users({
|
|
114
|
+
id: 1, // Optional - omit for insert
|
|
115
|
+
name: 'Alice',
|
|
116
|
+
email: 'alice@example.com'
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### DELETE - Remove Record
|
|
121
|
+
```javascript
|
|
122
|
+
const deleted = await ws.api.delete.users({ id: 1 });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### LOOKUP - Autocomplete/Label Lookup
|
|
126
|
+
```javascript
|
|
127
|
+
const options = await ws.api.lookup.users({ p_filter: 'ali' });
|
|
128
|
+
// Returns: [{ label: 'Alice', value: 1 }]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### SEARCH - Advanced Search with Pagination
|
|
132
|
+
```javascript
|
|
133
|
+
const results = await ws.api.search.users({
|
|
134
|
+
filters: {
|
|
135
|
+
name: { ilike: '%alice%' },
|
|
136
|
+
email: 'alice@example.com',
|
|
137
|
+
created_at: { gte: '2025-01-01' }
|
|
138
|
+
},
|
|
139
|
+
sort: { field: 'name', order: 'asc' },
|
|
140
|
+
page: 1,
|
|
141
|
+
limit: 25
|
|
142
|
+
});
|
|
143
|
+
// Returns: { data: [...], total: 42, page: 1, limit: 25 }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Entity Registration
|
|
147
|
+
|
|
148
|
+
Before DZQL works with a table, register it:
|
|
149
|
+
|
|
150
|
+
```sql
|
|
151
|
+
SELECT dzql.register_entity(
|
|
152
|
+
p_table_name := 'users',
|
|
153
|
+
p_label_field := 'name', -- For LOOKUP display
|
|
154
|
+
p_searchable_fields := array['name', 'email'], -- For SEARCH
|
|
155
|
+
p_fk_includes := '{"department": "departments"}'::jsonb, -- Dereference FKs
|
|
156
|
+
p_graph_rules := '{
|
|
157
|
+
"on_delete": {
|
|
158
|
+
"cascade": {
|
|
159
|
+
"actions": [{
|
|
160
|
+
"type": "delete",
|
|
161
|
+
"entity": "posts",
|
|
162
|
+
"condition": "user_id = @id"
|
|
163
|
+
}]
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}'::jsonb
|
|
167
|
+
);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Core API
|
|
171
|
+
|
|
172
|
+
### Server-Side (Bun/Node)
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
import { createServer, db, sql } from 'dzql';
|
|
176
|
+
|
|
177
|
+
// Direct SQL access
|
|
178
|
+
const users = await sql`SELECT * FROM users WHERE active = true`;
|
|
179
|
+
|
|
180
|
+
// DZQL operations (require userId for permissions)
|
|
181
|
+
const user = await db.api.get.users({ id: 1 }, userId);
|
|
182
|
+
const saved = await db.api.save.users({ name: 'Bob' }, userId);
|
|
183
|
+
const searched = await db.api.search.users(
|
|
184
|
+
{ filters: { name: 'bob' } },
|
|
185
|
+
userId
|
|
186
|
+
);
|
|
187
|
+
const deleted = await db.api.delete.users({ id: 1 }, userId);
|
|
188
|
+
const options = await db.api.lookup.users({ p_filter: 'bo' }, userId);
|
|
189
|
+
|
|
190
|
+
// Custom functions
|
|
191
|
+
const result = await db.api.myCustomFunction({ param: 'value' }, userId);
|
|
192
|
+
|
|
193
|
+
// Start server
|
|
194
|
+
const server = createServer({
|
|
195
|
+
port: 3000,
|
|
196
|
+
customApi: {}, // Optional: add custom functions
|
|
197
|
+
staticPath: './public' // Optional: serve static files
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Client-Side (Browser/Bun)
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
import { WebSocketManager } from 'dzql/client';
|
|
205
|
+
|
|
206
|
+
// Create connection
|
|
207
|
+
const ws = new WebSocketManager();
|
|
208
|
+
await ws.connect();
|
|
209
|
+
|
|
210
|
+
// Authentication
|
|
211
|
+
const auth = await ws.api.login_user({
|
|
212
|
+
email: 'user@example.com',
|
|
213
|
+
password: 'password'
|
|
214
|
+
});
|
|
215
|
+
// Returns: { token, profile, user_id }
|
|
216
|
+
|
|
217
|
+
// All DZQL operations
|
|
218
|
+
const user = await ws.api.get.users({ id: 1 });
|
|
219
|
+
const saved = await ws.api.save.users({ name: 'Charlie' });
|
|
220
|
+
const deleted = await ws.api.delete.users({ id: 1 });
|
|
221
|
+
const lookup = await ws.api.lookup.users({ p_filter: 'char' });
|
|
222
|
+
const search = await ws.api.search.users({ filters: {} });
|
|
223
|
+
|
|
224
|
+
// Custom functions
|
|
225
|
+
const result = await ws.api.myCustomFunction({ foo: 'bar' });
|
|
226
|
+
|
|
227
|
+
// Real-time events
|
|
228
|
+
const unsubscribe = ws.onBroadcast((method, params) => {
|
|
229
|
+
console.log(`${method}:`, params.data);
|
|
230
|
+
// Events: "users:insert", "users:update", "users:delete"
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Cleanup
|
|
234
|
+
ws.cleanDisconnect();
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Custom Functions
|
|
238
|
+
|
|
239
|
+
Add functions alongside DZQL operations:
|
|
240
|
+
|
|
241
|
+
### PostgreSQL Function
|
|
242
|
+
```sql
|
|
243
|
+
CREATE OR REPLACE FUNCTION transfer_amount(
|
|
244
|
+
p_user_id INT,
|
|
245
|
+
p_from_account INT,
|
|
246
|
+
p_to_account INT,
|
|
247
|
+
p_amount DECIMAL
|
|
248
|
+
) RETURNS TABLE (success BOOLEAN, message TEXT) AS $$
|
|
249
|
+
BEGIN
|
|
250
|
+
-- Your logic here
|
|
251
|
+
RETURN QUERY SELECT true, 'Transfer complete';
|
|
252
|
+
END;
|
|
253
|
+
$$ LANGUAGE plpgsql;
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Bun Function
|
|
257
|
+
```javascript
|
|
258
|
+
// server/api.js
|
|
259
|
+
export async function transfer_amount(userId, params) {
|
|
260
|
+
const { from_account, to_account, amount } = params;
|
|
261
|
+
// Your logic here
|
|
262
|
+
return { success: true, message: 'Transfer complete' };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// server/index.js
|
|
266
|
+
const customApi = await import('./api.js');
|
|
267
|
+
const server = createServer({ customApi });
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Usage
|
|
271
|
+
```javascript
|
|
272
|
+
const result = await ws.api.transfer_amount({
|
|
273
|
+
from_account: 1,
|
|
274
|
+
to_account: 2,
|
|
275
|
+
amount: 100
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Real-time Events
|
|
280
|
+
|
|
281
|
+
Listen for database changes in real-time:
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
ws.onBroadcast((method, params) => {
|
|
285
|
+
if (method === 'users:insert') {
|
|
286
|
+
console.log('New user:', params.data);
|
|
287
|
+
// params: { op: 'insert', table: 'users', data: {...}, notify_users: [...] }
|
|
288
|
+
}
|
|
289
|
+
if (method === 'users:update') {
|
|
290
|
+
console.log('User updated:', params.data);
|
|
291
|
+
}
|
|
292
|
+
if (method === 'users:delete') {
|
|
293
|
+
console.log('User deleted:', params.data);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Graph Rules - Cascading Operations
|
|
299
|
+
|
|
300
|
+
Automatically cascade changes through relationships:
|
|
301
|
+
|
|
302
|
+
```sql
|
|
303
|
+
SELECT dzql.register_entity(
|
|
304
|
+
p_table_name := 'organisations',
|
|
305
|
+
p_label_field := 'name',
|
|
306
|
+
p_searchable_fields := array['name'],
|
|
307
|
+
p_graph_rules := '{
|
|
308
|
+
"on_delete": {
|
|
309
|
+
"cascade_to_teams": {
|
|
310
|
+
"actions": [{
|
|
311
|
+
"type": "delete",
|
|
312
|
+
"entity": "teams",
|
|
313
|
+
"condition": "org_id = @id"
|
|
314
|
+
}]
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
"on_create": {
|
|
318
|
+
"create_default_team": {
|
|
319
|
+
"actions": [{
|
|
320
|
+
"type": "create",
|
|
321
|
+
"entity": "teams",
|
|
322
|
+
"data": {
|
|
323
|
+
"org_id": "@id",
|
|
324
|
+
"name": "Default Team"
|
|
325
|
+
}
|
|
326
|
+
}]
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}'::jsonb
|
|
330
|
+
);
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Available actions: `create`, `update`, `delete`, `insert`, `call_function`
|
|
334
|
+
|
|
335
|
+
## Permissions & Row-Level Security
|
|
336
|
+
|
|
337
|
+
Implement permissions in your entity registration:
|
|
338
|
+
|
|
339
|
+
```sql
|
|
340
|
+
SELECT dzql.register_entity(
|
|
341
|
+
p_table_name := 'posts',
|
|
342
|
+
p_label_field := 'title',
|
|
343
|
+
p_searchable_fields := array['title', 'content'],
|
|
344
|
+
p_permission_rules := '{
|
|
345
|
+
"view": {
|
|
346
|
+
"public_posts": {
|
|
347
|
+
"condition": "public = true OR author_id = @user_id"
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
"edit": {
|
|
351
|
+
"own_posts": {
|
|
352
|
+
"condition": "author_id = @user_id"
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}'::jsonb
|
|
356
|
+
);
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Search Filter Operators
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
const results = await ws.api.search.venues({
|
|
363
|
+
filters: {
|
|
364
|
+
// Exact match
|
|
365
|
+
name: 'Madison Square Garden',
|
|
366
|
+
|
|
367
|
+
// Comparison operators
|
|
368
|
+
capacity: { gt: 1000 }, // Greater than
|
|
369
|
+
capacity: { gte: 1000 }, // Greater or equal
|
|
370
|
+
capacity: { lt: 50000 }, // Less than
|
|
371
|
+
capacity: { lte: 50000 }, // Less or equal
|
|
372
|
+
capacity: { neq: 5000 }, // Not equal
|
|
373
|
+
|
|
374
|
+
// Range
|
|
375
|
+
capacity: { between: [1000, 50000] },
|
|
376
|
+
|
|
377
|
+
// Pattern matching
|
|
378
|
+
name: { like: '%garden%' }, // Case-sensitive
|
|
379
|
+
name: { ilike: '%GARDEN%' }, // Case-insensitive
|
|
380
|
+
|
|
381
|
+
// NULL checks
|
|
382
|
+
description: null, // IS NULL
|
|
383
|
+
description: { not_null: true }, // IS NOT NULL
|
|
384
|
+
|
|
385
|
+
// Arrays
|
|
386
|
+
categories: ['sports', 'music'], // IN array
|
|
387
|
+
categories: { not_in: ['adult'] }, // NOT IN array
|
|
388
|
+
|
|
389
|
+
// Text search (across searchable_fields)
|
|
390
|
+
_search: 'madison garden'
|
|
391
|
+
},
|
|
392
|
+
page: 1,
|
|
393
|
+
limit: 25
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Project Structure
|
|
398
|
+
|
|
399
|
+
```
|
|
400
|
+
my-app/
|
|
401
|
+
├── server/
|
|
402
|
+
│ ├── index.js # Server entry point
|
|
403
|
+
│ └── api.js # Custom API functions (optional)
|
|
404
|
+
├── database/
|
|
405
|
+
│ ├── docker-compose.yml # PostgreSQL setup
|
|
406
|
+
│ ├── init_db/
|
|
407
|
+
│ │ ├── 001_schema.sql # Your tables
|
|
408
|
+
│ │ └── 002_entities.sql # Entity registration
|
|
409
|
+
│ └── seeds/ # Sample data (optional)
|
|
410
|
+
├── client/
|
|
411
|
+
│ └── index.html # Frontend (optional)
|
|
412
|
+
├── tests/
|
|
413
|
+
│ └── app.test.js
|
|
414
|
+
├── package.json
|
|
415
|
+
└── bunfig.toml # Bun config (optional)
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Environment Variables
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
# Database
|
|
422
|
+
DATABASE_URL=postgresql://dzql:dzql@localhost:5432/dzql
|
|
423
|
+
|
|
424
|
+
# Server
|
|
425
|
+
PORT=3000
|
|
426
|
+
NODE_ENV=development
|
|
427
|
+
|
|
428
|
+
# JWT
|
|
429
|
+
JWT_SECRET=your-secret-key-min-32-chars
|
|
430
|
+
JWT_EXPIRES_IN=7d
|
|
431
|
+
|
|
432
|
+
# WebSocket
|
|
433
|
+
WS_PING_INTERVAL=30000 # Keep-alive ping (Heroku safe: <55s)
|
|
434
|
+
WS_PING_TIMEOUT=5000
|
|
435
|
+
|
|
436
|
+
# Logging
|
|
437
|
+
LOG_LEVEL=INFO # ERROR, WARN, INFO, DEBUG, TRACE
|
|
438
|
+
LOG_CATEGORIES=ws:debug,db:debug
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## Examples
|
|
442
|
+
|
|
443
|
+
See the [venues example](https://github.com/blueshed/dzql/tree/main/packages/venues) for a complete working application.
|
|
444
|
+
|
|
445
|
+
### Todo App
|
|
446
|
+
```javascript
|
|
447
|
+
// Schema
|
|
448
|
+
CREATE TABLE todos (
|
|
449
|
+
id SERIAL PRIMARY KEY,
|
|
450
|
+
user_id INT REFERENCES users(id),
|
|
451
|
+
title TEXT NOT NULL,
|
|
452
|
+
completed BOOLEAN DEFAULT FALSE,
|
|
453
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
SELECT dzql.register_entity('todos', 'title', array['title']);
|
|
457
|
+
|
|
458
|
+
// Client
|
|
459
|
+
const todo = await ws.api.save.todos({ title: 'Learn DZQL' });
|
|
460
|
+
const list = await ws.api.search.todos({
|
|
461
|
+
filters: { completed: false },
|
|
462
|
+
limit: 100
|
|
463
|
+
});
|
|
464
|
+
await ws.api.save.todos({ id: todo.id, completed: true });
|
|
465
|
+
await ws.api.delete.todos({ id: todo.id });
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Error Handling
|
|
469
|
+
|
|
470
|
+
```javascript
|
|
471
|
+
try {
|
|
472
|
+
const user = await ws.api.get.users({ id: 999 });
|
|
473
|
+
} catch (error) {
|
|
474
|
+
// Common errors:
|
|
475
|
+
// "record not found" - Record doesn't exist
|
|
476
|
+
// "Permission denied: view on users" - Access denied
|
|
477
|
+
// "entity users not configured" - Entity not registered
|
|
478
|
+
// "Column foo does not exist in table users" - Invalid column
|
|
479
|
+
console.error(error.message);
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Getting Help
|
|
484
|
+
|
|
485
|
+
- **Documentation**: [GETTING_STARTED.md](./GETTING_STARTED.md)
|
|
486
|
+
- **GitHub**: https://github.com/blueshed/dzql
|
|
487
|
+
- **Issues**: https://github.com/blueshed/dzql/issues
|
|
488
|
+
- **Email**: support@blueshed.com
|
|
489
|
+
|
|
490
|
+
## License
|
|
491
|
+
|
|
492
|
+
MIT - See LICENSE file
|
|
493
|
+
|
|
494
|
+
## Authors
|
|
495
|
+
|
|
496
|
+
Created by [Blueshed](https://blueshed.com)
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
**Ready to build?** Start with [GETTING_STARTED.md](./GETTING_STARTED.md) 🚀
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dzql",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
4
|
"description": "PostgreSQL-powered framework with zero boilerplate CRUD operations and real-time WebSocket synchronization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/server/index.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"src/**/*.js",
|
|
15
15
|
"src/database/migrations/**/*.sql",
|
|
16
16
|
"README.md",
|
|
17
|
+
"GETTING_STARTED.md",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
],
|
|
19
20
|
"scripts": {
|