dzql 0.1.0-alpha.1 → 0.1.0-alpha.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/GETTING_STARTED.md +1102 -0
- package/README.md +508 -0
- package/package.json +3 -1
- package/src/server/index.js +10 -1
package/README.md
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
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
|
+
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
|
|
447
|
+
```
|
|
448
|
+
|
|
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
|
|
477
|
+
|
|
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
|
+
```
|
|
490
|
+
|
|
491
|
+
## Getting Help
|
|
492
|
+
|
|
493
|
+
- **Documentation**: [GETTING_STARTED.md](./GETTING_STARTED.md)
|
|
494
|
+
- **GitHub**: https://github.com/blueshed/dzql
|
|
495
|
+
- **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) 🚀
|
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.3",
|
|
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": {
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
"jose": "^6.1.0",
|
|
25
26
|
"postgres": "^3.4.7"
|
|
26
27
|
},
|
|
28
|
+
|
|
27
29
|
"keywords": [
|
|
28
30
|
"postgresql",
|
|
29
31
|
"postgres",
|
package/src/server/index.js
CHANGED
|
@@ -13,7 +13,8 @@ export function createServer(options = {}) {
|
|
|
13
13
|
port = process.env.PORT || 3000,
|
|
14
14
|
customApi = {},
|
|
15
15
|
routes = {},
|
|
16
|
-
staticPath = null // No default static path - applications should specify
|
|
16
|
+
staticPath = null, // No default static path - applications should specify
|
|
17
|
+
onReady = null // Optional callback that receives { broadcast, server } after initialization
|
|
17
18
|
} = options;
|
|
18
19
|
|
|
19
20
|
// Merge default API with custom API
|
|
@@ -50,6 +51,14 @@ export function createServer(options = {}) {
|
|
|
50
51
|
|
|
51
52
|
routes['/health'] = () => new Response("OK", { status: 200 });
|
|
52
53
|
|
|
54
|
+
// Call onReady callback if provided to allow dynamic route setup
|
|
55
|
+
if (onReady && typeof onReady === 'function') {
|
|
56
|
+
const additionalRoutes = onReady({ broadcast, routes });
|
|
57
|
+
if (additionalRoutes && typeof additionalRoutes === 'object') {
|
|
58
|
+
Object.assign(routes, additionalRoutes);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
53
62
|
// Create and start the Bun server
|
|
54
63
|
const server = Bun.serve({
|
|
55
64
|
port,
|