dzql 0.5.27 → 0.5.28
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
|
@@ -247,7 +247,39 @@ $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;`;
|
|
|
247
247
|
scopeTables: scopeTables
|
|
248
248
|
});
|
|
249
249
|
|
|
250
|
-
//
|
|
250
|
+
// Pure collection mode: no rootEntity, only relations
|
|
251
|
+
if (!this.rootEntity) {
|
|
252
|
+
// Build relation-only selects (strip leading comma)
|
|
253
|
+
const collectionSelects = this._generateCollectionOnlySelects();
|
|
254
|
+
|
|
255
|
+
return `CREATE OR REPLACE FUNCTION get_${this.name}(
|
|
256
|
+
p_params JSONB,
|
|
257
|
+
p_user_id INT
|
|
258
|
+
) RETURNS JSONB AS $$
|
|
259
|
+
DECLARE
|
|
260
|
+
v_data JSONB;
|
|
261
|
+
BEGIN
|
|
262
|
+
-- Check access control
|
|
263
|
+
IF NOT ${this.name}_can_subscribe(p_user_id, p_params) THEN
|
|
264
|
+
RAISE EXCEPTION 'Permission denied';
|
|
265
|
+
END IF;
|
|
266
|
+
|
|
267
|
+
-- Build document with collections only (no root entity)
|
|
268
|
+
SELECT jsonb_build_object(
|
|
269
|
+
${collectionSelects}
|
|
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
|
+
|
|
282
|
+
// Dashboard mode: empty paramSchema with rootEntity means aggregate root as array
|
|
251
283
|
if (params.length === 0) {
|
|
252
284
|
return `CREATE OR REPLACE FUNCTION get_${this.name}(
|
|
253
285
|
p_params JSONB,
|
|
@@ -388,6 +420,33 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
|
|
|
388
420
|
return selects;
|
|
389
421
|
}
|
|
390
422
|
|
|
423
|
+
/**
|
|
424
|
+
* Generate collection-only selects (no root entity)
|
|
425
|
+
* Used when rootEntity is null/empty - pure collection mode
|
|
426
|
+
* @private
|
|
427
|
+
*/
|
|
428
|
+
_generateCollectionOnlySelects() {
|
|
429
|
+
if (Object.keys(this.relations).length === 0) {
|
|
430
|
+
return "'_empty', '{}'::jsonb";
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const selects = Object.entries(this.relations).map(([relName, relConfig]) => {
|
|
434
|
+
const relEntity = typeof relConfig === 'string' ? relConfig : relConfig.entity;
|
|
435
|
+
const relFilter = typeof relConfig === 'object' ? relConfig.filter : null;
|
|
436
|
+
|
|
437
|
+
// For collection mode, filter should be TRUE or we fetch all
|
|
438
|
+
const filterSQL = relFilter === 'TRUE' ? 'TRUE' : 'TRUE';
|
|
439
|
+
|
|
440
|
+
return `'${relName}', COALESCE((
|
|
441
|
+
SELECT jsonb_agg(row_to_json(rel.*))
|
|
442
|
+
FROM ${relEntity} rel
|
|
443
|
+
WHERE ${filterSQL}
|
|
444
|
+
), '[]'::jsonb)`;
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
return selects.join(',\n ');
|
|
448
|
+
}
|
|
449
|
+
|
|
391
450
|
/**
|
|
392
451
|
* Generate JOIN clause for via relations
|
|
393
452
|
* Handles multi-hop via chains by looking up each intermediate table
|
|
@@ -512,9 +571,11 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
|
|
|
512
571
|
const cases = [];
|
|
513
572
|
const seenEntities = new Set();
|
|
514
573
|
|
|
515
|
-
// Case 1: Root entity changed
|
|
516
|
-
|
|
517
|
-
|
|
574
|
+
// Case 1: Root entity changed (only if rootEntity exists)
|
|
575
|
+
if (this.rootEntity) {
|
|
576
|
+
cases.push(this._generateRootAffectedCase());
|
|
577
|
+
seenEntities.add(this.rootEntity);
|
|
578
|
+
}
|
|
518
579
|
|
|
519
580
|
// Case 2: Related entities changed (skip duplicates)
|
|
520
581
|
for (const [relName, relConfig] of Object.entries(this.relations)) {
|
|
@@ -664,7 +725,10 @@ $$ LANGUAGE plpgsql IMMUTABLE;`;
|
|
|
664
725
|
* @returns {string[]} Array of table names
|
|
665
726
|
*/
|
|
666
727
|
extractScopeTables() {
|
|
667
|
-
const tables = new Set(
|
|
728
|
+
const tables = new Set();
|
|
729
|
+
if (this.rootEntity) {
|
|
730
|
+
tables.add(this.rootEntity);
|
|
731
|
+
}
|
|
668
732
|
|
|
669
733
|
const extractFromRelations = (relations) => {
|
|
670
734
|
for (const [relName, relConfig] of Object.entries(relations || {})) {
|
|
@@ -701,8 +765,10 @@ $$ LANGUAGE plpgsql IMMUTABLE;`;
|
|
|
701
765
|
buildPathMapping() {
|
|
702
766
|
const paths = {};
|
|
703
767
|
|
|
704
|
-
// Root entity maps to top level
|
|
705
|
-
|
|
768
|
+
// Root entity maps to top level (only if it exists)
|
|
769
|
+
if (this.rootEntity) {
|
|
770
|
+
paths[this.rootEntity] = '.';
|
|
771
|
+
}
|
|
706
772
|
|
|
707
773
|
const buildPaths = (relations, parentPath = '') => {
|
|
708
774
|
for (const [relName, relConfig] of Object.entries(relations || {})) {
|