dzql 0.5.26 → 0.5.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dzql",
3
- "version": "0.5.26",
3
+ "version": "0.5.27",
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",
@@ -247,6 +247,38 @@ $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;`;
247
247
  scopeTables: scopeTables
248
248
  });
249
249
 
250
+ // Dashboard mode: empty paramSchema means aggregate root as array
251
+ if (params.length === 0) {
252
+ return `CREATE OR REPLACE FUNCTION get_${this.name}(
253
+ p_params JSONB,
254
+ p_user_id INT
255
+ ) RETURNS JSONB AS $$
256
+ DECLARE
257
+ v_data JSONB;
258
+ BEGIN
259
+ -- Check access control
260
+ IF NOT ${this.name}_can_subscribe(p_user_id, p_params) THEN
261
+ RAISE EXCEPTION 'Permission denied';
262
+ END IF;
263
+
264
+ -- Build document with root as array (dashboard mode)
265
+ SELECT jsonb_build_object(
266
+ '${this.rootEntity}', COALESCE((
267
+ SELECT jsonb_agg(row_to_json(root.*))
268
+ FROM ${this.rootEntity} root
269
+ ), '[]'::jsonb)${relationSelects}
270
+ )
271
+ INTO v_data;
272
+
273
+ -- Return data with embedded schema for atomic updates
274
+ RETURN jsonb_build_object(
275
+ 'data', v_data,
276
+ 'schema', '${schemaJson}'::jsonb
277
+ );
278
+ END;
279
+ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
280
+ }
281
+
250
282
  return `CREATE OR REPLACE FUNCTION get_${this.name}(
251
283
  p_params JSONB,
252
284
  p_user_id INT
@@ -478,13 +510,19 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
478
510
  */
479
511
  _generateAffectedDocumentsFunction() {
480
512
  const cases = [];
513
+ const seenEntities = new Set();
481
514
 
482
515
  // Case 1: Root entity changed
483
516
  cases.push(this._generateRootAffectedCase());
517
+ seenEntities.add(this.rootEntity);
484
518
 
485
- // Case 2: Related entities changed
519
+ // Case 2: Related entities changed (skip duplicates)
486
520
  for (const [relName, relConfig] of Object.entries(this.relations)) {
487
- cases.push(this._generateRelationAffectedCase(relName, relConfig));
521
+ const relEntity = typeof relConfig === 'string' ? relConfig : relConfig.entity;
522
+ if (!seenEntities.has(relEntity)) {
523
+ cases.push(this._generateRelationAffectedCase(relName, relConfig));
524
+ seenEntities.add(relEntity);
525
+ }
488
526
  }
489
527
 
490
528
  const casesSQL = cases.join('\n\n ');
@@ -515,7 +553,15 @@ $$ LANGUAGE plpgsql IMMUTABLE;`;
515
553
  */
516
554
  _generateRootAffectedCase() {
517
555
  const params = Object.keys(this.paramSchema);
518
- const firstParam = params[0] || 'id';
556
+
557
+ // Dashboard mode: empty paramSchema means notify ALL subscribers
558
+ if (params.length === 0) {
559
+ return `-- Root entity (${this.rootEntity}) changed - dashboard mode, notify all
560
+ WHEN '${this.rootEntity}' THEN
561
+ v_affected := ARRAY['{}'::jsonb];`;
562
+ }
563
+
564
+ const firstParam = params[0];
519
565
 
520
566
  return `-- Root entity (${this.rootEntity}) changed
521
567
  WHEN '${this.rootEntity}' THEN