@dav3/gql-of-power 1.7.0 → 1.8.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.
|
@@ -507,6 +507,10 @@ class GQLtoSQLMapper {
|
|
|
507
507
|
parentAlias: alias,
|
|
508
508
|
alias: childAlias,
|
|
509
509
|
parentGqlFieldNameKey: parentGqlFieldNameKey,
|
|
510
|
+
// Pass the inline filter (from `books(filter: {...})`) to the
|
|
511
|
+
// CHILD's recursiveMap so the WHERE lands inside the child's
|
|
512
|
+
// lateral join subquery — child-row filtering, NOT EXISTS on parent.
|
|
513
|
+
gqlFilters: mapping.inlineFilter ? [mapping.inlineFilter] : undefined,
|
|
510
514
|
});
|
|
511
515
|
variables_1.logger.log('NEW MAPPING:', { parentGqlFieldNameKey, gqlFieldName }, [...newMappings.keys()], (0, gql_to_sql_types_1.mappingsTypeToString)(mapping, true));
|
|
512
516
|
const newMapping = utils_1.QueriesUtils.newMappings();
|
|
@@ -567,41 +571,28 @@ class GQLtoSQLMapper {
|
|
|
567
571
|
const { filter, pagination } = args ?? {};
|
|
568
572
|
variables_1.logger.log(prefix, 'args', parentGqlFieldNameKey, { ...filter }, JSON.stringify(pagination));
|
|
569
573
|
if (filter || pagination) {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
alias,
|
|
591
|
-
gqlFilters: [
|
|
592
|
-
{ [parentGqlFieldNameKey]: { ...convertedFilter } },
|
|
593
|
-
],
|
|
594
|
-
isFieldFilter: true,
|
|
595
|
-
});
|
|
596
|
-
const { innerJoin, where: w, values, _or, _and, _not } = utils_1.QueriesUtils.mappingsReducer(mapped);
|
|
597
|
-
mapping.innerJoin.push(...innerJoin);
|
|
598
|
-
mapping.where.push(...w);
|
|
599
|
-
mapping.values = { ...mapping.values, ...values };
|
|
574
|
+
if (filter) {
|
|
575
|
+
// Convert mapNumericEnum string keys → raw DB values for inline
|
|
576
|
+
// field filter args. The filter keys belong to the TARGET entity
|
|
577
|
+
// (e.g. Book), not the parent (e.g. Author). We WRAP the filter
|
|
578
|
+
// under the field name and convert using the PARENT entity's
|
|
579
|
+
// registries — convertRelationField / convertMappedCustomField
|
|
580
|
+
// then resolve the target entity and recurse into its enum
|
|
581
|
+
// fields, handling arbitrary nesting depth.
|
|
582
|
+
const gqlEntityName = (0, gql_entity_1.getGQLEntityNameFor)(entityMetadata.name ?? '');
|
|
583
|
+
const parentEnumFields = (0, gql_entity_1.getMapEnumFieldsFor)(gqlEntityName);
|
|
584
|
+
const parentCustomFields = (0, gql_entity_1.getCustomFieldsFor)(gqlEntityName);
|
|
585
|
+
const parentRelationFields = (0, gql_entity_1.getRelationFieldsFor)(gqlEntityName);
|
|
586
|
+
const wrappedConverted = (0, enum_filter_converter_1.convertFilterEnumValues)({ [parentGqlFieldNameKey]: filter }, parentEnumFields, parentCustomFields, parentRelationFields);
|
|
587
|
+
const convertedFilter = wrappedConverted[parentGqlFieldNameKey] ?? filter;
|
|
588
|
+
// Store the converted filter for mapField to pass to the child's
|
|
589
|
+
// recursiveMap. This ensures the WHERE clause lands inside the
|
|
590
|
+
// child's lateral join subquery (child-row filtering), NOT as an
|
|
591
|
+
// EXISTS on the parent.
|
|
592
|
+
mapping.inlineFilter = convertedFilter;
|
|
593
|
+
}
|
|
600
594
|
mapping.limit = pagination?.limit;
|
|
601
595
|
mapping.offset = pagination?.offset;
|
|
602
|
-
mapping._or.push(..._or);
|
|
603
|
-
mapping._and.push(..._and);
|
|
604
|
-
mapping._not.push(..._not);
|
|
605
596
|
mapping.orderBy.push(...(pagination?.orderBy ?? []));
|
|
606
597
|
variables_1.logger.log('GQLtoSQLMapper - handleFieldArguments - processed', filter, 'mapping', (0, gql_to_sql_types_1.mappingsTypeToString)(mapping, true));
|
|
607
598
|
}
|
|
@@ -15,6 +15,15 @@ export type MappingsType = {
|
|
|
15
15
|
_or: MappingsType[];
|
|
16
16
|
_and: MappingsType[];
|
|
17
17
|
_not: MappingsType[];
|
|
18
|
+
/**
|
|
19
|
+
* Converted inline filter args from a relation field's `filter` argument
|
|
20
|
+
* (e.g. `books(filter: { title_eq: "X" })`). Stored by
|
|
21
|
+
* `handleFieldArguments` and consumed by `mapField`, which passes it to the
|
|
22
|
+
* child entity's `recursiveMap` as `gqlFilters` so the WHERE lands inside
|
|
23
|
+
* the child's lateral join subquery (child-row filtering), NOT as an EXISTS
|
|
24
|
+
* on the parent.
|
|
25
|
+
*/
|
|
26
|
+
inlineFilter?: any;
|
|
18
27
|
};
|
|
19
28
|
export declare const mappingsTypeToString: (m: MappingsType, full?: boolean) => string;
|
|
20
29
|
export type FilterMappingType = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dav3/gql-of-power",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Generate a single optimized SQL query from GraphQL operations — eliminate N+1 with recursive field-to-SQL mapping. Ships with agent skill for AI coding assistants.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -998,6 +998,10 @@ export class GQLtoSQLMapper {
|
|
|
998
998
|
parentAlias: alias,
|
|
999
999
|
alias: childAlias,
|
|
1000
1000
|
parentGqlFieldNameKey: parentGqlFieldNameKey,
|
|
1001
|
+
// Pass the inline filter (from `books(filter: {...})`) to the
|
|
1002
|
+
// CHILD's recursiveMap so the WHERE lands inside the child's
|
|
1003
|
+
// lateral join subquery — child-row filtering, NOT EXISTS on parent.
|
|
1004
|
+
gqlFilters: mapping.inlineFilter ? [mapping.inlineFilter] : undefined,
|
|
1001
1005
|
});
|
|
1002
1006
|
|
|
1003
1007
|
logger.log(
|
|
@@ -1182,49 +1186,35 @@ export class GQLtoSQLMapper {
|
|
|
1182
1186
|
|
|
1183
1187
|
logger.log(prefix, 'args', parentGqlFieldNameKey, { ...filter }, JSON.stringify(pagination));
|
|
1184
1188
|
if (filter || pagination) {
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
parentRelationFields
|
|
1205
|
-
);
|
|
1206
|
-
const convertedFilter = wrappedConverted[parentGqlFieldNameKey] ?? filter;
|
|
1207
|
-
|
|
1208
|
-
const mapped = this.recursiveMap({
|
|
1209
|
-
entityMetadata,
|
|
1210
|
-
parentAlias: alias,
|
|
1211
|
-
alias,
|
|
1212
|
-
gqlFilters: [
|
|
1213
|
-
{ [parentGqlFieldNameKey]: { ...convertedFilter } } as GQLEntityFilterInputFieldType<T>,
|
|
1214
|
-
],
|
|
1215
|
-
isFieldFilter: true,
|
|
1216
|
-
});
|
|
1189
|
+
if (filter) {
|
|
1190
|
+
// Convert mapNumericEnum string keys → raw DB values for inline
|
|
1191
|
+
// field filter args. The filter keys belong to the TARGET entity
|
|
1192
|
+
// (e.g. Book), not the parent (e.g. Author). We WRAP the filter
|
|
1193
|
+
// under the field name and convert using the PARENT entity's
|
|
1194
|
+
// registries — convertRelationField / convertMappedCustomField
|
|
1195
|
+
// then resolve the target entity and recurse into its enum
|
|
1196
|
+
// fields, handling arbitrary nesting depth.
|
|
1197
|
+
const gqlEntityName = getGQLEntityNameFor(entityMetadata.name ?? '');
|
|
1198
|
+
const parentEnumFields = getMapEnumFieldsFor(gqlEntityName);
|
|
1199
|
+
const parentCustomFields = getCustomFieldsFor(gqlEntityName);
|
|
1200
|
+
const parentRelationFields = getRelationFieldsFor(gqlEntityName);
|
|
1201
|
+
const wrappedConverted = convertFilterEnumValues(
|
|
1202
|
+
{ [parentGqlFieldNameKey]: filter },
|
|
1203
|
+
parentEnumFields,
|
|
1204
|
+
parentCustomFields,
|
|
1205
|
+
parentRelationFields
|
|
1206
|
+
);
|
|
1207
|
+
const convertedFilter = wrappedConverted[parentGqlFieldNameKey] ?? filter;
|
|
1217
1208
|
|
|
1218
|
-
|
|
1209
|
+
// Store the converted filter for mapField to pass to the child's
|
|
1210
|
+
// recursiveMap. This ensures the WHERE clause lands inside the
|
|
1211
|
+
// child's lateral join subquery (child-row filtering), NOT as an
|
|
1212
|
+
// EXISTS on the parent.
|
|
1213
|
+
mapping.inlineFilter = convertedFilter;
|
|
1214
|
+
}
|
|
1219
1215
|
|
|
1220
|
-
mapping.innerJoin.push(...innerJoin);
|
|
1221
|
-
mapping.where.push(...w);
|
|
1222
|
-
mapping.values = { ...mapping.values, ...values };
|
|
1223
1216
|
mapping.limit = pagination?.limit;
|
|
1224
1217
|
mapping.offset = pagination?.offset;
|
|
1225
|
-
mapping._or.push(..._or);
|
|
1226
|
-
mapping._and.push(..._and);
|
|
1227
|
-
mapping._not.push(..._not);
|
|
1228
1218
|
mapping.orderBy.push(...(pagination?.orderBy ?? []));
|
|
1229
1219
|
logger.log(
|
|
1230
1220
|
'GQLtoSQLMapper - handleFieldArguments - processed',
|
|
@@ -19,6 +19,15 @@ export type MappingsType = {
|
|
|
19
19
|
_or: MappingsType[];
|
|
20
20
|
_and: MappingsType[];
|
|
21
21
|
_not: MappingsType[];
|
|
22
|
+
/**
|
|
23
|
+
* Converted inline filter args from a relation field's `filter` argument
|
|
24
|
+
* (e.g. `books(filter: { title_eq: "X" })`). Stored by
|
|
25
|
+
* `handleFieldArguments` and consumed by `mapField`, which passes it to the
|
|
26
|
+
* child entity's `recursiveMap` as `gqlFilters` so the WHERE lands inside
|
|
27
|
+
* the child's lateral join subquery (child-row filtering), NOT as an EXISTS
|
|
28
|
+
* on the parent.
|
|
29
|
+
*/
|
|
30
|
+
inlineFilter?: any;
|
|
22
31
|
};
|
|
23
32
|
|
|
24
33
|
export const mappingsTypeToString = (m: MappingsType, full = false) => {
|