dzql 0.1.0-alpha.4 → 0.1.1
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/CLAUDE.md +931 -0
- package/package.json +3 -4
- package/src/client/ws.js +186 -0
- package/src/database/migrations/010_graph_rules_validation.sql +398 -0
- package/src/server/db.js +73 -0
- package/src/server/index.js +71 -0
package/src/server/db.js
CHANGED
|
@@ -190,6 +190,79 @@ function createEntityProxy(operation) {
|
|
|
190
190
|
);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* DZQL database API
|
|
195
|
+
*
|
|
196
|
+
* Provides server-side access to DZQL operations and custom PostgreSQL functions.
|
|
197
|
+
* All operations require explicit userId parameter (unlike client API which auto-injects).
|
|
198
|
+
*
|
|
199
|
+
* @namespace db.api
|
|
200
|
+
*
|
|
201
|
+
* @property {Object} get - Get single record by primary key
|
|
202
|
+
* @property {Object} save - Create or update record (upsert)
|
|
203
|
+
* @property {Object} delete - Delete record by primary key
|
|
204
|
+
* @property {Object} lookup - Autocomplete lookup by label field
|
|
205
|
+
* @property {Object} search - Advanced search with filters, pagination, sorting
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* // Get a single venue
|
|
209
|
+
* const venue = await db.api.get.venues({ id: 1 }, userId);
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* // Create a new venue
|
|
213
|
+
* const venue = await db.api.save.venues({
|
|
214
|
+
* name: 'Madison Square Garden',
|
|
215
|
+
* org_id: 3,
|
|
216
|
+
* address: '4 Pennsylvania Plaza, New York'
|
|
217
|
+
* }, userId);
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* // Update existing venue
|
|
221
|
+
* const updated = await db.api.save.venues({
|
|
222
|
+
* id: 1,
|
|
223
|
+
* name: 'Updated Name'
|
|
224
|
+
* }, userId);
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* // Delete venue
|
|
228
|
+
* await db.api.delete.venues({ id: 1 }, userId);
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* // Lookup for autocomplete
|
|
232
|
+
* const results = await db.api.lookup.venues({
|
|
233
|
+
* p_filter: 'garden' // Searches label field
|
|
234
|
+
* }, userId);
|
|
235
|
+
* // Returns: [{ value: 1, label: 'Madison Square Garden' }, ...]
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* // Advanced search with filters
|
|
239
|
+
* const results = await db.api.search.venues({
|
|
240
|
+
* p_filters: {
|
|
241
|
+
* city: 'New York',
|
|
242
|
+
* capacity: { gte: 1000 },
|
|
243
|
+
* _search: 'garden' // Full-text search
|
|
244
|
+
* },
|
|
245
|
+
* p_sort: { field: 'name', order: 'asc' },
|
|
246
|
+
* p_page: 1,
|
|
247
|
+
* p_limit: 25
|
|
248
|
+
* }, userId);
|
|
249
|
+
* // Returns: { data: [...], total: 100, page: 1, limit: 25 }
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* // Call custom PostgreSQL function
|
|
253
|
+
* const stats = await db.api.myCustomFunction({ param1: 'value' }, userId);
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* // Call auth functions (no userId required)
|
|
257
|
+
* const user = await db.api.register_user({
|
|
258
|
+
* email: 'user@example.com',
|
|
259
|
+
* password: 'secure123'
|
|
260
|
+
* });
|
|
261
|
+
* const session = await db.api.login_user({
|
|
262
|
+
* email: 'user@example.com',
|
|
263
|
+
* password: 'secure123'
|
|
264
|
+
* });
|
|
265
|
+
*/
|
|
193
266
|
// DZQL database API proxy with custom function support
|
|
194
267
|
export const db = {
|
|
195
268
|
api: new Proxy(
|
package/src/server/index.js
CHANGED
|
@@ -8,6 +8,77 @@ export { sql, db } from "./db.js";
|
|
|
8
8
|
export { metaRoute } from "./meta-route.js";
|
|
9
9
|
export { createMCPRoute } from "./mcp.js";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Create a DZQL server with WebSocket support, real-time updates, and automatic CRUD operations
|
|
13
|
+
*
|
|
14
|
+
* Sets up a Bun server with:
|
|
15
|
+
* - WebSocket endpoint at /ws for real-time communication
|
|
16
|
+
* - JSON-RPC 2.0 protocol for API calls
|
|
17
|
+
* - PostgreSQL NOTIFY/LISTEN for real-time broadcasts
|
|
18
|
+
* - Automatic JWT authentication
|
|
19
|
+
* - Health check endpoint at /health
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} [options={}] - Server configuration options
|
|
22
|
+
* @param {number} [options.port=3000] - Port number to listen on (or process.env.PORT)
|
|
23
|
+
* @param {Object} [options.customApi={}] - Custom Bun functions to expose via WebSocket API
|
|
24
|
+
* Each function receives (userId, params) and can return any JSON-serializable value
|
|
25
|
+
* @param {Object} [options.routes={}] - Additional HTTP routes as { path: handlerFunction }
|
|
26
|
+
* @param {string|null} [options.staticPath=null] - Path to static files directory for serving
|
|
27
|
+
* @param {Function} [options.onReady=null] - Callback invoked after server initialization
|
|
28
|
+
* Receives { broadcast, routes } to allow dynamic route setup
|
|
29
|
+
*
|
|
30
|
+
* @returns {Object} Server instance with the following properties:
|
|
31
|
+
* @returns {number} .port - The port number the server is listening on
|
|
32
|
+
* @returns {Object} .server - The underlying Bun.Server instance
|
|
33
|
+
* @returns {Function} .shutdown - Async function to gracefully shutdown server and close DB connections
|
|
34
|
+
* @returns {Function} .broadcast - Function to send messages to connected WebSocket clients
|
|
35
|
+
* Signature: broadcast(message: string, userIds?: number[])
|
|
36
|
+
* If userIds provided, sends only to those users; otherwise broadcasts to all authenticated users
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Basic server
|
|
40
|
+
* import { createServer } from 'dzql';
|
|
41
|
+
*
|
|
42
|
+
* const server = createServer({ port: 3000 });
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // Server with custom API functions
|
|
46
|
+
* import { createServer, db } from 'dzql';
|
|
47
|
+
*
|
|
48
|
+
* const server = createServer({
|
|
49
|
+
* port: 3000,
|
|
50
|
+
* customApi: {
|
|
51
|
+
* async getVenueStats(userId, params) {
|
|
52
|
+
* const { venueId } = params;
|
|
53
|
+
* return db.api.get.venues({ id: venueId }, userId);
|
|
54
|
+
* }
|
|
55
|
+
* }
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* // Client can call: await ws.api.getVenueStats({ venueId: 1 })
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* // Server with static files and custom routes
|
|
62
|
+
* const server = createServer({
|
|
63
|
+
* port: 3000,
|
|
64
|
+
* staticPath: './public',
|
|
65
|
+
* routes: {
|
|
66
|
+
* '/api/health': () => new Response(JSON.stringify({ status: 'ok' }))
|
|
67
|
+
* }
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // Server with onReady callback for dynamic setup
|
|
72
|
+
* const server = createServer({
|
|
73
|
+
* onReady: ({ broadcast, routes }) => {
|
|
74
|
+
* // Add routes dynamically
|
|
75
|
+
* routes['/api/notify'] = (req) => {
|
|
76
|
+
* broadcast(JSON.stringify({ method: 'alert', params: { msg: 'Hello!' } }));
|
|
77
|
+
* return new Response('Sent');
|
|
78
|
+
* };
|
|
79
|
+
* }
|
|
80
|
+
* });
|
|
81
|
+
*/
|
|
11
82
|
export function createServer(options = {}) {
|
|
12
83
|
const {
|
|
13
84
|
port = process.env.PORT || 3000,
|