dzql 0.1.0-alpha.3 → 0.1.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.
@@ -2,6 +2,8 @@
2
2
 
3
3
  DZQL is a PostgreSQL framework that gives you **atomic real-time updates** via WebSocket. Every database change broadcasts instantly to all connected clients. Zero boilerplate.
4
4
 
5
+ > **See also:** [REFERENCE.md](REFERENCE.md) for complete API documentation | [CLAUDE.md](../../CLAUDE.md) for AI development guide
6
+
5
7
  ## The Core Pattern
6
8
 
7
9
  1. **Schema = API**: Define a table → DZQL auto-creates CRUD endpoints
package/README.md CHANGED
@@ -1,91 +1,27 @@
1
- # DZQL - Zero-Boilerplate Database Framework
1
+ # DZQL
2
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.**
3
+ PostgreSQL-powered framework with automatic CRUD operations and real-time WebSocket synchronization.
4
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
- ```
5
+ ## Documentation
26
6
 
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
- ```
7
+ All documentation is maintained in the repository root:
34
8
 
35
- ## Features
9
+ - **[README.md](../../README.md)** - Project overview and quick start
10
+ - **[GETTING_STARTED.md](GETTING_STARTED.md)** - Complete tutorial with working todo app
11
+ - **[REFERENCE.md](REFERENCE.md)** - Complete API reference
12
+ - **[CLAUDE.md](../../CLAUDE.md)** - Development guide for AI assistants
13
+ - **[Venues Example](../venues/)** - Full working application
36
14
 
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
15
+ ## Quick Install
46
16
 
47
- ## Quick Start
48
-
49
- ### 1. Install
50
17
  ```bash
51
18
  bun add dzql
19
+ # or
20
+ npm install dzql
52
21
  ```
53
22
 
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
- ```
23
+ ## Quick Example
87
24
 
88
- ### 5. Use from Client
89
25
  ```javascript
90
26
  import { WebSocketManager } from 'dzql/client';
91
27
 
@@ -93,416 +29,16 @@ const ws = new WebSocketManager();
93
29
  await ws.connect();
94
30
 
95
31
  // 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
- routes: { // Optional: standard HTTP routes
199
- '/health': () => new Response('OK')
200
- },
201
- onReady: async (broadcast) => { // Optional: routes needing broadcast
202
- return {
203
- '/mcp': createMCPRoute(broadcast) // Example: MCP integration
204
- };
205
- }
206
- });
207
- ```
208
-
209
- ### Client-Side (Browser/Bun)
210
-
211
- ```javascript
212
- import { WebSocketManager } from 'dzql/client';
213
-
214
- // Create connection
215
- const ws = new WebSocketManager();
216
- await ws.connect();
217
-
218
- // Authentication
219
- const auth = await ws.api.login_user({
220
- email: 'user@example.com',
221
- password: 'password'
222
- });
223
- // Returns: { token, profile, user_id }
224
-
225
- // All DZQL operations
226
- const user = await ws.api.get.users({ id: 1 });
227
- const saved = await ws.api.save.users({ name: 'Charlie' });
228
- const deleted = await ws.api.delete.users({ id: 1 });
229
- const lookup = await ws.api.lookup.users({ p_filter: 'char' });
230
- const search = await ws.api.search.users({ filters: {} });
231
-
232
- // Custom functions
233
- const result = await ws.api.myCustomFunction({ foo: 'bar' });
234
-
235
- // Real-time events
236
- const unsubscribe = ws.onBroadcast((method, params) => {
237
- console.log(`${method}:`, params.data);
238
- // Events: "users:insert", "users:update", "users:delete"
239
- });
240
-
241
- // Cleanup
242
- ws.cleanDisconnect();
243
- ```
244
-
245
- ## Custom Functions
246
-
247
- Add functions alongside DZQL operations:
248
-
249
- ### PostgreSQL Function
250
- ```sql
251
- CREATE OR REPLACE FUNCTION transfer_amount(
252
- p_user_id INT,
253
- p_from_account INT,
254
- p_to_account INT,
255
- p_amount DECIMAL
256
- ) RETURNS TABLE (success BOOLEAN, message TEXT) AS $$
257
- BEGIN
258
- -- Your logic here
259
- RETURN QUERY SELECT true, 'Transfer complete';
260
- END;
261
- $$ LANGUAGE plpgsql;
262
- ```
263
-
264
- ### Bun Function
265
- ```javascript
266
- // server/api.js
267
- export async function transfer_amount(userId, params) {
268
- const { from_account, to_account, amount } = params;
269
- // Your logic here
270
- return { success: true, message: 'Transfer complete' };
271
- }
272
-
273
- // server/index.js
274
- const customApi = await import('./api.js');
275
- const server = createServer({ customApi });
276
- ```
277
-
278
- ### Usage
279
- ```javascript
280
- const result = await ws.api.transfer_amount({
281
- from_account: 1,
282
- to_account: 2,
283
- amount: 100
284
- });
285
- ```
286
-
287
- ## Real-time Events
288
-
289
- Listen for database changes in real-time:
290
-
291
- ```javascript
292
- ws.onBroadcast((method, params) => {
293
- if (method === 'users:insert') {
294
- console.log('New user:', params.data);
295
- // params: { op: 'insert', table: 'users', data: {...}, notify_users: [...] }
296
- }
297
- if (method === 'users:update') {
298
- console.log('User updated:', params.data);
299
- }
300
- if (method === 'users:delete') {
301
- console.log('User deleted:', params.data);
302
- }
303
- });
304
- ```
305
-
306
- ## Graph Rules - Cascading Operations
307
-
308
- Automatically cascade changes through relationships:
309
-
310
- ```sql
311
- SELECT dzql.register_entity(
312
- p_table_name := 'organisations',
313
- p_label_field := 'name',
314
- p_searchable_fields := array['name'],
315
- p_graph_rules := '{
316
- "on_delete": {
317
- "cascade_to_teams": {
318
- "actions": [{
319
- "type": "delete",
320
- "entity": "teams",
321
- "condition": "org_id = @id"
322
- }]
323
- }
324
- },
325
- "on_create": {
326
- "create_default_team": {
327
- "actions": [{
328
- "type": "create",
329
- "entity": "teams",
330
- "data": {
331
- "org_id": "@id",
332
- "name": "Default Team"
333
- }
334
- }]
335
- }
336
- }
337
- }'::jsonb
338
- );
339
- ```
340
-
341
- Available actions: `create`, `update`, `delete`, `insert`, `call_function`
342
-
343
- ## Permissions & Row-Level Security
344
-
345
- Implement permissions in your entity registration:
346
-
347
- ```sql
348
- SELECT dzql.register_entity(
349
- p_table_name := 'posts',
350
- p_label_field := 'title',
351
- p_searchable_fields := array['title', 'content'],
352
- p_permission_rules := '{
353
- "view": {
354
- "public_posts": {
355
- "condition": "public = true OR author_id = @user_id"
356
- }
357
- },
358
- "edit": {
359
- "own_posts": {
360
- "condition": "author_id = @user_id"
361
- }
362
- }
363
- }'::jsonb
364
- );
365
- ```
366
-
367
- ## Search Filter Operators
368
-
369
- ```javascript
370
- const results = await ws.api.search.venues({
371
- filters: {
372
- // Exact match
373
- name: 'Madison Square Garden',
374
-
375
- // Comparison operators
376
- capacity: { gt: 1000 }, // Greater than
377
- capacity: { gte: 1000 }, // Greater or equal
378
- capacity: { lt: 50000 }, // Less than
379
- capacity: { lte: 50000 }, // Less or equal
380
- capacity: { neq: 5000 }, // Not equal
381
-
382
- // Range
383
- capacity: { between: [1000, 50000] },
384
-
385
- // Pattern matching
386
- name: { like: '%garden%' }, // Case-sensitive
387
- name: { ilike: '%GARDEN%' }, // Case-insensitive
388
-
389
- // NULL checks
390
- description: null, // IS NULL
391
- description: { not_null: true }, // IS NOT NULL
392
-
393
- // Arrays
394
- categories: ['sports', 'music'], // IN array
395
- categories: { not_in: ['adult'] }, // NOT IN array
396
-
397
- // Text search (across searchable_fields)
398
- _search: 'madison garden'
399
- },
400
- page: 1,
401
- limit: 25
402
- });
403
- ```
404
-
405
- ## Project Structure
406
-
407
- ```
408
- my-app/
409
- ├── server/
410
- │ ├── index.js # Server entry point
411
- │ └── api.js # Custom API functions (optional)
412
- ├── database/
413
- │ ├── docker-compose.yml # PostgreSQL setup
414
- │ ├── init_db/
415
- │ │ ├── 001_schema.sql # Your tables
416
- │ │ └── 002_entities.sql # Entity registration
417
- │ └── seeds/ # Sample data (optional)
418
- ├── client/
419
- │ └── index.html # Frontend (optional)
420
- ├── tests/
421
- │ └── app.test.js
422
- ├── package.json
423
- └── bunfig.toml # Bun config (optional)
424
- ```
425
-
426
- ## Environment Variables
427
-
428
- ```bash
429
- # Database
430
- DATABASE_URL=postgresql://dzql:dzql@localhost:5432/dzql
431
-
432
- # Server
433
- PORT=3000
434
- NODE_ENV=development
435
-
436
- # JWT
437
- JWT_SECRET=your-secret-key-min-32-chars
438
- JWT_EXPIRES_IN=7d
439
-
440
- # WebSocket
441
- WS_PING_INTERVAL=30000 # Keep-alive ping (Heroku safe: <55s)
442
- WS_PING_TIMEOUT=5000
443
-
444
- # Logging
445
- LOG_LEVEL=INFO # ERROR, WARN, INFO, DEBUG, TRACE
446
- LOG_CATEGORIES=ws:debug,db:debug
32
+ const user = await ws.api.save.users({ name: 'Alice' });
33
+ const results = await ws.api.search.users({ filters: { name: 'alice' } });
447
34
  ```
448
35
 
449
- ## Examples
450
-
451
- See the [venues example](https://github.com/blueshed/dzql/tree/main/packages/venues) for a complete working application.
452
-
453
- ### Todo App
454
- ```javascript
455
- // Schema
456
- CREATE TABLE todos (
457
- id SERIAL PRIMARY KEY,
458
- user_id INT REFERENCES users(id),
459
- title TEXT NOT NULL,
460
- completed BOOLEAN DEFAULT FALSE,
461
- created_at TIMESTAMPTZ DEFAULT NOW()
462
- );
463
-
464
- SELECT dzql.register_entity('todos', 'title', array['title']);
465
-
466
- // Client
467
- const todo = await ws.api.save.todos({ title: 'Learn DZQL' });
468
- const list = await ws.api.search.todos({
469
- filters: { completed: false },
470
- limit: 100
471
- });
472
- await ws.api.save.todos({ id: todo.id, completed: true });
473
- await ws.api.delete.todos({ id: todo.id });
474
- ```
475
-
476
- ## Error Handling
36
+ ## License
477
37
 
478
- ```javascript
479
- try {
480
- const user = await ws.api.get.users({ id: 999 });
481
- } catch (error) {
482
- // Common errors:
483
- // "record not found" - Record doesn't exist
484
- // "Permission denied: view on users" - Access denied
485
- // "entity users not configured" - Entity not registered
486
- // "Column foo does not exist in table users" - Invalid column
487
- console.error(error.message);
488
- }
489
- ```
38
+ MIT
490
39
 
491
- ## Getting Help
40
+ ## Links
492
41
 
493
- - **Documentation**: [GETTING_STARTED.md](./GETTING_STARTED.md)
494
42
  - **GitHub**: https://github.com/blueshed/dzql
495
43
  - **Issues**: https://github.com/blueshed/dzql/issues
496
- - **Email**: support@blueshed.com
497
-
498
- ## License
499
-
500
- MIT - See LICENSE file
501
-
502
- ## Authors
503
-
504
- Created by [Blueshed](https://blueshed.com)
505
-
506
- ---
507
-
508
- **Ready to build?** Start with [GETTING_STARTED.md](./GETTING_STARTED.md) 🚀
44
+ - **npm**: https://www.npmjs.com/package/dzql